Two additions to the WAI standard, rebased clean onto current main: - SPEC.md gains section 6 Confidentiality (OPTIONAL): a security.encryption manifest field, keys as a requirement-of-existence (never transported), relay-sees-only-ciphertext transparency, and a decrypt-then-dispatch refinement of the capability-dispatch algorithm. Conformance/Versioning renumbered to 7/8; the neural intent bullet's replicate-soundness ref follows to 7. No cross-references broken. - New extensions/confidentiality.md sub-spec: byte-level SFrame (RFC 9605 suite 0x0006) framing + HKDF-SHA-512 schedule + nonce, the MLS (RFC 9420) exporter binding with single- vs multi-sender keying, and how it composes with the JWP receipt chain over ciphertext. - extensions/jwp-receipts.md: cross-links payload- vs receipt-confidentiality, and documents the optional wall-clock freshness bound on the live edge (companion to the monotonic group_id guard already in 6.1). Grounded byte-for-byte in the moq-sframe / joule-comm-mls reference impl; the private-channel path is live at wai.transaction.science/private-channel. Co-authored-by: Transaction Science <[email protected]> Co-authored-by: Claude Opus 4.8 <[email protected]>
467 lines
20 KiB
Markdown
467 lines
20 KiB
Markdown
# WAI Extension: JWP Receipt Chain
|
||
|
||
> Status: Draft. Sub-spec of WAI v1.0. Mirrors the Rust reference impl at
|
||
> https://github.com/Transaction-Science/open-standards/tree/main/wai/extensions/jwp-receipts.
|
||
|
||
Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY**, **REQUIRED**,
|
||
**OPTIONAL** are RFC 2119 / RFC 8174.
|
||
|
||
---
|
||
|
||
## 1. Scope and model
|
||
|
||
This extension defines a JWP-shaped receipt chain that attests
|
||
**per-object delivery** of WAI envelope objects through a chain of
|
||
relays. A receipt names the bytes a sink received, the publisher that
|
||
produced them, the energy spent producing them, and the chain of
|
||
relays the bytes traversed — all under a single Ed25519 signature per
|
||
group.
|
||
|
||
In scope:
|
||
|
||
- A per-object content commitment (BLAKE3-256) over WAI envelope bytes.
|
||
- A per-group Merkle root binding every object in a group under one
|
||
signature.
|
||
- A per-group Ed25519 signature over canonical group metadata.
|
||
- A typed receipt envelope (JSON or equivalent CBOR) with explicit
|
||
parent-receipt linkage for cross-hop preservation.
|
||
- Verifier behaviour, relay preservation requirements, and conformance
|
||
test vectors.
|
||
|
||
Out of scope:
|
||
|
||
- A transport. This extension does **not** define how receipts move on
|
||
the wire; receipts ride alongside WAI envelopes in the carrier
|
||
protocol (e.g. a MoQT object header field, an HTTP trailer, a
|
||
sidecar file).
|
||
- A codec. The receipt chain is opaque to the WAI capability dispatch
|
||
in WAI SPEC.md §4. A receipt attests bytes; it does not interpret
|
||
them.
|
||
- An envelope. WAI SPEC.md §2 defines the envelope; this extension
|
||
sits **above** it. A receipt's content hash is computed over the
|
||
full WAI envelope (`WAI1` magic + manifest + payload), not over the
|
||
payload alone.
|
||
|
||
Relationship to WAI SPEC.md:
|
||
|
||
- WAI SPEC.md §2 (container) — receipts hash the container bytes
|
||
end-to-end; a sink that mutates the envelope (re-orders manifest
|
||
keys, re-frames the payload) breaks the content hash and MUST be
|
||
rejected by §3 step 4 below.
|
||
- WAI SPEC.md §4 (capability dispatch) — receipts attest **what was
|
||
delivered to the dispatcher**, not what the dispatcher produced. A
|
||
sink that successfully verifies a receipt then dispatches the
|
||
envelope per §4 unchanged.
|
||
|
||
---
|
||
|
||
## 2. Wire format
|
||
|
||
### 2.1. Per-object content hash
|
||
|
||
For each WAI envelope object the publisher emits, the publisher MUST
|
||
compute a 32-byte BLAKE3-256 content hash:
|
||
|
||
```
|
||
content_hash = BLAKE3(domain || envelope_bytes)
|
||
domain = "moq-jwp:object\x01"
|
||
```
|
||
|
||
`envelope_bytes` is the full WAI envelope per WAI SPEC.md §2 (the
|
||
`WAI1` magic, the manifest-length prefix, the manifest, the payload-
|
||
length prefix, the payload — concatenated, with no insertion or
|
||
reframing). The domain string is appended as a domain separator to
|
||
prevent cross-protocol hash collisions.
|
||
|
||
### 2.2. Per-group Merkle root
|
||
|
||
A **group** is an ordered list of one or more objects under a single
|
||
publisher key. The publisher MUST build a BLAKE3 binary Merkle tree
|
||
over the object content hashes in their group order. Leaves and
|
||
internal nodes use distinct domain separators:
|
||
|
||
```
|
||
leaf(content_hash) = BLAKE3("moq-jwp:merkle-leaf\x01" || content_hash)
|
||
node(left, right) = BLAKE3("moq-jwp:merkle-node\x01" || left || right)
|
||
```
|
||
|
||
When the leaf count at any level is odd, the publisher MUST duplicate
|
||
the final leaf to obtain an even pair. The root is the single hash at
|
||
the top of the tree. The empty-group root is the 32-byte all-zero
|
||
value `0x00..00`.
|
||
|
||
### 2.3. Per-group Ed25519 signature
|
||
|
||
The publisher MUST sign the **canonical signing payload** with an
|
||
Ed25519 key per RFC 8032 (Pure Ed25519, no prehash). The signing
|
||
payload is the big-endian concatenation:
|
||
|
||
```
|
||
signing_payload =
|
||
"moq-jwp:group-sign\x01" // domain
|
||
|| u64_be(track_alias) // optional carrier-scoped id (0 if unused)
|
||
|| u64_be(group_id) // monotonic per-track group counter
|
||
|| merkle_root[32] // §2.2
|
||
|| u64_be(leaf_count) // number of objects in the group
|
||
|| u64_be(total_joules_micro) // sum of microjoules across the group
|
||
|| u32_be(len(group_start)) || group_start_utf8 // RFC 3339 start timestamp
|
||
|| u32_be(len(group_end)) || group_end_utf8 // RFC 3339 end timestamp
|
||
|| publisher_key[32] // Ed25519 verifying key (RFC 8032)
|
||
|| u32_be(len(publisher_id)) || publisher_id_utf8 // bound identity string
|
||
```
|
||
|
||
The signature is exactly 64 bytes. Implementations MUST reject any
|
||
group receipt whose `signature` length is not 64.
|
||
|
||
### 2.4. Receipt envelope (typed)
|
||
|
||
Two receipt kinds are defined: `group` (one per group) and `object`
|
||
(one per object). Both are typed JSON; equivalent CBOR is permitted
|
||
when the carrier mandates it, provided the field set and semantics
|
||
are identical.
|
||
|
||
#### 2.4.1. `kind: "group"`
|
||
|
||
Required fields:
|
||
|
||
| field | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | MUST equal `"group"`. |
|
||
| `track_alias` | u64 | Carrier-scoped track identifier; `0` if unused. |
|
||
| `group_id` | u64 | Monotonic group counter under `publisher_key`. |
|
||
| `root_hash` | 32 B hex | §2.2 Merkle root over object content hashes. |
|
||
| `leaf_count` | u64 | Number of objects covered by `root_hash`. |
|
||
| `joules_total` | u64 | Sum of microjoules across the group's objects. |
|
||
| `group_start` | string | RFC 3339 timestamp of the first object. |
|
||
| `group_end` | string | RFC 3339 timestamp of the last object. |
|
||
| `signer_pubkey` | 32 B hex | Ed25519 verifying key per RFC 8032. |
|
||
| `signer_id` | string | Bound identity string (DID, URN, or display). |
|
||
| `sig` | 64 B hex | Ed25519 signature over §2.3 payload. |
|
||
| `parent_receipt_hash` | 32 B hex or `null` | BLAKE3 of the previous group receipt's canonical bytes under this publisher key; `null` for the first group. |
|
||
|
||
#### 2.4.2. `kind: "object"`
|
||
|
||
Required fields:
|
||
|
||
| field | type | meaning |
|
||
|---|---|---|
|
||
| `kind` | string | MUST equal `"object"`. |
|
||
| `track_alias` | u64 | Mirrors the parent group receipt. |
|
||
| `group_id` | u64 | Mirrors the parent group receipt. |
|
||
| `object_id` | u64 | Monotonic from 0 within the group. |
|
||
| `content_hash` | 32 B hex | §2.1 BLAKE3 over the WAI envelope bytes. |
|
||
| `joules_micro` | u64 | Microjoules consumed producing this object. |
|
||
| `origin` | string | Datacenter / origin label. |
|
||
| `merkle_proof` | object | Inclusion proof: `{ "siblings": [32 B hex, …], "direction_bits": u64, "leaf_index": u64 }`. `direction_bits` bit `i` is `1` iff the current node at level `i` is the right child. |
|
||
|
||
### 2.5. Worked example
|
||
|
||
Manifest JSON (group receipt, formatted for readability — wire form
|
||
serializes with no insignificant whitespace and preserves key
|
||
insertion order):
|
||
|
||
```json
|
||
{
|
||
"kind": "group",
|
||
"track_alias": 7,
|
||
"group_id": 3,
|
||
"root_hash": "5d2c7b1f9a04e2a16c83b3d774a09f12c44b65a3e8d11f88c2901a47b50d2c61",
|
||
"leaf_count": 2,
|
||
"joules_total": 250,
|
||
"group_start": "2026-04-25T12:00:00Z",
|
||
"group_end": "2026-04-25T12:00:01Z",
|
||
"signer_pubkey": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
|
||
"signer_id": "did:joule:publisher-1",
|
||
"sig": "9f3a4d6b1c0e8742f1b6b94e7d1c2a85f5103e4c8b2d61a93f0e4b8f2c70a1d61f6a2b71d4eecbb0d7a2e90c4b1bf2c8a0e4d63b9101fa0e2c4d83b1f70e9a103",
|
||
"parent_receipt_hash": null
|
||
}
|
||
```
|
||
|
||
Object receipt for the second object in that group:
|
||
|
||
```json
|
||
{
|
||
"kind": "object",
|
||
"track_alias": 7,
|
||
"group_id": 3,
|
||
"object_id": 1,
|
||
"content_hash": "1c3f4a2e88b0d77192aabd0c66f12345e89c7b41ab2cd35f7e80b912ad6c9012",
|
||
"joules_micro": 150,
|
||
"origin": "joule-cloud-us-west-2",
|
||
"merkle_proof": {
|
||
"siblings": [
|
||
"a07b4d2c91f5e3110687f4b210ff8e1133acdb0741e3920c19acf8b3110d57e2"
|
||
],
|
||
"direction_bits": 1,
|
||
"leaf_index": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
The signature hex shown is illustrative; conformance is checked
|
||
against the test vectors in §5.
|
||
|
||
---
|
||
|
||
## 3. Verifier algorithm
|
||
|
||
A conforming verifier MUST execute the following six steps in order
|
||
for every group receipt and every object receipt it accepts. Steps
|
||
match the algorithm style of WAI SPEC.md §4 (numbered, terminating).
|
||
|
||
1. **Parse and shape-check.** Read the group receipt. If `kind !=
|
||
"group"`, reject. If `sig` is not 64 bytes, reject. If
|
||
`signer_pubkey` is not a valid 32-byte Ed25519 verifying key
|
||
(RFC 8032), reject. If any required field from §2.4.1 is absent,
|
||
reject.
|
||
|
||
2. **Verify the Ed25519 group signature.** Construct the canonical
|
||
signing payload per §2.3 from the receipt's fields. Verify `sig`
|
||
against `signer_pubkey` per RFC 8032 Pure Ed25519. If verification
|
||
fails, reject. After this step, the verifier holds a **verified
|
||
root**: the receipt's `root_hash` field, now bound to
|
||
`signer_pubkey` under signature.
|
||
|
||
3. **Check parent linkage.** If the verifier maintains receipt-chain
|
||
state for `signer_pubkey`, compute `BLAKE3("moq-jwp:receipt-link\x01"
|
||
|| canonical_bytes(previous_group_receipt))` and compare to
|
||
`parent_receipt_hash`. If they differ, reject (chain break). If
|
||
no previous receipt is known and `parent_receipt_hash` is `null`,
|
||
accept as the chain head. A non-null `parent_receipt_hash` without
|
||
a corresponding previously-verified receipt is a recoverable
|
||
condition: the verifier MAY request the missing receipt before
|
||
accepting; it MUST NOT silently accept.
|
||
|
||
4. **Verify the object's content hash.** For each accompanying
|
||
`kind: "object"` receipt: recompute `content_hash` per §2.1 over
|
||
the received WAI envelope bytes and compare to the receipt's
|
||
`content_hash` field. If they differ, reject the object (the
|
||
envelope was mutated in transit).
|
||
|
||
5. **Verify the Merkle inclusion proof.** Compute the leaf as
|
||
`BLAKE3("moq-jwp:merkle-leaf\x01" || content_hash)`. Walk the
|
||
`merkle_proof.siblings` list from level 0 upward; at each level
|
||
`i`, combine the current value with the sibling using the
|
||
`direction_bits` bit `i` (1 = current is right child, 0 = current
|
||
is left child) and the `moq-jwp:merkle-node\x01` domain. After
|
||
walking all siblings, compare the resulting hash to the verified
|
||
root from step 2. If they differ, reject.
|
||
|
||
6. **Accept.** The object's bytes are bound to `signer_pubkey` at
|
||
`(track_alias, group_id, object_id)`, with the publisher's
|
||
declared `joules_micro` and `origin`. The verifier MAY now persist
|
||
the group receipt as the new chain head for `signer_pubkey` and
|
||
dispatch the WAI envelope per WAI SPEC.md §4.
|
||
|
||
A verifier MUST treat any rejection in steps 1–5 as a hard failure
|
||
for the object in question. A verifier MUST NOT accept a partial
|
||
verification (e.g. valid Merkle proof against an unverified root).
|
||
|
||
---
|
||
|
||
## 4. Relay preservation requirements
|
||
|
||
A **conforming relay** forwards WAI envelopes from one or more
|
||
publishers to one or more subscribers. To preserve the receipt
|
||
chain across hops, a conforming relay:
|
||
|
||
### 4.1. MUST
|
||
|
||
- Forward every group receipt and every object receipt **unaltered**
|
||
to every subscriber that requested receipts for the corresponding
|
||
track. Byte-for-byte equality of receipt bytes between ingress and
|
||
egress is REQUIRED.
|
||
- Verify the group receipt's Ed25519 signature per §3 step 2 before
|
||
the first object of the group is fanned out. A relay that cannot
|
||
verify the signature MUST drop the group and emit a structured
|
||
error to upstream operators.
|
||
- Forward the `parent_receipt_hash` field as received. A relay that
|
||
has previously forwarded the chain MAY additionally check the link
|
||
per §3 step 3 and refuse to fan out a broken chain.
|
||
- Preserve the byte-exact WAI envelope (`WAI1` magic, manifest bytes,
|
||
payload bytes) end-to-end. Any rewriting at the envelope layer
|
||
invalidates §2.1 content hashes.
|
||
|
||
### 4.2. MAY
|
||
|
||
- Batch multiple `kind: "object"` receipts into a carrier-level frame
|
||
to amortize per-receipt overhead, provided every receipt's bytes
|
||
are preserved (no field elision, no canonicalization rewrite).
|
||
- Emit relay-scoped telemetry alongside the forwarded receipts:
|
||
cumulative `joules_total` across observed groups, per-publisher
|
||
delivery counts, fan-out fan-in ratios. Such telemetry is local to
|
||
the relay and MUST NOT be conflated with publisher-issued
|
||
receipts.
|
||
- Reorder objects within a group on egress when the carrier protocol
|
||
permits — Merkle inclusion proofs are position-bound by
|
||
`leaf_index`, so reordering does not invalidate verification at
|
||
the subscriber. The relay MUST NOT renumber `object_id`.
|
||
|
||
### 4.3. MUST NOT
|
||
|
||
- Modify the WAI envelope payload bytes in any way (no transcoding,
|
||
no manifest rewrite, no key reordering, no whitespace insertion).
|
||
Envelope mutation is detected at §3 step 4.
|
||
- Re-issue the Merkle root or re-compute a new tree over a subset of
|
||
objects. The publisher is the sole authority for the group root.
|
||
- Re-sign the group receipt under a relay-owned key. There is one
|
||
signer per group, and it is the publisher named in
|
||
`signer_pubkey`. Relay attestation, when desired, MUST be carried
|
||
as a separate, parallel receipt and MUST NOT replace the publisher
|
||
signature.
|
||
- Strip the `parent_receipt_hash` field, even when its value is
|
||
`null`. The field is required by §2.4.1 and its absence is a
|
||
shape-check failure at §3 step 1.
|
||
- Truncate the receipt to fewer fields than §2.4 requires, even when
|
||
the carrier framing has size pressure.
|
||
|
||
---
|
||
|
||
## 5. Test vectors
|
||
|
||
Conforming implementations MUST verify against the canonical test
|
||
vector set generated by
|
||
`joulesperbit/crates/joule-moq-publisher/examples/jwp_receipts_vectors.rs`.
|
||
The generator emits five vectors covering the cases below. Each
|
||
vector consists of: the WAI envelope bytes for every object in the
|
||
group, the canonical group-receipt JSON, the canonical object-receipt
|
||
JSON for every object, and a `result.json` declaring the expected
|
||
verifier outcome (`accept` or one of the named rejection reasons).
|
||
|
||
### 5.1. Single-object group (vector `v1_single_object`)
|
||
|
||
One WAI envelope (`wai.text.zstd`, payload `"hello"` zstd-encoded).
|
||
Group receipt with `leaf_count = 1`, `parent_receipt_hash = null`.
|
||
Object receipt with `object_id = 0`, `merkle_proof.siblings = []`,
|
||
`direction_bits = 0`, `leaf_index = 0`. Expected outcome: `accept`.
|
||
|
||
### 5.2. Multi-object group with proof verification (vector `v2_multi_object`)
|
||
|
||
Five WAI envelopes (`wai.audio.opus`, 20 ms frames). Group receipt
|
||
with `leaf_count = 5` (the publisher duplicates the trailing leaf
|
||
internally when building the tree). Five object receipts, each with
|
||
a non-empty `merkle_proof`. Expected outcome: `accept` for each
|
||
object receipt.
|
||
|
||
### 5.3. Cross-hop preservation through 2 relays (vector `v3_cross_hop`)
|
||
|
||
Three publisher-issued group receipts in chain order G0, G1, G2 with
|
||
`parent_receipt_hash` chaining each to its predecessor. Two relay
|
||
hops are simulated: ingress bytes → relay A egress bytes → relay B
|
||
egress bytes are emitted as three byte-equal copies. The verifier
|
||
walks the chain in order; expected outcome: `accept` at every hop,
|
||
and the chain head after G2 equals the BLAKE3 of G2's canonical
|
||
bytes per §3 step 3.
|
||
|
||
### 5.4. Invalid signature rejection (vector `v4_bad_sig`)
|
||
|
||
A well-formed group receipt with a single bit flipped in the `sig`
|
||
field. Expected outcome: reject at §3 step 2 with reason
|
||
`signature_verify_failed`.
|
||
|
||
### 5.5. Missing parent_receipt_hash rejection (vector `v5_missing_parent`)
|
||
|
||
Two group receipts G0, G1. G1 omits the `parent_receipt_hash` field
|
||
entirely (rather than setting it to `null` or a hash). Expected
|
||
outcome: reject at §3 step 1 with reason `missing_required_field`.
|
||
|
||
The generator writes vectors to
|
||
`joulesperbit/crates/joule-moq-publisher/examples/jwp_receipts_vectors/`
|
||
as one directory per case. A conforming verifier MUST accept the
|
||
`accept` cases bit-exactly and MUST reject the named cases at the
|
||
declared step with the declared reason. The Rust reference verifier
|
||
in `inv-moq::receipt::ReceiptVerifier` exercises all five vectors
|
||
under `cargo test --lib -p inv-moq`.
|
||
|
||
---
|
||
|
||
## 6. Security considerations
|
||
|
||
### 6.1. Replay
|
||
|
||
A receipt without sequencing information is replayable: an attacker
|
||
who captures group receipt G can re-present it to a downstream
|
||
verifier and bind unrelated bytes to the publisher's identity at a
|
||
later time. This extension mitigates replay with two mechanisms:
|
||
|
||
- `parent_receipt_hash` (§2.4.1) links each group receipt to its
|
||
predecessor under the same `signer_pubkey`, so a chain replay
|
||
requires forging every intermediate hash.
|
||
- `group_id` is monotonic per publisher key. Verifiers SHOULD track
|
||
the highest `group_id` seen per `signer_pubkey` and reject any
|
||
receipt with a non-increasing `group_id`. This binds the chain
|
||
to forward progress.
|
||
- `group_end` (§2.3) carries a signed RFC 3339 timestamp. A verifier
|
||
MAY additionally bound it against its own clock for the group it
|
||
treats as the current tip — rejecting a receipt whose `group_end`
|
||
is older than a deployment freshness window (a relay holding
|
||
content back) or implausibly far in the future (skew). This
|
||
wall-clock check is OPTIONAL and MUST NOT be applied to historical
|
||
receipts replayed for late-joiner resync, whose timestamps are
|
||
legitimately old — apply it only to the live edge. The reference
|
||
verifier exposes both the monotonic `group_id` guard (per track)
|
||
and this freshness bound as opt-in checks, disabled by default so
|
||
durable re-verification of an old receipt stays a feature.
|
||
|
||
### 6.2. Forgery
|
||
|
||
The signing payload (§2.3) is bound under Ed25519 (RFC 8032). At
|
||
classical security levels, Ed25519 provides ~128-bit unforgeability
|
||
under chosen-message attack (EUF-CMA). Forging a group receipt is
|
||
equivalent to recovering the publisher's private key or breaking
|
||
Ed25519. Implementations MUST source signing keys from a CSPRNG and
|
||
MUST NOT reuse keys across distinct publisher identities.
|
||
|
||
### 6.3. Cross-group confusion
|
||
|
||
The signing payload binds `track_alias`, `group_id`, and
|
||
`signer_pubkey` under the same signature. An attacker who attempts
|
||
to re-present a group receipt under a different `(track_alias,
|
||
group_id)` tuple cannot do so without re-signing — the receipt's
|
||
fields are signature-covered, and any field mutation breaks
|
||
verification at §3 step 2. The Merkle proof is similarly bound:
|
||
`leaf_index` is verifier-checked during the proof walk, so an
|
||
object's proof cannot be transplanted to a different position within
|
||
the same group.
|
||
|
||
### 6.4. Envelope mutation in transit
|
||
|
||
Any mutation of the WAI envelope bytes (manifest rewrite, payload
|
||
re-frame, whitespace insertion) changes the §2.1 content hash and is
|
||
detected at §3 step 4. Relays that re-canonicalize the manifest
|
||
SHOULD be treated as broken: they break content-hash equality even
|
||
when the semantic meaning is unchanged. WAI SPEC.md §2 already
|
||
requires that the manifest be serialized with no insignificant
|
||
whitespace and that encoders preserve key insertion order; receipt
|
||
verification depends on relays honouring that requirement.
|
||
|
||
### 6.5. Quorum and multi-signer receipts
|
||
|
||
v1.0 of this extension is single-signer: every group receipt is
|
||
signed by exactly one Ed25519 key. Multi-signer / quorum receipts
|
||
(threshold Ed25519, BLS aggregate signatures) are explicitly out of
|
||
scope for v1.0. A future minor revision MAY extend §2.3 with a
|
||
multi-signer container; the v1.0 wire format remains a strict subset.
|
||
|
||
### 6.6. Out of scope
|
||
|
||
- **Post-quantum group-signing ciphersuites.** Migration to
|
||
hash-based signatures (SLH-DSA), lattice-based signatures
|
||
(ML-DSA), or hybrid Ed25519 + post-quantum receipts is deferred
|
||
to a future major revision of this extension. Implementers
|
||
building for long-lived archives SHOULD plan key-rotation policies
|
||
that allow ciphersuite migration when the upgrade lands.
|
||
- **Confidentiality of receipts.** Receipts carry identity,
|
||
joules, and origin labels in cleartext. Carriers that require
|
||
receipt confidentiality (e.g. trade-secret origin labels) MUST
|
||
layer their own encryption above this extension; v1.0 receipts
|
||
are integrity-protected, not confidentiality-protected. Note this
|
||
is distinct from **payload** confidentiality: the
|
||
[Confidentiality extension](confidentiality.md) seals the object
|
||
bytes a receipt attests, and this chain deliberately commits over
|
||
that ciphertext so a relay signs delivery + energy without ever
|
||
reading the content.
|
||
- **Pricing semantics for joules.** `joules_micro` and
|
||
`joules_total` are unit-of-energy fields, not unit-of-account
|
||
fields. Conversion to currency, carbon, or grid-mix CO₂ is
|
||
performed by downstream aggregators and is not normative here.
|