Files
Trusty/docs/twin-coupling.md

93 lines
3.5 KiB
Markdown

# Twin Coupling -- Data Convention
## Problem
A digital-twin computation ingests readings from one or more sensor
chains and produces a derived result. The result is appended to its own
trusty chain (separate `entity_id`). How does an external verifier
confirm which sensor rows fed the computation?
## Convention
The twin row carries a `source_refs` array inside its `data` field:
```json
{
"computation": "rolling_avg_temp_c",
"result": 21.37,
"source_refs": [
{"chain_id": "chain_sensor-001_...", "sequence": 0, "row_hash": "ae1cfe445cda1f6d"},
{"chain_id": "chain_sensor-001_...", "sequence": 1, "row_hash": "dee9856d170787cf"},
{"chain_id": "chain_sensor-001_...", "sequence": 2, "row_hash": "26cd125f051243cf"}
]
}
```
Each entry is:
| Field | Source | Purpose |
|------------|---------------------------------|--------------------------------------------|
| `chain_id` | `AppendResult.chain_id` | Identifies which sensor chain |
| `sequence` | `AppendResult.sequence` | Row position within that chain |
| `row_hash` | `AppendResult.row_hash` | Tamper-evident fingerprint of the row |
Because `data` is already part of the payload that `row_hash` commits
to, the `source_refs` array is itself hash-protected. Altering any
reference after the fact invalidates the twin row's `row_hash` and
fails `verify_chain`.
## Verification flow (two ProofBundles)
An external verifier needs no database access -- only two
`ProofBundle` blobs exported via `Trusty::export_bundle`:
1. `sensor_bundle.verify(&sip_key)` -- confirms the sensor chain is
internally consistent.
2. `twin_bundle.verify(&sip_key)` -- confirms the twin chain is
internally consistent.
3. Walk twin rows; for each `source_refs` entry, look up the matching
row in `sensor_bundle.rows` by `chain_id` + `sequence` and assert
`row_hash` matches.
If all three checks pass, the verifier knows:
- The sensor readings are unaltered.
- The twin computation row is unaltered.
- The twin row commits to exactly those sensor row_hashes at write time.
See `crates/trusty-sqlite/examples/twin_coupling.rs` for a runnable
end-to-end demo.
## Design decisions
**Why `data` field, not a new `break_reason` variant.**
`break_reason` marks loss-of-continuity (tamper, gap, domain mismatch).
A twin derivation is a planned event, not an anomaly. Mixing the two
pollutes `breaks_since()` as an alerting surface and requires callers
to filter by break kind.
**Why not a separate `twin_reference` table.**
A join table outside the hash surface cannot be verified from a
ProofBundle alone. The verifier would need database access, breaking
the self-contained export contract. `source_refs` in `data` is
verifiable anywhere the bundle travels.
**What this does NOT prove.**
The convention proves the twin row committed to those row_hashes at
write time. It does not prove the computation is correct (the twin
operator could have computed a wrong average and still referenced the
right sensor rows). Computation correctness is out of scope for trusty;
it belongs to a separate audit or reproducibility layer.
## Multi-sensor / multi-chain refs
`source_refs` can mix `chain_id` values from different sensor entities.
The verifier simply exports a ProofBundle per sensor entity and checks
each ref against the appropriate bundle.
```json
"source_refs": [
{"chain_id": "chain_sensor-001_...", "sequence": 5, "row_hash": "..."},
{"chain_id": "chain_sensor-002_...", "sequence": 3, "row_hash": "..."}
]
```