# ECHO-OPI/1 — Offline Payment Intent, draft 0.1

Status: experimental, open 0BSD specification and project reference implementations.
No real-money ledger, independent audit, physical-device acceptance or production
identity recovery is asserted. Existing Echo Pay and HAP signed formats are unchanged.

## 1. Object and guarantees

An OPI carries a key's signed instruction to a named recipient on one settlement
domain. It is portable data, not an account, an app, a bearer asset or a proof of
funds. Possession permits inspection and forwarding, never changing the payee.
Transport, inspection, receipt publication, authority and financial finality are
separate boundaries. An offline verifier always reports `pending-sync`.

There are no protocol callbacks, URLs to fetch, telemetry, discovery service,
mandatory Money Musk domain or token. A complete verifier can run offline.

## 2. Exact envelope

The wire is UTF-8 JSON containing exactly `intent` and `signature`. Maximum 1,400
bytes; ASCII printable bytes only; no whitespace outside string values, BOM,
trailing newline, duplicate keys or alternative escaping. Object keys are sorted
lexicographically by ASCII code. Strings use their literal ASCII spelling with
JSON quotation marks. Numbers use ordinary nonnegative decimal integer spelling
with no leading zeros, negative zero, exponent or decimal point. Unknown fields
or versions are refused. No implicit coercion or Unicode normalization occurs.

`intent` contains exactly these fields (table order is explanatory, not wire order):

| Field | Rule |
|---|---|
| `version` | Literal `ECHO-OPI/1` |
| `network` | 1–48 lowercase ASCII letters/digits/`:._-`; first character alphanumeric |
| `asset` | Same alphabet, 1–64 characters; meaningful only inside this network |
| `scale` | Integer 0–18; minor-unit precision, independently pinned by the receiver/adapter |
| `payer`, `payee` | `ed25519:` followed by 64 lowercase hex characters encoding the full 32-byte public key; keys must differ |
| `amount` | String of 1–18 decimal digits, positive, no leading zero; **integer minor units**, never floating-point money |
| `nonce` | 16 cryptographically random bytes encoded as 32 lowercase hex characters |
| `issuedAt`, `expiresAt` | Integer UTC seconds, 0–253402300799; issuedAt < expiresAt <= issuedAt + 604800 |
| `reference` | Empty or 64 lowercase hex characters; optional commitment to an independently agreed invoice/challenge |
| `authority` | Empty or 64 lowercase hex characters; commitment to separate authority evidence, **not proof it passed** |
| `memoCommitment` | Empty or 64 lowercase hex characters; optional commitment to separately shared private data |

Short Echo callsigns are display aliases, not payment routing keys. This draft
address is a full key binding. Mapping an existing Echo identity, rotated key or
delegated device to it requires an explicit, separately verified binding. The lab
does not import, migrate or replace existing Echo keys.

All Ed25519 public keys and signature R points must have canonical encodings,
be non-small-order and torsion-free. Signature S must be canonical (S < L).
Verification uses ordinary Ed25519 with these stricter point checks, not ZIP215
acceptance. The signature is exactly 128 lowercase hex characters.

Signed bytes: UTF-8(`ECHO-OPI/1/sign\n` + canonical(intent)).

Payment ID: `opi1_` + lowercase hex SHA-256 of UTF-8(`ECHO-OPI/1/id\n` +
canonical(intent)). The ID commits to the complete intent rather than the
signature representation. Re-signing the same intent cannot create a new payment
identity. The ID is computed, never accepted as an extra envelope field.

## 3. Verification and trust

`inspect(wire)` validates syntax, version, key encodings and signature. A positive
`SIGNATURE_VALID` report does not establish the payer's real-world identity,
funds, freshness, absence of conflicting spends, authority or settlement.

`inspect(wire, context)` also requires independently configured `network`, `asset`,
`scale`, `payee`, and a UTC `now`; optional `payer` pins a separately confirmed key.
It refuses mismatches and requires issuedAt <= now < expiresAt. A caller must
not construct its expected values by copying the incoming intent. Even the
positive `VERIFIED_PENDING` report has `settlementConfirmed:false` and
`status:pending-sync`. Pinning a key does not certify the person behind it.

The local clock is an input, not a trusted timestamp. A settlement adapter checks
expiry again using its own authoritative admission time. A late first admission
is rejected; a query/retry of an already committed ID returns its original result.
Signature inspection without context deliberately permits historical/expired
evidence; the report explicitly says context was not checked.

The presence of `authority` only sets `authorityReferenced:true`. A gateway must
obtain and verify the actual authority, issuer/delegate binding, amount, recipient,
network, asset, time, attenuation and current revocation state before execution.
An OPI is never an authority grant and cannot widen an existing grant.

Reason codes: MALFORMED, SCHEMA, VERSION, NON_CANONICAL, BAD_SIGNATURE,
CONTEXT_REQUIRED, PAYER_MISMATCH, PAYEE_MISMATCH, CONTEXT_MISMATCH, NOT_YET_VALID,
EXPIRED. Transport parsing additionally uses TRANSPORT. Signing uses
SIGNING_KEY_MISMATCH. CLI request-wrapper failures use INPUT. Rejected input
never receives a payment ID or positive settlement status.

## 4. Transport neutrality

The portable text is `ECHO-OPI1:` + unpadded canonical base64url of the exact wire
bytes. Maximum 1,900 characters. Decode/re-encode must match exactly. A transport
cannot append status, change expiry, substitute a key or normalize signed bytes.
QR uses byte mode, correction level M and a four-module quiet zone. The whole
packet fits a single QR within this profile; fragmented QR is outside version 1.

Implemented clients: copy/paste, bounded `.opi` file import/export, QR display,
image-file decoding and explicitly activated camera scanning. QR decoding alone
does not verify or approve an intent. Relays can carry the same bytes without
permission to alter them. A generic camera may display only text; it is not a
cryptographic verifier. A web client must be loaded/cached before an outage.

NFC and LAN are future adapters, not requirements. Web NFC is NDEF tag access
on supported devices, not universal phone-to-phone NFC. Local LAN HTTPS trust,
discovery, browser permissions and iOS restrictions need explicit profiles;
Wi-Fi Direct and Bluetooth background discovery are not generic browser promises.
Bluetooth native clients remain optional. No transport is proof of proximity.

## 5. Honest finality and replay

Transport received -> signature checked -> context checked -> **pending sync**.
Only a separately authenticated authoritative ledger result can establish finality.
A receipt hash in a log, Claim Proof signature or Bitcoin timestamp is not that
result. They can attest exact bytes/existence, not funds or truth of the receipt.

A future adapter MUST serialize competing admissions atomically, enforce funds,
expiry and authority at the effect boundary, and retain an idempotent outcome
under the full payment ID. The replay/conflict scope includes network, asset,
payer key and nonce. Another intent using that tuple is a conflict, not a retry.
Arrival/commit ordering at that ledger governs conflicts; a payer-selected
issuedAt cannot win priority. Independent browser balances cannot implement
global double-spend prevention. Clearing local state cannot restore funds.

A response lost after submission means **outcome unknown**. Query the same ID;
never reissue under a fresh nonce, refund a reservation, mark settled, or assume
rejection merely because a connection failed. Retain pending bytes and evidence.
Cancellation, issuer revocation, recipient acknowledgement and a final receipt
need separately specified authenticated semantics. Disconnecting cannot undo a
completed payment or deliver a revocation to an unreachable peer.

The existing `echo_nearby_settlements` table is an unverified demo publication log.
This version neither connects it as a settlement adapter nor sends it OPIs.

## 6. Privacy and custody

OPIs are signed plaintext. Cameras, carriers and file recipients can read their
keys, asset, amount and timing. QR is not an encrypted channel. Keep private memo
text out of the object. A memo commitment should include a fresh random salt
shared privately with its opening; an unsalted predictable memo may be guessed.
Never publish the full OPI by default. Even an opaque receipt plus amount/time
reveals metadata. Confidential transport/receipts require a separate reviewed
encryption/disclosure profile, not a claim that hashing erases information.

The reference client generates temporary test keys in memory only. Local storage
holds signed public intents, not signing keys. A reload loses the temporary key;
saved/exported intents remain verifiable. Browser code, extensions, the OS and
the origin remain trusted while signing. Storage loss, rollback and compromise
are not solved by a signature. Durable production key custody and recovery are
separate requirements.

## 7. Echo integration and independent implementations

The experimental [ECHO-REQ/1 companion profile](REQUESTS.md) now binds an OPI
to a receiver-signed request through `reference`, with exact terms and an
independently retained request ID. This adds no fields to the OPI envelope.
Two matching signatures remain pending and do not consume a request globally.

HAP-1 can govern creation/submission at an executor that actually enforces it.
Border Control must verify both the authority and the exact OPI reference;
merely supplying the reference grants nothing. A chosen settlement rail defines
currency/issuer finality, adapter key custody, fees and conflict handling.
Claim Proofs may sign the exact finalized receipt digest, and OpenTimestamps may
anchor its commitment. Neither substitutes for an authenticated ledger outcome.

The TypeScript/noble and independently written Rust/dalek inspectors consume the
same hostile corpus. Rust also builds to WASM; native Rust and WASM share code
and are **not** two independent implementations. Both implementations are
project-maintained, not third-party audited. Version 1 is a draft; incompatible
changes require explicit versioning and new compatibility vectors.

Platform references checked 23 October 2026:
- https://developer.chrome.com/docs/capabilities/nfc
- https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector
- https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API
