1 unstable release
| new 0.1.7 | Mar 6, 2026 |
|---|
#1870 in Cryptography
32KB
835 lines
immutable-trace
This repository contains a tamper-evident audit log PoC built in Rust from IoT devices to cloud services.
Motivation
In recent years, labor shortages have become a serious challenge in infrastructure operations. Labor-intensive industries such as construction are increasingly adopting IoT devices for remote inspections.
At the same time, if device spoofing, device takeover, or inspection data tampering occurs, trust in the entire system is fundamentally undermined. This makes continuous verification of both device authenticity and data integrity essential.
For public-infrastructure IoT deployments, Singapore's Cybersecurity Labelling Scheme (CLS) may require Level 3 or Level 4, which introduces hardware-level security requirements.
Because those hardware-dependent setups are often difficult to evaluate quickly in an early PoC phase, this repository focuses on sample code for tamper prevention and tamper-evident audit records.
Crates
ledger-core: Audit record types, hashing, signature verification, and chain verificationdevice-agent: Device-side signed record generationingest-api: Ingestion-time verification, deduplication, sequence validation, and persistence workflow for raw data / audit ledger / operation logsaudit-cli: Command-line operations for signing and verifying audit records
Concepts
For a glossary-style explanation of the core concepts in this repository, see CONCEPTS.md.
Device Side vs Cloud Side
This PoC assumes a public-infrastructure IoT deployment where field devices (for example, lift inspection devices) send inspection evidence to cloud services.
Device side (resource-constrained edge)
The device-side responsibility is implemented by device-agent + ledger-core.
- Generate inspection event payloads (door check, vibration check, emergency brake check)
- Compute
payload_hash(BLAKE3) - Sign the hash using an Ed25519 private key
- Link each event to the previous record hash (
prev_record_hash) so records form a chain - Send only compact audit metadata plus object reference (
object_ref) to keep edge-side cost low
Cloud side (verification and trust enforcement)
The cloud-side responsibility is implemented by ingest-api + ledger-core.
- Verify that the device is known (
device_id-> public key) - Verify signature validity for each incoming record
- Enforce sequence monotonicity and reject duplicates
- Enforce hash-chain continuity (
prev_record_hashmust match previous record hash) - Reject tampered, replayed, or reordered data before persistence
Shared trust logic
ledger-core is shared by both sides to keep hashing and verification rules identical across edge and cloud boundaries.
Using Rust on both the device and cloud sides also enables a more unified implementation model. Core types, validation logic, and test cases can be reused across environments with minimal translation overhead. This reduces drift between edge and cloud behavior, lowers maintenance cost, and makes security-critical logic easier to review and verify end-to-end.
Resource-Constrained Device Design
The device-side design is intentionally lightweight so it can be adapted to Cortex-M class environments.
- Small cryptographic footprint: records store fixed-size hashes (
[u8; 32]) and signatures ([u8; 64]) - Minimal compute path: hash and sign only; no heavy server-side validation logic on device
- Compact wire format readiness: record structure is deterministic and serializable (
serde+postcardsupport in core) - Offload heavy work to cloud: duplicate detection, sequence policy checks, and full-chain verification are cloud concerns
- Tamper-evident by construction: a one-byte modification breaks signature checks or chain continuity
Concrete Design Flow
- Device creates event payload
D. - Device computes
H = hash(D)and signsH-> signatureS. - Device emits
AuditRecord { device_id, sequence, timestamp_ms, payload_hash=H, signature=S, prev_record_hash, object_ref }. - Cloud verifies signature with registered public key.
- Cloud verifies sequence and previous-hash link.
- If any check fails, ingest is rejected; otherwise the record is accepted.
In short, the edge signs facts, and the cloud enforces continuity and authenticity.
Operations
All execution procedures are centralized in AGENTS.md:
- Unit test commands
- macOS prerequisites
- CLI usage
- Lift inspection end-to-end scenario
- Tampering and detection walkthrough
Quality and License Check
Run workspace unit tests and commercial-use OSS license checks in one command:
./scripts/run_unit_and_license_check.sh
This script runs:
cargo test --workspacecargo test -p ingest-api --features s3cargo deny check licenses(policy fromdeny.toml)
Interactive Local Demo
This repository includes an interactive end-to-end demo script that validates the tamper-evident workflow locally:
Note: unlike the library-only example, this demo requires PostgreSQL and MinIO.
- Start backend services (PostgreSQL + MinIO)
- Generate and verify a signed chain with
audit-cli - Tamper with a generated chain and confirm verification fails
- Persist accepted records into PostgreSQL
- Display audit records and operation logs from the database
- Stop PostgreSQL + MinIO at the end of the flow
Run:
bash scripts/local_demo.sh
The script pauses at each step and waits for Enter (or OK) so results can be inspected interactively.
For full command details and manual inspection steps, see AGENTS.md.
Library Usage Example (Lift Inspection Scenario)
If you want to integrate the libraries directly (without using audit-cli), run the example below.
Prerequisites:
- Rust toolchain (
cargo) - PostgreSQL / MinIO are not required for this example (it uses in-memory stores)
Run:
cargo run -p ingest-api --example lift_inspection_flow
Source:
For the full scenario steps and expected behavior, see AGENTS.md.
S3 / MinIO Switching
ingest-api supports a switchable S3-compatible raw-data backend behind the s3 feature.
S3Backend::AwsS3: use AWS S3 (default AWS credential chain, or optional static key)S3Backend::Minio: use MinIO (custom endpoint + static access key/secret)
This is an S3-compatible object-storage design. The ingest layer is coded against a common raw-data storage abstraction, while concrete configuration selects AWS S3 or MinIO without changing ingest business logic.
Use these types from ingest-api:
S3ObjectStoreConfig::for_aws_s3(...)S3ObjectStoreConfig::for_minio(...)S3CompatibleRawDataStore::new(config)
For build and test commands, see AGENTS.md.
License
This project is licensed under either of:
at your option.
Dependencies
~5–15MB
~254K SLoC