Bare-metal Write-Side Custody enforcement for MicroPython sensor nodes with hardware crypto abstraction
Project description
sovereign-sensor — Phase 9
A lightweight, MicroPython-compatible Hardware Abstraction Layer (HAL) that enforces data custody at the Point of Genesis by sealing each sensor observation into a versioned, tamper-evident, replay-protected JSON transmission envelope before any network transit or cloud ingestion occurs.
Zero external dependencies. Internal library code is restricted to standard MicroPython
built-ins (json, sys, hashlib, hmac, binascii). Targets ESP32 and Raspberry Pi
Pico via MicroPython; fully exercisable on CPython 3.12 for desktop CI.
Architecture
Hardware Abstraction Layer
SovereignCryptoDriver (interface.py) defines a three-method contract:
| Method | Contract |
|---|---|
initialize_hardware() -> None |
Load key material; configure accelerator subsystem. |
sign(payload: bytes) -> bytes |
Return raw binary signature bytes (no encoding). |
algorithm() -> str |
Return a canonical algorithm identifier string. |
Two concrete drivers are provided:
-
SoftwareFallbackDriver— HMAC-SHA256 over a VFS-resident binary key file. Used on all non-ESP32 targets (desktop CI, Raspberry Pi Pico, etc.). The class-levelMOCK_KEY_SENTINEL = "/mock/test_gateway.key"opts into a deterministic stub key for desktop testing; every other path that cannot be opened raisesRuntimeErrorimmediately, eliminating silent key substitution. A zero-byte key file raisesValueErrorto prevent HMAC keyed withb"". -
ESP32HardwareDriver— Skeleton placeholder for the on-chip ECC accelerator.initialize_hardware()raisesNotImplementedErroruntil register-level engineering is complete;bootstrap_sensor_node()catches this and falls back toSoftwareFallbackDriverautomatically so the sealing and VFS layers remain exercisable on the workbench.
Seven-Step Sealing Pipeline (SovereignEnvelope.seal())
-
Monotonic sequence counter — computed transiently as
_sequence + 1and bound into the preimage. The in-memory counter and VFS file (default.sovereign_sequence) are advanced only after signing succeeds, so asign()failure never consumes a sequence position or introduces a gap in the on-disk custody timeline. A truncated (0-byte) file resets to 0; a negative stored value is clamped to 0. VFS write failures degrade gracefully to RAM-only tracking. -
Algorithm identifier — queried from the active driver via
algorithm()and embedded in the authenticated preimage, providing protocol agility without a schema change. -
Canonical payload serialization —
json.dumps(payload, separators=(",", ":"), sort_keys=True, ensure_ascii=False)guarantees a byte-identical preimage for semantically equivalent payloads regardless of key insertion order.ensure_ascii=Falseforces raw UTF-8 output for all characters, eliminating the\uXXXX-vs-raw-UTF-8 split-brain divergence that would cause cross-platform HMAC verification to fail silently on any payload containing characters outside U+007F. -
UTF-8 byte-count-prefixed preimage assembly —
node_id,timestamp, and thealgorithmidentifier are each encoded to UTF-8 independently; the prefix for each field is the UTF-8 byte count (not the Unicode character count). The preimage is assembled from raw byte slices:1|{len(node_bytes)}:{node_id}|{len(time_bytes)}:{timestamp}|{seq}|{len(algo_bytes)}:{algo}|{canonical}Byte-count prefixes close all variable-length field injection surfaces: delimiter injection (any two inputs that differ only in where a
|character falls produce identical naive pipe-joined preimage bytes without prefixes) and multi-byte encoding ambiguity (a receiver using character-count semantics parses field boundaries at the wrong byte offset for any non-ASCII field value). -
Driver signing — raw preimage bytes traverse
driver.sign(), returning raw binary output from the underlying cryptographic primitive. -
Hex encoding —
binascii.hexlify()maps all byte values0x00–0xFFto the lowercase alphanumeric characters0–9,a–f, preventingUnicodeDecodeErroron constrained MicroPython silicon. -
Wire frame serialization — all seven envelope fields (
v,n,t,q,alg,d,s) are packed into a dict and serialized withjson.dumps(..., separators=(",", ":"), sort_keys=True, ensure_ascii=False), freezing the alphabetical key sequence and enforcing raw UTF-8 wire encoding independently of MicroPython allocator-driven insertion order.
Quick Start
from sovereign_sensor import bootstrap_sensor_node
# Auto-selects ESP32HardwareDriver or SoftwareFallbackDriver at runtime.
# Falls back to SoftwareFallbackDriver with a warning if hardware crypto
# is not yet implemented on the target.
envelope = bootstrap_sensor_node(
node_id="node-temperature-01",
private_key_path="/flash/keys/node.key",
sequence_file="/flash/.sovereign_sequence",
)
observation = {"sensor": "temperature", "value": 21.4, "unit": "C"}
wire_bytes = envelope.seal("2026-06-16T12:00:00Z", observation)
# → b'{"alg":"hmac-sha256","d":{"sensor":"temperature","unit":"C","value":21.4},'
# '"n":"node-temperature-01","q":1,"s":"<64-char hex>","t":"2026-06-16T12:00:00Z","v":1}'
Desktop / CI (mock key sentinel)
from sovereign_sensor import bootstrap_sensor_node
from sovereign_sensor.drivers.software_fallback import SoftwareFallbackDriver
envelope = bootstrap_sensor_node(
node_id="ci-node-001",
private_key_path=SoftwareFallbackDriver.MOCK_KEY_SENTINEL,
)
wire = envelope.seal("2026-06-16T00:00:00Z", {"ping": True})
Invariants
| Property | Guarantee |
|---|---|
| Replay protection | Monotonic q counter persisted to VFS; resumes across reboots. |
| Sequence atomicity | Counter advanced only after sign() succeeds; a signing failure leaves the on-disk counter unchanged with no gap. |
| Key material safety | Missing or empty key file raises immediately; no silent substitution. |
| Preimage determinism | sort_keys=True and ensure_ascii=False on payload; byte-count length prefixes on all three variable-length fields. |
| Wire frame determinism | sort_keys=True and ensure_ascii=False on the outer frame; byte-identical UTF-8 output across CPython and MicroPython builds. |
| Encoding safety | binascii.hexlify prevents UnicodeDecodeError on raw binary digest bytes. |
| Zero dependencies | No network calls, no PyTorch, no external packages at runtime. |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sovereign_sensor-0.1.0.tar.gz.
File metadata
- Download URL: sovereign_sensor-0.1.0.tar.gz
- Upload date:
- Size: 21.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5cb1614063f8cff56e396eb2ea8134fa28c9583883dfb0e5e048f547b57ed6b8
|
|
| MD5 |
e4989bf6fd04cca104a1b91e0f3f1281
|
|
| BLAKE2b-256 |
13e324d5061528cca385bb1d9cad7a3b6d4508dcf090cf5162ed35dbc8a4a292
|
File details
Details for the file sovereign_sensor-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sovereign_sensor-0.1.0-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2f26838b4474cb7bbdab9568ad1024d9f45ba362599d2450759194eb85db908
|
|
| MD5 |
89d6f90489a57910209f4e1a344099a7
|
|
| BLAKE2b-256 |
92ffd3b9012928c97bc47f4a45665955c0b3c59f79c0cb9c467517bc80de7d05
|