# 2. Secure Ingestion & Transmission

**Summary:** Proof starts before the chain. EDMA only admits data that arrives over authenticated, encrypted channels, is signed at the source, is canonicalized to a stable JSON, and includes strong replay protection. Anything else is rejected upstream—long before PoV attestations.

### Threat model

* Tampering in transit (MITM, header/body swap)
* Spoofed devices (fake meter identity, cloned keys)
* Replay (old windows resent to mint again)
* Ambiguity (pretty-printed JSON, unit drift, clock drift)
* Flooding (DoS via duplicate or overlapping windows)

### Transport & authentication

* **Encryption:** mTLS/TLS 1.2+ for HTTPS or MQTTs; TLS cipher suites with PFS
* **Mutual auth:** device/client certs or OAuth2 client-credentials; rotate keys; short-lived tokens
* **Scopes:** per-device permissions (write only its own device\_id); no wildcard writes
* **Headers:** include X-Device-Id, X-Window-Id (batch\_id), X-Nonce, X-Signature, X-Timestamp (ms)

### Source signing

* On-device signatures: sign the canonical JSON or its SHA-256 with a key in a secure element/HSM
* Signature format: Ed25519 or ECDSA P-256; send as Base64 in X-Signature
* Key registry: commissioning binds public key → device\_id; compromised keys are revoked

### Canonicalization boundary

* **Rule:** devices (or gateways) must submit canonical JSON (sorted keys, minified, fixed units/types)
* **Digest:** evidenceHash = SHA-256(utf8(canonical\_json))
* **Equality guarantee:** the same reality always hashes to the same bytes; PoV equality checks rely on this

Minimal payload (energy window)

```json
{"batch_id":"0x<32-byte>","device_id":"0x<32-byte>","start_ts":1698796800,"end_ts":1698883200,"quantity_wh":10000,"nonce":"0x<32-byte>"}
```

### Replay protection & idempotency

* **Nonces:** 32-byte random per window; reject nonce reuse for the same device\_id
* **Window id:** batch\_id unique per device; server enforces idempotent upsert on batch\_id
* **Timestamp**: X-Timestamp (ms) must be within policy skew; stale requests are rejected

### Ordering & windows

* **No overlaps:** windows for a device\_id must not overlap; server rejects and logs
* **UTC seconds:** start\_ts and end\_ts are UTC; include clock\_offset\_ms when available
* **Units:** report quantity\_wh, not kWh; conversions cause equality failures

### Edge buffering & retry

* **Store-and-forward:** buffer when offline; resend original canonical JSON + headers
* **Backoff:** exponential retry with jitter; cap max attempts; do not mutate payload on retry
* **Integrity:** include source\_file\_hash when submitting file-based exports

### Ingestion API contract (HTTP example)

**Endpoint:** POST /v1/ingest/meter-window

**Headers (required):**

* Content-Type: application/json
* X-Device-Id: 0x…32
* X-Window-Id: 0x…32  (batch\_id)
* X-Nonce: 0x…32
* X-Timestamp: 1700000000000  (ms)
* X-Signature: base64(ed25519\_sign(canonical\_json))

**Body:** canonical JSON (see minimal payload)

**Server checks:** mTLS → header auth → signature verify → duplicate/overlap → canonicalization → enqueue for attestation

### From ingestion to PoV

1. Ingest canonical JSON over mTLS with signature
2. Verify signature, nonces, window rules, and dedupe
3. Compute evidenceHash; persist canonical JSON and meta (off-chain)
4. Attest MeterReadingBatch.v1 (EAS) with the evidenceHash
5. Notify authorized attestors; collect Verification.v1 records (must include AUDITOR)
6. Gate is called by asset/settlement contracts; on pass, mint proofs (gas-only) or settle (EDM)

### Minimal examples

<br>

**TypeScript:** canonical hash and header signature

```typescript
import { createHash } from "crypto";
import nacl from "tweetnacl";

export function sha256Hex(s: string) {
  return "0x" + createHash("sha256").update(Buffer.from(s, "utf8")).digest("hex");
}

export function signCanonicalJson(privateKey: Uint8Array, canonicalJson: string) {
  const msg = Buffer.from(canonicalJson, "utf8");
  const sig = nacl.sign.detached(msg, privateKey);
  return Buffer.from(sig).toString("base64");
}
```

**cURL:** submit a window

```bash
curl -sS -X POST https://api.edma.app/v1/ingest/meter-window \
 -H 'Content-Type: application/json' \
 -H 'X-Device-Id: 0xDEADBEEF...' \
 -H 'X-Window-Id: 0xBATCHID...' \
 -H 'X-Nonce: 0xABC123...' \
 -H 'X-Timestamp: 1700000000000' \
 -H 'X-Signature: BASE64_SIGNATURE' \
 --data-binary '{"batch_id":"0xBATCHID...","device_id":"0xDEADBEEF...","start_ts":1698796800,"end_ts":1698883200,"quantity_wh":10000,"nonce":"0xABC123..."}'
```

### Monitoring & alerts

* Auth errors per device (spikes signal key misuse)
* Overlap/duplicate rate by device and operator
* Signature failures and stale timestamps
* End-to-end latency: window close → PoVPassed
* Revocation flags impacting recent windows

### Conformance checklist

* Commission device identity (key → device\_id); keep certs rotated
* Sign canonical JSON (or its digest) on device/gateway
* Encrypt transport (mTLS/TLS) with per-device auth
* Prevent replay (nonce + unique batch\_id + timestamp skew limits)
* Enforce windows (UTC, no overlaps, fixed units)
* Persist canonical JSON off-chain; compute and store evidenceHash
* Attest MeterReadingBatch.v1; collect Verification.v1 (incl. AUDITOR)
* Call PoV Gate in the same transaction that mints/settles

<br>

<img src="https://4141632533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FvCX7EzuE9nwtTuIaxXGQ%2Fuploads%2F3Ep7OLS9xCXvuvEm35cr%2Ffile.excalidraw.svg?alt=media&#x26;token=bd34b80b-fa8e-4c5e-9775-5f4182633b16" alt="" class="gitbook-drawing">

{% hint style="success" %}

## Bottom line: secure ingestion makes dubious data cheap to reject and good data trivial to admit. With authenticated transport, source signatures, canonical JSON, and replay protection, the PoV Gate can decide on evidence—not on trust.

{% endhint %}
