# The Mirror Protocol — v1 draft

**Status:** draft for review. **Audience:** a bank's cryptography team, its data-protection officer,
and its counsel. **What this document is for:** so those three people can decide whether Mirror is
safe to join *without* taking a vendor's word for anything.

Everything below is implemented and test-enforced. Where something is *not* implemented, or is
weaker than a reader might assume, it says so in the same sentence. There is no section of this
document whose purpose is to sound impressive.

---

## 1. The problem, stated precisely

The three legs of a trade live in three institutions. The exporter's bank sees the invoice. Customs
sees the declared value. The importer's bank sees the payment. Trade-based money laundering is a
**mismatch between those legs** — declare a 1,000-unit shipment at $300/unit when it is worth $100
and $200,000 has moved under cover of a legitimate-looking trade.

No party can see the mismatch, because no party holds more than one leg. Every incumbent AML system
monitors one institution in isolation. The obvious fix — pool the data — is unlawful, commercially
unacceptable, or both.

Mirror is the join, performed blind.

---

## 2. Roles and trust model

| Role | Holds | Learns |
|---|---|---|
| **Member** (a bank, customs, a trade-data source) | raw values, the corridor key, the shared secret | its own data; whether its leg agreed |
| **Operator** | commitments only | *nothing about any value* — see §9 |
| **Auditor / examiner** | a signed record a member handed them | that the record is authentic and unaltered |
| **Supervisor** (regulator, read-only) | signed tree heads and proofs | that the log has not been rewritten — never a value |

**The operator is not trusted, and does not need to be.** It is structurally unable to read a value
because a value is never in its input. That is a property of the wire format, not a policy, not a
contractual promise, and not a configuration setting. §9 states exactly where the property ends.

---

## 2.1 Threat model — what must remain impossible

Assume all of these adversaries at once, and the guarantee still holds. This table is the security
product; if any row becomes false, that is a Sev-0, not a backlog item.

| Adversary | Goal | Why it fails |
|---|---|---|
| Curious, coerced, or **rooted operator** | Read a value, a name, a locator, a salt | Those objects were never in its input. Not encrypted — **absent** |
| Operator **insider** | Export "the good stuff" | There is no good stuff. The schema holds commitments, public keys, verdicts |
| **Malicious member** | Forge a clear, bury a divergence, learn a counterparty's invoice | Forging needs the other member's key or a hash collision; burial is visible in the append-only log; raw invoices never cross |
| **Compromised bander** (supply chain) | Exfiltrate raw values, or commit the wrong band | Deterministic commitment + member-side auditor replay: the member re-runs the pinned code on its own salt and must get the same commitment |
| **Network observer** | Traffic-analyse who reconciles what, when | ⚠️ **NOT MITIGATED TODAY — see §2.4.** `shipment_ref` travels in the clear so the operator can group a trade's legs. Cover traffic and opaque locators are specified, not default |
| **Examiner or foreign state** compelling Mirror | Compel the values | Mirror can produce only commitments, keys and verdicts. This must be **true**, not policy — see §2.2 |
| **Rogue analyst inside a member** | Use the bander as a dump valve | The bander logs *who ran it*, never *what it saw* |
| **Rollback / replay** | Re-use a cheap table over a price spike | The table is bound into the commitment (the corridor id **is** the hash of the edges) and versions are append-only |

**The claim we make to a CISO** is not "we encrypt well". It is: *full root on Mirror production
yields membership, corridor ids, opaque 256-bit blobs, signed verdicts, and the shipment references
those verdicts are grouped by — nothing else.* If a debug flag, a tracing span, or an error message
ever violates that sentence, the architecture has failed, not the configuration.

That sentence used to end at "signed verdicts — nothing else", which was not true: `shipment_ref` is
in the clear on the wire and in the store (§5.0). A security audit found the contradiction between that
claim and §5.0 and was right to treat it as disqualifying — a reader who catches a spec contradicting
itself has no reason to believe the rows they cannot check. The clause is now in the sentence, and the
exposure it creates has its own section rather than a footnote.

### 2.2 Designed inability, and its limits

The operator's inability is enforced in four places, and it is worth being exact about how strong each
one is:

1. **The wire rejects raw data.** Any payload carrying a value, unit count, band, corridor table,
   secret, salt, entity name, or counterparty name is refused at the door with `400 raw_leaked` — on
   every route, and for the pKYC and flow surfaces with their own blocklists. Rejected, never stripped:
   silently discarding it would teach a caller that sending it is acceptable.
2. **No filing decision can enter.** A separate wall refuses any suspicious-activity field at any
   nesting depth (`400 sar_wall`). See §10.
3. **A build gate on the operator's own source.** A linter fails the build if operator-side code so
   much as *names* a value-shaped field. Encryption-at-rest of data you should not have is how you
   accidentally have it; the control is that the code cannot name it.
4. **The commitment construction itself.** A value is not encrypted on its way in — it is never sent.
   Only a salted hash of a *band index* travels.

**The limit, stated plainly.** Point 3 is a lint over source, which catches a *named* field, not
obfuscation: it cannot stop `leg["amo"+"unt"]`, a base64 blob, or data smuggled through a generic key.
Its realistic threat is the well-meaning engineer adding a debug field, and against that it is
effective. It is not a defence against a determined insider with commit access — nothing in this list
is. What defends against that insider is point 4: even with commit access, the operator cannot recover
a value it was never sent, because the salt lives with the members.

### 2.3 Cryptographic construction notes

- **Binding** comes from the hash: after a member emits a commitment it cannot open it to a different
  band without a second preimage.
- **Hiding from the operator** lives entirely in the salt — 256 bits, derived per shipment via
  HMAC-SHA-256 from a secret the operator never holds. This is what makes the operator's brute force
  infeasible even though the band space is roughly 12–24 buckets.
- **Not hiding from the counterparty.** Stated in §9 and not treated as a defect.
- **Domain separation.** Distinct hash namespaces are tagged and length-prefixed so that fields cannot
  be spliced across them — a locator preimage is `<len>:<field>` concatenated under
  `mirror.locator.v1`, and each banded dimension commits under its own tag. The core band commitment
  concatenates a **fixed-width 64-hex salt** with the band, which is unambiguous for the same reason.
- **Known gap, honestly.** The verdict signature is ES256 (ECDSA P-256), which is **not**
  post-quantum. The *commitments* are hash-based and therefore already PQ-safe, but a verdict that
  must still verify in 2040 should carry a second, lattice-based signature. Mirror does not ship one
  today, and will not hand-roll one: it requires a vetted implementation, and shipping unreviewed
  post-quantum crypto would be worse than the gap it closes.

### 2.4 Residual exposure: what the wire still reveals

This section exists because §2.1 used to claim "no shipment identifiers on the wire" while §5.0 said
`shipment_ref` travels in the clear. Both could not be true. Rather than quietly delete the row, here
is the exposure, stated at the same resolution as everything Mirror does mitigate.

**What is exposed.** Every leg submission carries `shipment_ref` in the clear, and the operator stores
it. So the operator — and anyone who can observe its traffic or compel its logs — learns:

- that two named members reconciled *the same trade reference*, and when;
- how often a given pair of institutions reconciles, and the shape of that timing;
- the reference itself, which in documentary trade is frequently an LC number (§4) and is therefore a
  real-world identifier, not an opaque token.

**What is still NOT exposed**, even to a rooted operator: the value, the unit count, the band, the
corridor's price table, the salt, and the counterparty's customer. Those remain absent, not encrypted.
The exposure here is **traffic analysis of correspondent pairs**, not disclosure of trade terms.

**Why it is like this.** The operator has to group the legs of one trade to compare them, and grouping
needs a join key. `shipment_ref` is the join key that requires no additional coordination between
members, which is why the protocol shipped with it.

**The specified mitigation, and its status.** §4 defines a locator commitment: an opaque, length-prefixed
value both members derive from the corridor secret. Grouping by that instead of by `shipment_ref` gives
the operator a 256-bit blob with no external meaning, and padded fixed-interval batches remove the
timing signal. That construction is **specified but not the default**, and until it is, this row of the
threat table is false and is marked false.

Do not read the mitigation as shipped. A control that exists in a document and not on the wire is a
plan, and this specification does not get to describe plans in the present tense.

## 3. The corridor

A **corridor** is a party-agreed, versioned reference table of price bands for one product class.

- **Mirror invents no prices.** Corridor edges are supplied by the members. There is no default
  price curve anywhere in the implementation, and shipping one would make Mirror a data vendor
  competing with the sources members already license.
- A corridor is identified by its content, so two parties who used the same table get the same id
  without coordinating a label:

  ```
  corridor_id = "c:" + sha256(sorted_edges.join(","))
  ```

- Corridors are **append-only and versioned**. Superseding a table creates a new version; the old
  one is retained forever, so a verdict signed last year can be re-checked against the exact table
  it was judged under. A commitment cites its table version, and **mixing two versions can never
  reconcile** — it is `undetermined`.
- A corridor becomes usable only when a **quorum of distinct members** ratifies it. Quorum counts
  distinct members, never distinct signatures, and is never less than 2.
- **A table's outer bands are open.** Band 0 runs from zero to the first edge and the top band from the
  last edge to infinity, so two legs that both land in either one agree at *any* ratio. A corridor MAY be
  ratified **closed** (`closed: true`, §5): the table is then the half-open range `(first edge, last edge]`,
  and a value outside it can never clear. Only on a closed table can a **match ceiling** (`max_ratio`, §7.1)
  be met, because only there is every clear's window bounded.
- **The shape a new corridor must have.** An operator admits a new `corridor.propose` (and a ratification
  of one still pending) only if it is **closed**, carries a `max_ratio` above 1 and **at most 1.5**, has at
  least two edges, and its widest band at tolerance 0 — with its ratified `edge_margin` — fits under that
  ceiling. Anything else is refused at `/registry/event` with `corridor_not_closed`, `ceiling_required`,
  `ceiling_too_wide`, `corridor_no_interior` or `ceiling_below_table`; a citation corridor (§7.3), which
  cannot be closed yet, with `corridor_shape`. The rule is applied at the door only: replayed history is
  never re-judged. The cap is a released constant (`CORRIDOR_RATIO_CAP` in the kernel), never an operator
  setting.

### 3.2 Federation — a verdict co-signed by independent operators

A corridor MAY name a **federation**: `{ k, operators: [{ kid, org, jwk }] }`, ratified with the table.

- **Shape.** `k` is a whole number, at least 2 and at most the number of operators. The operators are 2 to 16.
  A `kid` is DERIVED from the operator's public key (`"mir-" + sha256(canonical({crv, kty, x, y})).slice(0, 16)`)
  and must equal the one given. Two operators may not share a key, and may not share an organisation name, compared
  case-insensitively with whitespace collapsed. A private key (`d`) is refused. The federation's id is
  `"fd:" + sha256(canonical(federation))`, with the operators sorted by kid.
- **Co-signing.** A verdict is co-signed when k distinct listed operators each return a record that verifies under
  their own listed key, names their own key in the proof, and is for the requested shipment, and the identical
  canonical record is what they signed. One operator counts once, however many answers it gives. Two different
  records for the same shipment are DISSENT: nothing is co-signed, even where k operators agree. An operator that
  does not answer is not a vote against.
- **Governance agreement.** Operators that answer and show a different federation for the corridor, or no corridor,
  disagree and block the co-sign. At least k must have answered with the federation.
- **Offline check.** A co-signed verdict is verified from the artifact and the federation alone: each co-signature
  verifies over the record, each is a listed key, and at least k distinct keys signed.
- **What is not changed.** The record is an ordinary verdict, so every operator still learns only the commitments.
  The federation does not lift the independence gate: two parties alone are undetermined on every operator.

Co-signing is replication with quorum. It is not threshold ECDSA and not secure multi-party computation.

### 3.1 Ratio corridors — a table nobody negotiates

A corridor MAY band the **ratio of each price to a public reference** instead of the price itself. The table
is then dimensionless and published once for every corridor, and the only corridor-specific choice is which
public figures make the reference — a citation, not a price.

- **Published ratio tables** (closed by construction, constants of the protocol):
  `ratio-5 = [0.85, 0.9, 0.95, 1, 1.05, 1.1, 1.15]`, `ratio-10 = [0.85, 0.95, 1.05, 1.15]`,
  `ratio-20 = [0.7, 0.9, 1.1, 1.3]`.
- **The reference set** (ratified with the corridor, public): `{ table, sources, quorum, max_spread, rule }`.
  `sources` is 1–8 citations, each exactly `{ source, instrument, publication, basis, currency, unit }` — text
  only, no figure — all quoting the same currency, unit and basis, none listed twice. `quorum` is a whole
  number from 1 to the number of sources; `max_spread` a decimal in (0, 0.2]; `rule` one of the FX date rules
  (`on_board_date`, `invoice_date`, `payment_value_date`, `fixed_date`). Its id is
  `"rs:" + sha256(canonical(set))`, with `max_spread` as its shortest decimal string.
- **The reference R** is derived **party-side** from the bank's own figures file
  `{ set_id, date, figures: [{ source, value, file_sha256 }] }`: one figure per source, each a plain positive
  decimal with the SHA-256 of the publisher's file it came from. At least `quorum` distinct sources must
  report. With the counted figures sorted, `hi ≤ lo · (1 + max_spread)` must hold **exactly**, or there is no
  reference and nothing is banded. R is the median: the middle figure, or for an even count the exact mean of
  the middle two, written as its shortest decimal string.
- **The pin** `{ set_id, date, rule, figures: [{ source, file_sha256 }] }` (sorted by source; never a value)
  rides inside the committed pricing basis as `ref` (§7.4), so two legs divided by different files read
  `basis_mismatch` and can never clear.
- **The leg** is an ordinary `commitLeg` (§5) with `unit_qty' = unit_qty × R` computed exactly, the ratio
  table as `corridor_edges` and `closed: true`. A product that cannot be represented exactly makes the leg
  unbandable. Commitments are therefore byte-compatible with every implementation of §5.
- **Admission.** A `corridor.propose` carrying `reference` must carry exactly that table's edges and
  `closed: true`; otherwise the registry refuses it and the operator refuses it at `/registry/event` with
  `corridor_reference`. The shape rule of §3 (a ceiling of at most 1.5) still applies.
- **Joining.** One member authors a corridor (`corridor.propose`); others join by accepting it
  (`corridor.ratify`), never by co-authoring it.

**What it does not change.** The operator still sees commitments only: it never learns R, a figure, a
price or a band. **What it adds** is a new attack surface, stated plainly: an adversary may attack the
reference instead of the trade. The quorum median and the spread refusal blunt that; they do not close it.
Goods with no credible public reference — services, bespoke goods — have no ratio corridor and keep a
negotiated closed table.

---

## 4. The locator — how two parties agree they mean the same shipment

This is the part most easily got wrong, and the part a DPO will ask about first.

Two banks must agree they are discussing one shipment **without either sending a
customer-identifying reference to the operator**. An invoice or bill-of-lading number is
commercially sensitive and often identifying, so it never travels in the clear.

```
LP(fields)  = concat over fields of  <byte-length> ":" <field>      (no separator)
inner       = sha256(LP("mirror.locator.v1",        corridor_key, locator_type, parts...))
commitment  = "l:" + sha256(LP("mirror.locator.v1.outer", corridor_key, inner))
```

**Length-prefixing is load-bearing, not decoration.** A bare `"|"` join makes the parse ambiguous:
type `"A|B"` with value `"C"` would hash identically to type `"A"` with value `"B|C"`, letting a
party walk a locator from one shipment onto another's mapping. Every field is length-prefixed so
`(type, parts)` has exactly one parse.

**The corridor key** is printable ASCII with no spaces (`0x21`–`0x7E`), trimmed, 32 to 512 characters.
A key outside that is refused, never truncated. The length is a floor, not an entropy measure.

**Normalisation.** Two families, and the difference is the point:

- **Free-form references** (`lc`, `bl`, and the reference parts of `inv`, `wr`, `po`): remove all
  whitespace, then upper-case. **Punctuation is content** — `LC-2026/0412` and `LC20260412` are
  different references, and Mirror does not decide otherwise.
- **Structured identifiers** (`awb`, the container number in `ctr`, the IMO number in `imo`, and every
  UN/LOCODE): remove whitespace, `-`, `/` and `.`, then upper-case. This is safe only because each
  standard defines a fixed alphanumeric form, so no two identifiers can differ by a separator alone.
- **Dates** have whitespace removed but **keep their hyphens**: strict `YYYY-MM-DD`, years 1900–2999,
  and they must be real calendar dates (`2026-02-30` is refused, not rolled into March).

A numeric input is accepted only when finite; a lost leading zero is **not** restored.

**Recognised locator types**, because real documentary trade is messy. `parts` are hashed in the order
listed, after normalisation:

| Type | `parts`, in order | Validation |
|---|---|---|
| `lc` | letter-of-credit number | 3–64 printable chars |
| `bl` | bill-of-lading number | 3–64 printable chars |
| `inv` | invoice no, invoice date, incoterms, HS4 | every part required; date real; incoterms `[A-Z]{3}`; HS4 exactly 4 digits (`.`/`-` removed) |
| `wr` | issuer, receipt no | both required; issuer 2–64, receipt 3–64 printable chars |
| `po` | issuer, PO no, PO date | all required; issuer 2–64, PO no 3–64; date real |
| `awb` | 11-digit air waybill number | exactly 11 digits; check digit valid (below) |
| `ctr` | container no, port of loading, on-board date | **`full_container_load` must be boolean `true`**; container `[A-Z]{3}U[0-9]{7}` with a valid check digit; port a UN/LOCODE; date real |
| `imo` | 7-digit IMO number, port of loading, load date | **`full_cargo` must be boolean `true`**; an optional leading `IMO` is removed; 7 digits with a valid check digit; port a UN/LOCODE; date real |

Composites (`inv`, `wr`, `po`, `ctr`, `imo`) are objects; a bare string for a composite type is
refused. Every composite refuses if **any** part is missing — a partial composite collides across
different shipments.

**Check digits**, exactly:

- **ISO 6346 container number.** Digits have their face value. Letters run from `A = 10` upward,
  skipping every multiple of 11 (`A=10, B=12 … K=21, L=23 … U=32, V=34 … Z=38`). Take the first ten
  characters, multiply the value at position `i` (0-based) by `2^i`, sum, take `mod 11`, then `mod 10`.
  The result must equal the eleventh character. `CSQU3054383` is valid.
- **IMO number.** Weight the first six digits `7, 6, 5, 4, 3, 2`, sum, and take the last digit of the
  sum. It must equal the seventh digit. `9074729` is valid.
- **Air waybill.** The digits in positions 4–10 (1-based), read as one number, `mod 7`, must equal
  the eleventh digit. `020-12345675` is valid.
- **UN/LOCODE.** Shape only: `[A-Z]{2}[A-Z2-9]{3}`. Whether the code is on the published list is not
  checked.

**The cargo keys are full-load only.** `ctr` and `imo` exist so two lenders holding **different**
documents for the same cargo — a bill of lading at one bank, a warehouse receipt at another, with no
shared reference to alias — can still meet. A consolidated (LCL) container or a part cargo carries
several consignments, so a container- or vessel-level key would join unrelated ones; those commit
their own bill of lading instead. The full-load flag is refused on anything but boolean `true` — a
string, `1` and an omitted flag are all refused. Container and IMO numbers are public and
low-entropy, so a cargo key blinds the operator and outsiders, **not** anyone else who holds the key.

**The hard case is handled explicitly.** One side holds an LC number, the other a BL number, for the
same shipment. A **member-held alias table** maps a locator to the equivalent locators that member
knows about, so a party emits commitments for all of its aliases and matching is set intersection.
The alias table lives inside the institution and never travels. Three conditions fail closed — the
resolution is refused and counted, never guessed:

1. an **ambiguous** alias: one locator mapped to two different shipments;
2. a **contaminated** class: any locator in the shipment's class also claimed by another shipment;
3. a **physical contradiction**: the same container or IMO number bound to two different
   (port, date) legs inside one shipment's class. One box or one full cargo loads once per leg.
   Reusing a box on a later voyage for a *different* shipment is not a contradiction — each leg is
   its own key.

Any unreadable row or entry in the table fails the whole resolution closed.

---

## 5. The commitment

Everything the operator receives about a value is derived as follows, entirely inside the
institution:

```
salt        = HMAC-SHA256(shared_secret, "mirror:" + shipment_ref)      (hex)
band        = count of corridor edges strictly below (value / unit_qty)
commitment  = sha256(salt + ":" + band)                                 for each band in [band-t, band+t]
corridor_commitment = sha256(salt + "|c|" + corridor_id)
```

- `t` is the **tolerance**, capped at 6. A party emits a commitment for its own band and for the
  neighbouring bands it would still accept as consistent.
- **`edge_margin` (optional, the near-edge fix).** A value a rounding error from a band edge is
  fragile: two honest banks can land in different bands and produce a false `divergent`. Widening `t`
  fixes it but enlarges the in-band blind spot everywhere. `edge_margin` is the surgical alternative,
  agreed in the corridor table as a relative fraction `m` (e.g. `0.005` = 0.5%), out of range
  (`m <= 0`, `m > 0.1`, or non-numeric) meaning **off** — never a wide default. When on, the set of
  *core bands* is expanded before the tolerance window is applied:

  ```
  core = { band }
  if band < len(edges)  and  value/unit >= edges[band]   * (1 - m):   core += { band + 1 }   # near the upper edge from below
  if band >= 1          and  value/unit <= edges[band-1] * (1 + m):   core += { band - 1 }   # near the lower edge from above
  commitments = sorted, de-duplicated { sha256(salt + ":" + b) : b in [c-t, c+t] for c in core, b >= 0 }
  ```

  The `(1 - m)` / `(1 + m)` comparisons are **exact decimal** (cross-multiplied as integers, never a
  float). A value in the middle of a band is untouched, so the blind spot is widened only at the edges
  where the wobble is real. With `edge_margin` absent or off the set is exactly `[band-t, band+t]`, so
  no existing commitment changes. Two honest banks straddling one edge now share a commitment and
  reconcile; a genuine multi-band divergence still shares nothing and stays `divergent`.

  **`edge_margin` is ratified in the corridor table, not sent on the wire.** It is a property of the
  agreed table (alongside the edges and the optional `max_ratio` ceiling), so the operator reads it from
  its own ratified registry via the verdict's `corridor_ref` — never from a leg. The leg is unchanged:
  a near-edge hit simply adds one neighbouring commitment, so a leg carries `2t+1` commitments in the
  middle of a band and `2t+2` next to an edge. The operator uses the ratified `m` to read the **match
  window** honestly: it knows a near-edge margin adds at most one band, so it subtracts that one band
  before inferring the tolerance (both counts read as `t`), and it widens the widest reported price
  ratio by exactly `(1 + m) / (1 - m)` — because a value can reach a band from `edges[k-1]·(1 - m)` to
  `edges[k]·(1 + m)`. Rounded **up**: this number states the corridor's blind spot on a signed record
  and must never understate it. Without a ratified `m` the window is computed exactly as before.
- **`closed` (optional, ratified with the table like `edge_margin`).** A boolean or absent; any other
  value is an ambiguous table and the leg is **unbandable** (no commitments). When `true`, the open outer
  bands are not part of the table: after the near-edge expansion, bands `0` and `len(edges)` are removed
  from `core`. If no core band is left — the value is at or below the first edge, or above the last, and
  not within the margin of the edge it is beyond — the leg commits **out-of-range markers** instead:

  ```
  commitments = [ sha256(salt + ":out:" + role + ":" + k)  for k = 0 .. 2t ]      # role lowercased, in k order
  ```

  Otherwise it commits `core` widened by `t` exactly as above. So a value inside the table commits what it
  commits on the open table, byte for byte, except that a near-edge value next to the table's boundary no
  longer also commits the open band beyond it. A marker can match no band commitment and no other role's
  marker, so an out-of-range leg can only read `divergent` — which routes it to review. Two legs agreeing
  far outside the agreed table (a 250/unit invoice and a 25,000/unit declaration both in the top band)
  **cannot clear**. A marker set has `2t+1` members, the count of a mid-band leg, so the operator's
  tolerance inference (and the commitment-count rule of the boundary register) is unchanged, and the
  operator cannot tell an out-of-range leg from any other divergence. The `corridor_id` is the edges'
  own: closure, like the margin, is a table property read from the ratified registry.

  **The closed window.** On a closed table every clear is between two interior bands at most `2t` apart,
  so `reaches_open_band` is `false`, the record carries `closed: true`, and the widest bounded ratio is the
  maximum, over interior bands `b`, of `edges[min(b + 2t, len(edges) - 1)] / edges[b - 1]` (times
  `(1 + m)/(1 - m)` under a margin). It gates the ceiling, so it is computed **exactly** — integer
  arithmetic over the decimal mantissas — and rounded **up** to two decimals: `110/100` is `1.1`, never the
  `1.11` a float ceiling prints, and `1.504` is `1.51`, never `1.5`. A closed table with one edge has no
  interior band: nothing clears under it. An open table's window is unchanged.
- The `corridor_commitment` is salted, so the operator can tell that two legs were banded against
  *the same* agreed table without learning *which* table it is. The counterparty, who holds the salt
  and the table, can open it at audit time.
- **The raw value, the unit count, and the band itself never leave the institution.**

### 5.0 The shared secret — a security parameter, not a configuration detail

`shipment_ref` travels to the operator **in the clear**: the operator has to group the legs of one
trade, and it has nothing else to group them by. The band space is also small — a corridor table has
a handful of edges, so `band` ranges over roughly 5 to 30 values.

Those two facts together mean an operator holding a single leg can run this, with no cryptanalysis
at all:

```
for each candidate shared_secret S:
    salt = HMAC-SHA256(S, "mirror:" + shipment_ref)
    for band b in 0 .. B:
        if sha256(salt + ":" + b) is among the leg's commitments:  the band is b
```

Cost is `(candidate secrets) × (B + 1)` hashes. The band loop is a rounding error. **The entire
distance between "the operator cannot read a band" and "the operator reads every band before lunch"
is the guess cost of `shared_secret`.**

Therefore, normatively:

1. `shared_secret` **MUST** carry at least **128 bits** of entropy. An implementation **MUST**
   refuse to band below that floor rather than warn. The reference bander refuses.
2. `shared_secret` **MUST** be produced by a cryptographically secure random generator. It **MUST
   NOT** be derived from, or equal to, any business artifact — letter-of-credit number, UETR,
   invoice reference, booking reference, or any concatenation of them with a corridor name or year.
   Those pass every statistical entropy test and represent a search space of a few million to anyone
   who knows the format, which every counterparty and every operator does.
3. An implementation **SHOULD** generate the secret itself and record its provenance, because
   requirement 2 is not checkable by measurement. Measurement is the backstop; generation is the
   control.
4. The secret **MUST NOT** reach the operator by any route. An operator holding a per-shipment secret
   does not need to search at all — it derives the salt and opens every commitment directly. There is
   deliberately no operator-side endpoint anywhere in this protocol that accepts, assesses, escrows,
   or recovers a shared secret.

### 5.0.1 Establishing the secret — the channel is part of the threat model

The whole operator-blindness property rests on the two counterparties, and only them, holding the
shared secret. If that secret is intercepted in transit, the scheme is defeated before a commitment is
ever computed — so **how the secret reaches the counterparty is in scope, not an implementation
detail.**

For v1, the establishment channel is **out of band and pre-existing**: the two institutions already
run a trusted channel to agree a corridor at all (the same channel that ratifies the corridor table and
exchanges member key fingerprints at onboarding), and the generated secret is delivered over that
channel. The reference bander's `new-secret` command writes it to an owner-only file for exactly this
hand-off; it is never printed to a shared terminal and never emailed in the clear.

A stated limitation, and the intended v2: the secret is currently a symmetric value delivered over a
trusted side channel, not derived. A later version **SHOULD** establish it by authenticated key
agreement between the institutions' already-registered long-term keys — an X25519 ECDH with the shared
value run through HKDF-SHA256 and bound to the corridor and a per-shipment identifier — so that no
secret value ever transits any channel and compromise of one shipment's material does not touch
another's. That is deliberately **not** claimed as built: shipping an unaudited key-agreement path
would be a worse trade than stating plainly that today the channel is trusted and out of band.

### 5.1 Two dimensions, not one

Banding price-per-unit alone conflates two independent frauds. A party that inflates quantity and
deflates unit price by matching factors produces a price-per-unit band that matches perfectly while
the trade is a structure; and quantity short-shipment is invisible to a price band entirely.

So **quantity** and **unit price** are banded and compared separately, each under its own
domain-separated reference (`"mirror-dim:<dimension>:<locator>"`) so each dimension gets its own
salt and a band-2 quantity commitment can never collide with a band-2 price commitment.

**The combination rule that makes it worth building: a dimension that agrees never masks one that
disagrees.** Any diverging dimension makes the whole reading `divergent`. The reading also judges
the *union* of the policy's dimensions and the dimensions actually present, so a party cannot hide a
divergence by simply not banding that dimension.

---

## 6. Time — matching windows

The legs of a trade are not same-day. Without a time model, "undetermined" is meaningless and a
clear is indefensible, because nothing establishes that the legs belong to one trade.

A window is `[anchor + min_days, anchor + max_days]`, closed at both ends, running forward.
Party-supplied per corridor; the defaults below are **conventions, not law**:

| Role | Window | Measured from |
|---|---|---|
| `invoice` | 0–0 days | (the conventional anchor) |
| `shipment` | 0–30 days | invoice |
| `customs` | 0–30 days | shipment |
| `payment` | 0–90 days | invoice |

Everything ambiguous is `undetermined`, never a pass: a leg out of window, an **undated** leg, a
stale corridor table, legs banded against different table versions, or a leg dated after the
supplied clock.

**One distinction matters operationally more than any other in this section:** a window that is
still **open** with a leg missing is *pending* — the trade is in flight. A window that has **closed**
with a leg missing is a *finding*. Conflating them either floods the queue with in-flight trades or
silently drops the structure.

Nonsense window policy **falls to the stricter default, never the looser one**, and every
substitution is recorded on the face of the record.

---

## 7. The verdict — three states, and no fourth

```
reconciled  | divergent | undetermined
```

- There is **no score**. There is no `0.73 likely laundering`. A probability would make Mirror an
  accusation engine, and would put a number in front of an examiner that no one can defend.
- `cleared` is true only for `reconciled`.
- **Fail-closed runs the opposite way to a permissions system.** Here the dangerous act is *clearing*
  a trade that should have been looked at, so ambiguity must never clear. A missing leg, an
  unbandable value, an unreadable commitment, or legs banded against two different corridors all
  yield `undetermined`. You reach a clear **only on positive evidence that the legs agree.**

Every verdict — including a clear — is signed and canonical. **The clear is the product**: an
examiner asks "why did you not escalate this?" and the answer is a portable file they can check
themselves.

### 7.1 What a clear asserts — exactly

A `reconciled` verdict asserts that the parties' **committed band labels** agree. It does **not** assert
that each label is the band of its submitter's true value: that link is checked at **opening** (§8),
and, where a leg is bound to an origination document (§7.4), against the bank's own signed document.
Every record MUST carry an `asserts` block stating this, with `label_equals_value_band:
"not_proven_here"`. A verifier MUST reject a record whose `asserts` block does not restate the record's
own fields.

**The governed match ceiling.** A clear is only as strong as the window its legs agreed within. When the
named corridor ratified a `max_ratio`, a comparison that agreed stays `reconciled` only if the match window
is bounded (a closed table, §5) and its widest ratio is at most the ceiling. Otherwise — the window reaches
an open band, exceeds the ceiling, or cannot be read at all (a corridor the operator holds no edges for) —
the verdict is `undetermined` with `undetermined_reason: "match_ceiling"` and the ratified `max_ratio` on
the record. A ceiling is never skipped into a clear. Like the independence gate (§7.2), the hold moves
`reconciled` to `undetermined` only, so an offline verifier that recomputes the join and finds it
`reconciled` MUST accept a record held for `match_ceiling`, and MUST NOT accept the reverse.

### 7.2 The independence gate

A comparison that comes out `reconciled` stays `reconciled` only if at least one **anchor** agreed. An
anchor is evidence from a party with no stake in the price:

- a leg signed by a **witness key** the members ratified by quorum under an independent class (customs,
  terminal, port, weighbridge, carrier, warehouse, inspectorate, surveyor, assayer, certifier), whose
  organisation is not a stakeholder's; or
- evidence such a key signed: a terminal's commitment-bulletin entry proven by inclusion (§7.5), or a
  signed measurement.

The anchor MUST be competent on an axis the reading judged, and that axis MUST have agreed. A class lives
on the ratified **key**, never on what a leg says about itself. A **relayed** document (a stakeholder's
segregated desk, class `relay`) anchors only on a corridor that ratified `anchor.accept_relayed`.
Otherwise the verdict is `undetermined`, with `undetermined_reason: "insufficient_independence"`. The
gate moves `reconciled` to `undetermined` only. It never mints a clear, never softens a divergence and
never produces a fourth state.

### 7.3 Nested resolution

Legs MAY also commit at fine resolution under the same salt: `sha256(salt ":fine:" band)` against a fine
table that contains every coarse edge. Coarse agreement with fine disagreement is `divergent` on axis
`resolution`. Once any leg commits finely, every leg MUST, under one agreed (or ratified) fine window;
otherwise the reading is `undetermined`. Every ratified table has a signed **geometry certificate**
stating the widest mispricing that still reconciles, as a ratio (public) and an amount per unit
(members). A corridor MAY be ratified as a **citation** of a published figure, with a closed field set
and no price. Each bank derives the nested table party-side.

### 7.4 Label integrity

A corridor MAY ratify its bander **builds** (a leg from any other build is `undetermined`) and MAY
require every label to be bound to an **origination document**. That is a commitment to the document's
figures, signed at booking by a key of class `origin` held by the bank's trade system and never by the
bander. A leg MAY commit its **pricing basis** (currency, unit, Incoterm, place); a basis mismatch is
`undetermined`. The leg signature MUST cover every block it carries: fine commitments, physical,
dimensions, origin, basis, bander pin.

### 7.5 Physical witnesses, paths and services

A terminal publishes an RFC 6962 **commitment bulletin** of `H(locator, mass commitment, table
commitment)` entries, salted by a receipt secret that travels with the cargo's papers, and signs the
head with its ratified witness key. The operator verifies inclusion and compares the terminal's mass
commitment with each stakeholder's declared-mass set; it sees no kilogram and no container number. A
**path certificate** counts one cargo's hops by opaque locator (states, declared free-zone hops, hops
without an independent or customs-class anchor, and drift from `mirror-chain`), proves each hop into
the verdict log, and is released to a member only when k institutions other than the requester stand
behind it. **Services** use an obligation key derived from the signed statement of work, band hours and
rate as two dimensions, and are witnessed only by a ratified **certifier**.

### 7.6 A cited benchmark

A corridor MAY require a leg with the role `benchmark`: a public market figure (a settlement price, a
published assessment) that one party cites, obtains itself and commits under the shipment's own salt and
the corridor's table exactly as any leg, with unit quantity 1 and a tolerance equal to the market window
the parties agreed (`2w+1` commitments, `w` at most 6). It is compared pairwise with every other required
role like any leg, so requiring it can only add a divergent pair; it can never remove one. Three rules
keep it from changing what a clear asserts about the trade legs:

- The operator MUST NOT count a `benchmark` leg when it infers the tolerance for the match window (§5):
  the window describes the trade legs. A record whose required roles include `benchmark` states the
  benchmark's own window in its `reason`.
- A `benchmark` leg takes no part in nested resolution (§7.3). It is compared at the corridor's
  resolution only, by the operator and by every verifier that recomputes the join.
- It is outside a corridor's origination requirement (§7.4): it cites a public document, not a booking.
  A pricing basis it commits MUST match the trade legs', or the reading is `undetermined`.

A benchmark is not a witness and never satisfies the independence gate (§7.2). Mirror supplies, fetches
and chooses no figure: the citation and the figure stay with the party that committed the leg, and are
checked at opening against the public document.

---

## 8. Signing and the Clear File

- **ES256 (ECDSA P-256)** over the record as **canonical JSON**: recursively sorted keys.
- The signature covers canonical **text**, never an object. Handing an object to a signer
  stringifies it as `"[object Object]"` — a constant — so every record would carry the same
  signature and any signature would verify. The implementation canonicalises internally so a caller
  cannot make that mistake.
- A record must contain **no undefined-valued keys**. `canonical()` signs them as `null` while
  `JSON.stringify` drops them, so a genuine record would fail its own signature after one wire round
  trip. This is test-enforced on every emitted record.
- Key id, derived from the public half only, so signer and verifier reach the same id independently:

  ```
  kid = "mir-" + sha256(canonical({crv, kty, x, y})).slice(0, 16)
  ```

- Verification selects the key by `proof.kid`, never `keys[0]` — a JWKS carries several keys through
  rotation.

### 8.1 Input attestation — signing the legs, not only the verdict

An operator signature proves **one** thing: that this operator computed this verdict. It says nothing
about whether the inputs were real. Without input attestation, a compromised operator can copy the
invoice bank's commitments into the customs slot, obtain a mathematically perfect agreement, and sign
a **clear that is cryptographically flawless and factually invented** — and every offline verifier
in the world would accept it.

So each member signs its own leg, with the **same ratified member key** the registry already
publishes and member login already uses. No second key hierarchy and no second directory.

```
leg_envelope = { v: 1, type: "mirror.leg-commitment/v1",
                 shipment_ref, role, commitments (sorted), corridor_commitment, kid }
attest       = { alg: "ES256", kid, sig = ES256(canonical(leg_envelope)) }
```

Every binding in that envelope is load-bearing: `shipment_ref` stops a leg being replayed onto
another trade, `role` stops it being pasted into another slot, `commitments` stops any alteration,
`corridor_commitment` stops re-judging under a different table, and `kid` puts the author inside the
signed bytes. The `type` tag is domain separation — a leg attestation can never be replayed as a
governance vote, a login assertion, or a cosignature.

The operator verifies each attestation against the **active ratified** member directory, re-checking
membership on every request, and **embeds the attestations in the verdict**. A supervisor holding the
verdict and the published keys then re-checks every input offline, trusting the operator for nothing.

Normatively:

- A leg whose signature does not verify, whose `kid` is unknown, or whose `kid` is not an active
  ratified member **MUST** make the shipment `undetermined`. Unverifiable is treated as bad, never as
  absent — otherwise corrupting a leg would be a way to delete it.
- **Equivocation** — one member signing two different commitment sets for the same role on the same
  shipment — **MUST** make the shipment `undetermined` and **MUST** be attributed to that member.
- A leg carrying **no** attestation **MAY** still reconcile, so a rail can migrate without breaking,
  but the verdict **MUST** state that its inputs were unauthenticated. An implementation **MUST**
  offer a strict mode in which unsigned legs are `undetermined`, and the verdict **MUST** record
  which regime it was judged under.
- Offline verification returns exactly three outcomes: `AUTHENTIC`, `TAMPERED`, or
  `UNAUTHENTICATED_INPUTS`. "Probably fine" is not one of them.

**What this does not fix:** signatures defeat forgery, not **censorship**. An operator can still
withhold a leg and report it missing, and no signature scheme can compel delivery. The member's own
signed copy of that leg is the counter-evidence — the verdict says the leg was absent, the member
holds a signed leg for that exact shipment and role, and both cannot be true. That is a dispute with
proof rather than an argument, and §11's dispute mechanism is where it goes.

**The Clear File** is the artifact an examiner receives: a self-contained HTML file that re-verifies
every signature **in the browser, with the network unplugged**, plus the canonical JSON for the
archive. It has no external references of any kind, and is served under a Content-Security-Policy
that forbids it from making any network request at all — so "it cannot call home" is enforced by the
browser, not promised by us.

### 8.2 Canonical forms and derivations (normative)

Everything an independent implementation must reproduce byte for byte. Each rule is pinned by named
vectors in `/conformance-vectors.json`, and `conformance/reference-impl.js` reproduces them from this
text without importing the kernels. Where this text and a vector disagree, that is a defect to report.

**Text and canonical JSON**

- `canon(v)`:
  - an array is `[` + its elements' `canon`, comma-joined, in order + `]`;
  - an object is `{` + `JSON(key):canon(value)`, comma-joined, keys sorted by UTF-16 code unit + `}`;
  - anything else is `JSON(v)`, where `undefined` becomes `null`.
  - `JSON` is ECMAScript `JSON.stringify`: shortest round-trip numbers, standard string escapes, no whitespace.
- **Signed string values are ASCII.** A record carrying another character is a defect of the emitter, not a variant to accept.
- `lp(x)` = the decimal length of `x` in UTF-16 code units, then `":"`, then `x`. Every multi-part hash below length-prefixes its parts, so no two part-lists collide.
- `H(x)` = lowercase hex SHA-256 of the UTF-8 bytes of `x`.

**Exact decimal banding**

- A decimal is its mantissa and base-10 scale, read from its shortest string. Exponent notation, non-finite and non-positive values do not band.
- `value / unit_qty > edge` iff `v·10^(s_e+s_q) > e·q·10^(s_v)`, in integers.
- `band` = the number of ascending edges passed, stopping at the first that fails.
- There is no floating-point division anywhere on the path.

**Commitments**

| Name | Formula |
|---|---|
| salt | `HMAC-SHA256(secret, "mirror:" + shipment_ref)`, hex |
| band commitment | `H(salt + ":" + band)` for each band from max(0, band−t) to band+t, listed in ascending **band** order (plus the ratified near-edge extension when a corridor sets `edge_margin`) |
| out-of-range marker (§5, closed tables) | `H(salt + ":out:" + role + ":" + k)` for k = 0 … 2t, in k order, with `role` trimmed and lowercased — committed instead of bands when a closed table leaves no interior core band |
| corridor id | `"c:" + H(edges sorted numerically, each as its shortest decimal string, comma-joined)` |
| corridor commitment | `H(salt + "\|c\|" + corridor_id)` |
| fine commitment (§7.3) | `H(salt + ":fine:" + band_fine)` over the fine window, listed sorted as **strings** |
| fine table | `H(salt + "\|f\|" + corridor_id(fine_edges))` |
| pricing basis (§7.4) | `H(salt + "\|b\|" + canon({currency, unit, incoterm, place}))`; currency uppercase ISO 4217, unit lowercase, Incoterm uppercase from the 2020 set, place or `null` |
| origin (§7.4) | `"o:" + H(lp(doc_salt) + lp("mirror:origin:v1") + lp(canon({value, unit_qty, currency, basis, terms_hash})))`; `value` and `unit_qty` are decimal strings, missing optional fields are `null` |

**Physical witness (§7.5)**

- `cargo_key` = the ISO 6346 container number (check digit verified) + `"|"` + the UN/LOCODE + `"|"` + the load date `YYYY-MM-DD`.
- `derive(label)` = `H(lp(receipt_secret) + lp("mirror:bulletin:" + label + ":v1") + lp(cargo_key))`.
- locator = `"cl:" + derive("locator")`; salt = `"ps:" + derive("salt")`.
- mass commitment = `H(lp(salt) + lp("mirror:bulletin:mass") + lp(band_kg))`, where `band_kg` = `bandOf(kg, 1, kg_edges)`. A declared set covers the tolerance window and is listed sorted as strings.
- quality commitment = the same with `"mirror:bulletin:quality"`.
- table commitment = `H(lp(salt) + lp("mirror:bulletin:table") + lp(corridor_id(edges)))`.

**Keys and signatures**

- `kid = "mir-" + H(canon({crv, kty, x, y})).slice(0, 16)`.
- An ES256 signature is `r‖s`, base64url, with **low-S** (`s ≤ n/2`). A high-S twin is the same signing, never a second record.

**Webhooks**

- subscription id = `"wh_" + H("mirror:webhook-subscription:v1|" + canon({kid, url, events})).slice(0, 24)`.
- delivery id = `"whd_" + H("mirror:webhook-delivery:v1|" + subscription_id + "|" + H(canon(judgement(record)))).slice(0, 32)`.
- `judgement(record)` = the record with every property named `sig` or `signature` removed, at any depth.
  A failover resubmission re-signs the same commitments, so it is the same judgement: one delivery, and
  one verdict in every count.

**Field names at the wall**

A key is compared after this normalisation:

1. NFKC;
2. invisible characters removed;
3. listed confusables folded;
4. camelCase split to snake_case;
5. lowercased;
6. runs of non-alphanumerics collapsed to one underscore;
7. edge underscores trimmed.

`Unit Price`, `unitPrice` and `unit_price` are one field.

---

## 9. What each party can learn — stated exactly

This section is the one a DPO should read twice. **It is deliberately unflattering.**

**The operator learns:** that two legs did or did not land in the same band; that they were banded
against the same corridor (not which); opaque locator, entity and node commitments; counts;
timestamps. It cannot invert a commitment: it lacks the salt, which is derived from a secret it
never holds.

**A counterparty learns more than the operator, and this is the real boundary.** The two parties on
a trade share the per-shipment secret. A price band is low-cardinality — typically 12–24 buckets per
product — so a counterparty **can brute-force the other's band by hashing every candidate.** That is
not a flaw to be patched; it follows from the threat model. Mirror's threat model is *"do not pool
raw trade data at a central operator, and do not expose it to unrelated members"* — **not** *"hide
the two legs from each other."* The two banks on a trade are, by construction, already jointly
examining one shipment.

**This is not zero-knowledge, not secure multi-party computation, and not homomorphic encryption.**
Anyone claiming those without building them would be lying. Where the counterparty-visibility gap
actually matters to a member, Mirror ships **real private set intersection** (Diffie–Hellman PSI on
P-256) as a separate, party-to-party step which closes exactly that brute-force — and which is
honest-but-curious secure only, and is documented as such including the reflection attack it cannot
detect.

**Graph topology leaks.** Flow edges and chain hops reveal structure to the operator even though the
nodes are opaque. The mitigation is a k-anonymity release gate that withholds an edge until *k
distinct members* have contributed to its window — which delays detection, a real cost, stated
rather than hidden.

**What Mirror can never do:** compare either party to the physical world. Two colluding parties who
agree a consistent lie reconcile perfectly. Mirror compares parties **to each other**.

---

## 10. Attribution, legal basis, and the SAR wall

- A trigger may name the member that raised it, and **that name counts only if a signature proves
  it.** Otherwise any holder of the pilot token could post the same entity twice under two member
  names and manufacture "corroborated by two institutions" out of one bank's hunch. Unsigned
  triggers are still accepted and still score; they simply stay unattributed and can never become a
  second source.
- A cross-institution signal is shared under a **named legal basis** (FinCEN 314(b), UK ECCTA
  ss.188–189, EU AMLA, or explicit consent) or not at all. The basis is bound into the signature, so
  a member is cryptographically on record for the basis it asserted and cannot later disclaim it.
- **The SAR wall.** The rail is structurally incapable of holding a suspicious-activity filing
  decision. Any field naming one is refused at the door on every endpoint, at any nesting depth.
  Two reasons, both load-bearing: disclosing that a SAR was filed is a **tipping-off offence** in
  most jurisdictions; and if a Mirror record could hold a filing decision, the archive becomes a
  different legal object and members would rationally stop submitting the awkward trades. The
  joint-SAR conversation happens off-rail, referring to a Clear File id.

---

## 11. Supervision without disclosure

Verdicts are appended to an **RFC 6962-shaped transparency log**. A supervisor holds signed tree
heads and demands **consistency proofs**, which prove a newer head append-only-extends an older one:
nothing inserted, reordered, or removed. It never sees a value.

If the operator ever signs **two different roots at the same tree size**, it has signed two
contradictory histories — and because both are signed, that is non-repudiable proof of operator
misbehaviour. This is why multiple independent supervisors comparing heads is what makes the
guarantee real.

A supervisor shown **nothing** reports `undetermined`, never "consistent". A supervisor that says
"all good" because it was shown no evidence is worse than no supervisor at all.

---

## 12. What this protocol does not do

- It does not prove a trade is legitimate. A clear is a **defensible record of why a trade was not
  escalated**, never a guarantee of innocence.
- It does not prove a divergence is laundering. A divergence is **a reason to look**.
- It does not prove, at reconciliation, that a committed label is the band of its submitter's true value.
  That is checked at opening, against the bank's own origination document where one is bound (§7.4). A
  proof of it at reconciliation would need a zero-knowledge circuit, and none is part of this protocol.
- It does not prove that parties who all have a stake in the price told the truth. That is why a clear
  needs an independent anchor (§7.2), and why an anchor confirms only the axis it is competent on.
- It does not prove completeness of participation. A trade never submitted to the rail is invisible
  to it. The completeness digest proves a member disclosed the whole set **it committed to** — not
  that it committed everything it did.
- It does not verify that corridor tables are correct. Garbage corridors give garbage bands, and no
  part of the mechanism can tell a wrong price table from a right one.
- It does not measure detection rate or false-positive rate. No such measurement is performed here
  and none is implied.

---

## 13. Conformance

An independent implementation earns a **signed conformance certificate** by reproducing generated
test vectors byte-for-byte. There is **no partial pass**. The challenge is stateless — the seed
re-derives the reference — so the operator keeps no memory of what it issued.

This exists so the commitment format can become something the industry emits even when it is not
anyone's customer, and so a member can verify a vendor's claim to speak the protocol rather than
trusting a logo.

---

*Mirror is a working name, after central-bank mirror statistics — the practice of comparing what one
country says it exported against what its partner says it imported.*
