Files
Trusty/docs/device-signer-provisioning.md

6.5 KiB

Device-Signer Provisioning

Pattern for enrolling, using, rotating, and revoking Ed25519 signing keys on devices (IoT sensors, edge nodes, agents) that append data to a trusty chain.


Problem

Many trusty deployments have more than one source of writes:

  • A carbon-credit SaaS ingesting readings from hundreds of IoT sensors.
  • An AI-agent audit log receiving tool-call records from many running agents.
  • A monitoring/logging API collecting events from thousands of customer services.

Each source needs a persistent cryptographic identity. The chain needs to record those signatures in a way external verifiers can check. The operator needs a way to recognize an enrolled device, retire a compromised one, or rotate a scheduled key.

Trusty keeps chain verification self-contained (signature + embedded public key per row). Trust policy — which devices are still trusted today — lives in the operator's application layer, not the chain.


Core Idea

Trusty's v2 payload embeds each row's Ed25519 public key inline in the signature object (see docs/payload-v2.md §4). This means:

  • A proof bundle is cryptographically self-verifying: a consumer with the payload bytes can check verify(pk, payload, sig) without any registry lookup.
  • Whether a particular pk should have been trusted at append time is a separate question. That question is answered by an application-level signer registry.

Two layers, cleanly separated:

+------------------------------------+
|  Chain layer (trusty):             |
|    raw_payload, row_hash,          |
|    anchor_hash, signatures[pk,sig] |
|  -> verifies math, not policy      |
+------------------------------------+
|  App layer (you):                  |
|    signer_registry table           |
|    device_id, pk, enrolled_at,     |
|    revoked_at, metadata            |
|  -> verifies policy (was this      |
|     device trusted at time T?)     |
+------------------------------------+

Lifecycle

1. Enroll

On the device:

use trusty_core::{Ed25519Signer, Signer};

let signer = Ed25519Signer::generate();                 // OS RNG
let secret = signer.secret_bytes();                     // persist to
                                                        // device secure
                                                        // storage
let pk_hex = hex::encode(signer.public_key_bytes());    // send to
                                                        // operator

On the operator side (app), insert into a registry:

INSERT INTO signer_registry (device_id, public_key, enrolled_at)
VALUES (?, ?, ?);

The secret never leaves the device. Only the public key is transmitted.

2. Sign and append

let signer = Ed25519Signer::from_secret_bytes(&secret)?;
trusty.append_signed("device_42", &data, "sensor://42", &signer)?;

The resulting row carries the signature with the pk embedded inline.

3. Rotate

When a device's key reaches its scheduled lifetime (or for routine hygiene), generate a new keypair on the device and register a new entry (or update the existing one with a new pk + rotated_at timestamp).

Old rows remain fully verifiable — the old pk lives inline in those rows. Only new rows use the new key.

4. Revoke

If a device is decommissioned, lost, or suspected of compromise:

UPDATE signer_registry SET revoked_at = ? WHERE device_id = ?;

Revocation is not a chain event. Revoked rows still carry valid signatures (the math is unchanged). The app decides to distrust them:

  • A proof-bundle consumer joins the bundle's rows against the registry and flags any row where the pk's revoked_at precedes the row's detected_at.
  • An auditor treats revoked-key rows as quarantined, not missing.

Compromise Scenarios

Per docs/threat-model.md §6 (per-key compromise), trusty's surface already addresses the crypto side. The registry pattern closes the policy side.

Scenario Chain behavior Registry response
Device secret leaked; attacker signs Row sig verifies mathematically Revoke pk; downstream flags the row
Old key rotated out Old-key rows still verify Mark rotated_at; retain the entry
Device destroyed Future writes just stop Mark revoked_at with reason
Clone/replay across devices Chain sequence gate detects it Registry has no further role

Registry Schema (Reference, Not Mandatory)

Trusty does not prescribe a registry schema. Apps vary: a carbon- credit platform cares about device_type and location; a monitoring SaaS cares about customer_id and plan; an agent log cares about agent_version and operator_account.

A minimal schema:

CREATE TABLE signer_registry (
    device_id   TEXT PRIMARY KEY,
    public_key  TEXT NOT NULL,     -- hex-encoded 32 bytes
    enrolled_at TEXT NOT NULL,     -- ISO-8601
    rotated_at  TEXT,              -- NULL until rotated
    revoked_at  TEXT,              -- NULL until revoked
    note        TEXT
);

CREATE INDEX idx_sr_pk ON signer_registry(public_key);

The pk index exists so a verifier can look up registry status given a pk extracted from a chain row.


Worked Example

See crates/trusty-sqlite/examples/device_signer.rs for a runnable demo that walks enroll → sign → rotate → revoke end to end on the same database file trusty uses, using a second rusqlite::Connection for the registry table.

Run:

cargo run -p trusty-sqlite --example device_signer

What Trusty Does NOT Do (and Why)

  • No built-in registry. Schema needs vary per product; a prescribed schema would either over-serve (unused fields) or under-serve (missing fields). Apps roll their own table.
  • No revocation as chain event. Chain rows are immutable by design. Introducing a "this pk is revoked" row would conflate cryptographic truth (the sig is valid) with operational policy (we no longer trust it). Those belong in different places.
  • No rotation ceremony. Each v2 row carries its own pk inline; switching to a new signer at append time is a one-line change on the device. No chain-side mechanism needed.

The Signer trait accepts any backing implementation (HSM, TPM, cloud KMS, file-backed). Ed25519Signer is the default software impl; production deployments commonly substitute a hardware-backed signer.