sprint-8: EKF + adaptive tuner + HWID + SHA-256 audit hash-chain
- heading_ekf.py: 2-state Kalman filter fusing PGN 127250 heading and 127251 ROT with shortest-arc innovation and symmetric covariance update - adaptive_tuner.py: gradient-descent outer-loop Kp/Ki adjuster bounded to ±adaptive_max_deviation_pct; oscillation vs steady-state detection - hwid.py: HMAC-SHA256 activation token (verify side); hwid_from_mac_words converts three Modbus uint16 MAC words to 12-char hex HWID - audit.py: SHA-256 hash-chain -- each JSONL line carries prev_hash and line_hash; verify_chain() detects tampering, deletion, insertion - firmware/system/hwid.h+cpp: esp_efuse_mac_get_default wrapper + FNV-32 hash + "AA:BB:CC:DD:EE:FF" formatter - modbus_registers.yaml + generated .h/.py: HWID_MAC_01/23/45 at input addrs 9/10/11 (three 16-bit words = 6-byte MAC) - modbus_slave.cpp: INPUT_HWID_MAC_01/23/45 cases read eFuse MAC - main.cpp: logs HWID string + FNV-32 hash at boot (activation traceability) - tests: 72 new tests (audit signing, EKF, adaptive tuner, HWID) -- 398 total Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
"""Hardware ID binding and activation token -- Sprint 8.
|
||||
|
||||
The 6-byte ESP32 MAC (read via Modbus INPUT_HWID_MAC_01/23/45) is used to
|
||||
bind a project license to a specific hardware unit.
|
||||
|
||||
Activation token format
|
||||
-----------------------
|
||||
The factory generates a token by HMAC-SHA256(key=SECRET, msg=hwid_hex),
|
||||
where ``hwid_hex`` is the 12-character lower-case hex representation of the
|
||||
6-byte MAC. The token is the first 16 bytes (32 hex chars) of the HMAC
|
||||
output.
|
||||
|
||||
The SECRET is a per-product deployment key embedded in the Studio binary
|
||||
(not in the open-source firmware). This file ships the *verification* side
|
||||
only; token generation happens offline in the factory tooling.
|
||||
|
||||
For development / local testing a deterministic stub secret is used
|
||||
(STUB_SECRET_KEY). The production key must be injected via the environment
|
||||
variable ``AR_ACTIVATION_KEY``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
|
||||
STUB_SECRET_KEY = b"AR-Autopilot-Dev-Key-2026"
|
||||
TOKEN_BYTES = 16 # 32 hex chars
|
||||
|
||||
|
||||
def _get_secret() -> bytes:
|
||||
key = os.environ.get("AR_ACTIVATION_KEY", "").encode()
|
||||
return key if key else STUB_SECRET_KEY
|
||||
|
||||
|
||||
def hwid_from_mac_words(mac01: int, mac23: int, mac45: int) -> str:
|
||||
"""Convert three 16-bit Modbus words to a 12-char lower-case hex HWID string."""
|
||||
mac = bytes([
|
||||
(mac01 >> 8) & 0xFF, mac01 & 0xFF,
|
||||
(mac23 >> 8) & 0xFF, mac23 & 0xFF,
|
||||
(mac45 >> 8) & 0xFF, mac45 & 0xFF,
|
||||
])
|
||||
return mac.hex()
|
||||
|
||||
|
||||
def generate_token(hwid_hex: str) -> str:
|
||||
"""Generate the activation token for a given HWID.
|
||||
|
||||
Should only be called by factory tooling. In the Studio this is
|
||||
called only during development / bench testing with the stub key.
|
||||
"""
|
||||
h = hmac.new(_get_secret(), hwid_hex.lower().encode(), hashlib.sha256)
|
||||
return h.hexdigest()[:TOKEN_BYTES * 2]
|
||||
|
||||
|
||||
def verify_token(hwid_hex: str, token: str) -> bool:
|
||||
"""Return True iff the token is valid for the given HWID.
|
||||
|
||||
Uses constant-time comparison to resist timing attacks.
|
||||
"""
|
||||
expected = generate_token(hwid_hex)
|
||||
return hmac.compare_digest(expected.lower(), token.lower())
|
||||
|
||||
|
||||
def format_hwid(hwid_hex: str) -> str:
|
||||
"""Format a 12-char hex HWID as 'AA:BB:CC:DD:EE:FF'."""
|
||||
if len(hwid_hex) != 12:
|
||||
raise ValueError(f"HWID must be 12 hex chars, got {len(hwid_hex)}")
|
||||
h = hwid_hex.upper()
|
||||
return ":".join(h[i:i+2] for i in range(0, 12, 2))
|
||||
Reference in New Issue
Block a user