Verify a Data Exchange listing offline
When an Edge Agent is about to ingest data offered by a peer organization across the Data Exchange, it must not pull it on trust. It first verifies the peer org's signed cross-org offer listing against a peer-org key it pinned out of band, offline, deny-closed. This tutorial runs that gate end to end with real commands.
The edge holds the verify-only half of the Data Exchange. Signing, minting, settlement, and the live request/approve/provision lifecycle run on a full cell; the edge verifies a hand-carried listing against an out-of-band-pinned peer key, on a possibly disconnected cell. This is a deliberate boundary, not a gap. See Offline verification.
What you will prove
A listing signed by a pinned, active peer org, inside its validity window, whose scope stays within your pinned ceiling, verifies. Anything else, an unknown signer, a tampered field, an expired window, a swapped payload, refuses with a non-zero exit and a signed deny-receipt, so an ingest wrapper never pulls data behind an unverified offer.
Prerequisites
- A device with
s9on itsPATH(Quickstart working). - A peer-org pins file, the pinned peer-org keys in the wire-JSON shape. The baked-in set ships empty, so a device with no pins deny-closes every listing until the fleet or a release provisions pins.
- A hand-carried listing JSON file, the peer org's signed cross-org offer, produced on the peer's cell and carried to you (on removable media, or any out-of-band channel).
The gate
Figure: the edge verify gate. A hand-carried listing is checked against pinned peer keys and the device's anchor-corrected clock; the cascade is deny-closed, so it verifies only if every gate passes and otherwise refuses with a signed receipt.
Table equivalent
| Input | Role |
|---|---|
| Hand-carried listing (candidate) | The peer's signed cross-org offer |
| Pinned peer-org keys (verified) | The out-of-band trust anchor |
| Anchor-corrected clock (neutral) | Judges validity windows; not a wall clock |
s9 exchange verify-listing (edge) | The deny-closed cascade |
| VERIFIED (green) | Every gate passed: safe to ingest |
| REFUSED (red) | A gate failed: signed deny-receipt, non-zero exit |
Step 1: Verify a listing against your pins
s9 exchange verify-listing \
--listing /media/usb/peer-offer.json \
--pins ./peer-org-pins.json
On success the gate prints the verified offer and exits zero:
Cross-org listing VERIFIED
publisher org : org:peer-acme
product : product:telemetry-2026
offer : offer:9f2c
reach : cross_org
scope tags : region:emea, class:telemetry
valid window : 1756598400..1759190400
If you omit --pins, the device uses its baked-in set (which ships empty) so
every listing deny-closes until pins are provisioned. That is the safe default, not
a bug.
Step 2: Bind the offer to the data (defense in depth)
If you know the content hash of the data you are about to pull, bind the offer to it so a swapped payload is caught even when the offer itself is valid:
s9 exchange verify-listing \
--listing /media/usb/peer-offer.json \
--pins ./peer-org-pins.json \
--content-hash "$EXPECTED_SHA256"
Step 3: See the deny-closed refusal
Point the gate at a listing whose signer you have not pinned (or a tampered or expired one). The gate refuses, mints a signed deny-receipt, and exits non-zero:
s9 exchange verify-listing --listing ./unknown-signer.json --pins ./peer-org-pins.json
echo "exit=$?" # non-zero, the pull is refused
The refusal is deterministic and offline: an unknown or revoked signer is refused before any cryptography is run; a tampered field reads as a content-hash mismatch; a listing outside its window fails the validity gate. Each refusal is captured as an answer, not a crash.
Validity windows are judged against the device's anchor-corrected trusted
clock, not the host wall clock, an air-gapped box must not verify against a clock
an attacker can wind back. You can override it for testing with --now <unix>.
What you proved
- A cross-org data pull is gated on a deny-closed verification of the peer's signed offer: no network, no shared secret, no coordinator to phone.
- Trust rests on a key you pinned out of band, and the empty default means an unprovisioned device pulls nothing.
- Every refusal produces a signed deny-receipt with the first failing gate named, so a refusal is auditable evidence, not a silent failure.
Related
- Offline verification, the full gate cascade.
- Signed cross-org listings, how a listing is minted (core side).
- Settlement & accrual, the money side that stays on a cell.
- Edge command reference · Governance, audit & telemetry