3.5 KiB
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:
{
"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:
sensor_bundle.verify(&sip_key)-- confirms the sensor chain is internally consistent.twin_bundle.verify(&sip_key)-- confirms the twin chain is internally consistent.- Walk twin rows; for each
source_refsentry, look up the matching row insensor_bundle.rowsbychain_id+sequenceand assertrow_hashmatches.
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.
"source_refs": [
{"chain_id": "chain_sensor-001_...", "sequence": 5, "row_hash": "..."},
{"chain_id": "chain_sensor-002_...", "sequence": 3, "row_hash": "..."}
]