From e1fbfbae53c590b9cf81eef75d973af4f681ec8c Mon Sep 17 00:00:00 2001 From: Aviral Shukla Date: Sat, 5 Apr 2025 07:33:34 +0530 Subject: [PATCH] feat: added cryptographic verifiability for the module. --- {cam_module => cam-module}/AdNetCamModule.ino | 223 +++++++++--------- cam-module/FirmwareHash.cpp | 59 +++++ cam-module/FirmwareHash.h | 13 + cam-module/README.md | 81 +++++++ cam-module/SecureSign.cpp | 0 cam-module/SecureSign.h | 19 ++ {cam_module => cam-module}/camera_pins.h | 0 {cam_module => cam-module}/ci.json | 0 8 files changed, 283 insertions(+), 112 deletions(-) rename {cam_module => cam-module}/AdNetCamModule.ino (53%) create mode 100644 cam-module/FirmwareHash.cpp create mode 100644 cam-module/FirmwareHash.h create mode 100644 cam-module/README.md create mode 100644 cam-module/SecureSign.cpp create mode 100644 cam-module/SecureSign.h rename {cam_module => cam-module}/camera_pins.h (100%) rename {cam_module => cam-module}/ci.json (100%) diff --git a/cam_module/AdNetCamModule.ino b/cam-module/AdNetCamModule.ino similarity index 53% rename from cam_module/AdNetCamModule.ino rename to cam-module/AdNetCamModule.ino index ed5c0c7..b7254ec 100644 --- a/cam_module/AdNetCamModule.ino +++ b/cam-module/AdNetCamModule.ino @@ -1,28 +1,30 @@ +// AdNetCamModule.ino #include "esp_camera.h" #include extern "C" { #include "esp_http_server.h" } + +// Include our helper libraries +#include "FirmwareHash.h" +#include "SecureSign.h" + // =================== // Select camera model // =================== #define CAMERA_MODEL_AI_THINKER // Has PSRAM - #include "camera_pins.h" // =========================== // Enter your WiFi credentials // =========================== const char* ssid = "FTTH_2.4ghz"; -const char* password = "qefe2fqe"; +const char* password = "jjrch8dh20"; // =========================== // Status LED pin configuration // =========================== -// Change this to a valid pin for a status LED on your board. -// If you do not have a spare LED, you can disable these lines or -// re-map them. Many ESP32-CAM boards do not have a dedicated status LED. #define LED_STATUS_PIN 33 // --------------------------- @@ -31,8 +33,8 @@ const char* password = "qefe2fqe"; IPAddress local_IP(192, 168, 1, 43); IPAddress gateway(192, 168, 1, 1); IPAddress subnet(255, 255, 255, 0); -IPAddress primaryDNS(8, 8, 8, 8); // optional -IPAddress secondaryDNS(8, 8, 4, 4); // optional +IPAddress primaryDNS(8, 8, 8, 8); +IPAddress secondaryDNS(8, 8, 4, 4); // Forward declarations static esp_err_t stream_handler(httpd_req_t *req); @@ -41,14 +43,18 @@ static void blinkWifiConnecting(void); httpd_handle_t stream_httpd = NULL; +// Global strings to hold the firmware hash and signature in hex format. +String gFirmwareHashHex = ""; +String gSignatureHex = ""; + // ----------------------------------------------------------- -// Blink the LED rapidly while trying to connect to Wi-Fi +// Blink LED while connecting to Wi-Fi // ----------------------------------------------------------- static void blinkWifiConnecting(void) { static unsigned long lastBlink = 0; static bool ledState = false; - if (millis() - lastBlink >= 300) { // adjust blink speed if desired + if (millis() - lastBlink >= 300) { lastBlink = millis(); ledState = !ledState; digitalWrite(LED_STATUS_PIN, ledState ? HIGH : LOW); @@ -56,7 +62,20 @@ static void blinkWifiConnecting(void) } // ----------------------------------------------------------- -// Setup function +// Helper: Convert byte array to hex string +// ----------------------------------------------------------- +String bytesToHexString(const uint8_t* data, size_t len) { + String hexStr = ""; + for (size_t i = 0; i < len; i++) { + if (data[i] < 16) hexStr += "0"; + hexStr += String(data[i], HEX); + } + hexStr.toUpperCase(); + return hexStr; +} + +// ----------------------------------------------------------- +// Setup // ----------------------------------------------------------- void setup() { @@ -64,37 +83,27 @@ void setup() Serial.setDebugOutput(true); Serial.println(); - // Set up the LED (for status) and turn it off initially pinMode(LED_STATUS_PIN, OUTPUT); digitalWrite(LED_STATUS_PIN, LOW); - // Static IP configuration + // Configure static IP. if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { Serial.println("STA Failed to configure"); - // Turn LED steady on to indicate error - digitalWrite(LED_STATUS_PIN, HIGH); - while (true) { - delay(100); - } + digitalWrite(LED_STATUS_PIN, HIGH); // steady LED = error + while (true) { delay(100); } } WiFi.begin(ssid, password); WiFi.setSleep(false); - Serial.println("WiFi connecting..."); - // Blink LED while connecting while (WiFi.status() != WL_CONNECTED) { blinkWifiConnecting(); delay(50); } - // Once connected, turn the LED off (indicating normal operation) digitalWrite(LED_STATUS_PIN, LOW); - - Serial.println(""); - Serial.println("WiFi connected. IP address: "); - Serial.println(WiFi.localIP()); + Serial.printf("\nWiFi connected. IP address: %s\n", WiFi.localIP().toString().c_str()); // ----------------------------------------------------------- // Camera configuration @@ -119,137 +128,132 @@ void setup() config.pin_pwdn = PWDN_GPIO_NUM; config.pin_reset = RESET_GPIO_NUM; config.xclk_freq_hz = 20000000; - - // High-quality capture settings (UXGA + JPEG) - config.frame_size = FRAMESIZE_UXGA; // 1600x1200 (UXGA) - config.pixel_format = PIXFORMAT_JPEG; // for streaming + + config.frame_size = FRAMESIZE_UXGA; // 1600x1200 (high quality) + config.pixel_format = PIXFORMAT_JPEG; // for streaming config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; config.fb_location = CAMERA_FB_IN_PSRAM; config.jpeg_quality = 12; config.fb_count = 1; - // If PSRAM is found, improve performance/quality if (psramFound()) { - config.jpeg_quality = 10; // better quality - config.fb_count = 2; // double-buffer + config.jpeg_quality = 10; + config.fb_count = 2; config.grab_mode = CAMERA_GRAB_LATEST; } else { - // If no PSRAM, reduce resolution - config.frame_size = FRAMESIZE_SVGA; // 800x600 + config.frame_size = FRAMESIZE_SVGA; // 800x600 if no PSRAM config.fb_location = CAMERA_FB_IN_DRAM; } - // ----------------------------------------------------------- - // Initialize camera - // ----------------------------------------------------------- esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { - Serial.printf("Camera init failed with error 0x%x", err); - // Turn LED steady on to indicate camera error + Serial.printf("Camera init failed with error 0x%x\n", err); digitalWrite(LED_STATUS_PIN, HIGH); - while (true) { - delay(100); - } + while (true) { delay(100); } } - - // Optional sensor adjustments + sensor_t *s = esp_camera_sensor_get(); if (s->id.PID == OV3660_PID) { - s->set_vflip(s, 1); // flip it back - s->set_brightness(s, 1); // up the brightness - s->set_saturation(s, -2); // lower the saturation + s->set_vflip(s, 1); + s->set_brightness(s, 1); + s->set_saturation(s, -2); } - // DO NOT drop down to QVGA—keep it at UXGA or SVGA as configured above - // e.g. remove the typical line: - // s->set_framesize(s, FRAMESIZE_QVGA); // <--- REMOVED - - // Start the camera server + // Start HTTPS camera server (ensure you configure TLS certs in httpd_config_t if needed) startCameraServer(); + Serial.println("Camera Ready! Visit: https://" + WiFi.localIP().toString()); - Serial.print("Camera Ready! Use 'http://"); - Serial.print(WiFi.localIP()); - Serial.println("' to connect (MJPEG stream)."); + // ----------------------------------------------------------- + // Compute firmware hash and sign it. + // ----------------------------------------------------------- + uint8_t fwHash[32]; + if (computeFirmwareHash(fwHash)) { + gFirmwareHashHex = bytesToHexString(fwHash, sizeof(fwHash)); + Serial.print("Firmware Hash: "); + Serial.println(gFirmwareHashHex); + + // Sign the firmware hash using our secure key. + uint8_t signature[65]; // 65 bytes: 32 (r) + 32 (s) + 1 (v) + size_t sigLen = 0; + if (signData(fwHash, sizeof(fwHash), signature, &sigLen)) { + gSignatureHex = bytesToHexString(signature, sigLen); + Serial.println("Firmware hash signed successfully!"); + Serial.print("Signature (hex): "); + Serial.println(gSignatureHex); + } else { + Serial.println("Failed to sign the firmware hash."); + } + } else { + Serial.println("Failed to compute firmware hash!"); + } } void loop() { - // The server runs in the background. Nothing special needed here. + // Server runs in the background. delay(1000); } // ----------------------------------------------------------- -// MJPEG Stream Handler (only endpoint) +// MJPEG Stream Handler (with extra headers) // ----------------------------------------------------------- static esp_err_t stream_handler(httpd_req_t *req) { - // Provide MJPEG "multipart/x-mixed-replace" content - static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=123456789000000000000987654321"; - static const char* _STREAM_BOUNDARY = "\r\n--123456789000000000000987654321\r\n"; - static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n"; - - camera_fb_t * fb = NULL; - esp_err_t res = ESP_OK; - uint8_t * _jpg_buf = NULL; - size_t _jpg_buf_len = 0; - char part_buf[128]; - - // Set appropriate content type - res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE); - if (res != ESP_OK) { - return res; - } + // Add custom headers for on-chain verification. + httpd_resp_set_hdr(req, "X-Firmware-Hash", gFirmwareHashHex.c_str()); + httpd_resp_set_hdr(req, "X-Signature", gSignatureHex.c_str()); + + // Set content type to MJPEG. + static const char* CONTENT_TYPE = "multipart/x-mixed-replace;boundary=123456789000000000000987654321"; + static const char* BOUNDARY = "\r\n--123456789000000000000987654321\r\n"; + static const char* PART_FMT = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n"; + + esp_err_t res = httpd_resp_set_type(req, CONTENT_TYPE); + if (res != ESP_OK) return res; - // We can set a custom header if desired httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); - // Streaming loop while (true) { - fb = esp_camera_fb_get(); + camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Camera capture failed"); res = ESP_FAIL; } else { + uint8_t* jpg_buf = NULL; + size_t jpg_buf_len = 0; if (fb->format != PIXFORMAT_JPEG) { - bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len); + bool converted = frame2jpg(fb, 80, &jpg_buf, &jpg_buf_len); esp_camera_fb_return(fb); fb = NULL; - if (!jpeg_converted) { + if (!converted) { Serial.println("JPEG compression failed"); res = ESP_FAIL; } } else { - _jpg_buf_len = fb->len; - _jpg_buf = fb->buf; + jpg_buf_len = fb->len; + jpg_buf = fb->buf; } - } - if (res == ESP_OK) { - // Send boundary - res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY)); - } - if (res == ESP_OK) { - // Prepare the MJPEG part header - size_t hlen = snprintf(part_buf, 128, _STREAM_PART, - _jpg_buf_len, - fb ? fb->timestamp.tv_sec : 0, - fb ? fb->timestamp.tv_usec : 0); - // Send header - res = httpd_resp_send_chunk(req, part_buf, hlen); - } - if (res == ESP_OK) { - // Send frame data - res = httpd_resp_send_chunk(req, (const char*)_jpg_buf, _jpg_buf_len); - } + if (res == ESP_OK) { + res = httpd_resp_send_chunk(req, BOUNDARY, strlen(BOUNDARY)); + } + if (res == ESP_OK) { + char part_buf[128]; + size_t hlen = snprintf(part_buf, sizeof(part_buf), PART_FMT, + jpg_buf_len, + fb ? fb->timestamp.tv_sec : 0, + fb ? fb->timestamp.tv_usec : 0); + res = httpd_resp_send_chunk(req, part_buf, hlen); + } + if (res == ESP_OK) { + res = httpd_resp_send_chunk(req, (const char*)jpg_buf, jpg_buf_len); + } - // Return the frame buffer back to be reused - if (fb) { - esp_camera_fb_return(fb); - fb = NULL; - _jpg_buf = NULL; - } else if (_jpg_buf) { - free(_jpg_buf); - _jpg_buf = NULL; + if (fb) { + esp_camera_fb_return(fb); + } else if (jpg_buf) { + free(jpg_buf); + } } if (res != ESP_OK) { @@ -265,14 +269,10 @@ static esp_err_t stream_handler(httpd_req_t *req) void startCameraServer() { httpd_config_t config = HTTPD_DEFAULT_CONFIG(); - // Optionally, adjust server port if needed: - // config.server_port = 80; // default - - // Start the web server + // For HTTPS support, you must configure TLS certificates in config. if (httpd_start(&stream_httpd, &config) == ESP_OK) { - // Register only one URI: the streaming endpoint at "/" httpd_uri_t stream_uri = { - .uri = "/", // <--- single endpoint + .uri = "/", .method = HTTP_GET, .handler = stream_handler, .user_ctx = NULL @@ -281,7 +281,6 @@ void startCameraServer() Serial.println("HTTP Server started on port " + String(config.server_port)); } else { Serial.println("Failed to start HTTP server"); - // Turn LED on to indicate server start error digitalWrite(LED_STATUS_PIN, HIGH); } } diff --git a/cam-module/FirmwareHash.cpp b/cam-module/FirmwareHash.cpp new file mode 100644 index 0000000..30432fd --- /dev/null +++ b/cam-module/FirmwareHash.cpp @@ -0,0 +1,59 @@ +#include "FirmwareHash.h" + +extern "C" { + #include "esp_partition.h" + #include "esp_spi_flash.h" + #include "mbedtls/sha256.h" +} + +bool computeFirmwareHash(uint8_t* hash_out) +{ + // Locate the running application partition (OTA_0 or, if not found, factory) + const esp_partition_t* partition = esp_partition_find_first( + ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL + ); + if (!partition) { + partition = esp_partition_find_first( + ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL + ); + if (!partition) { + Serial.println("[FirmwareHash] Failed to find application partition."); + return false; + } + } + + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + if (mbedtls_sha256_starts_ret(&ctx, 0) != 0) { + mbedtls_sha256_free(&ctx); + return false; + } + + const size_t blockSize = 512; + uint8_t buffer[blockSize]; + size_t remaining = partition->size; + uint32_t address = partition->address; + + while (remaining > 0) { + size_t toRead = (remaining > blockSize) ? blockSize : remaining; + esp_err_t err = spi_flash_read(address, buffer, toRead); + if (err != ESP_OK) { + Serial.printf("[FirmwareHash] spi_flash_read failed: 0x%x\n", err); + mbedtls_sha256_free(&ctx); + return false; + } + if (mbedtls_sha256_update_ret(&ctx, buffer, toRead) != 0) { + mbedtls_sha256_free(&ctx); + return false; + } + remaining -= toRead; + address += toRead; + } + + if (mbedtls_sha256_finish_ret(&ctx, hash_out) != 0) { + mbedtls_sha256_free(&ctx); + return false; + } + mbedtls_sha256_free(&ctx); + return true; +} diff --git a/cam-module/FirmwareHash.h b/cam-module/FirmwareHash.h new file mode 100644 index 0000000..57e670c --- /dev/null +++ b/cam-module/FirmwareHash.h @@ -0,0 +1,13 @@ +#ifndef FIRMWARE_HASH_H +#define FIRMWARE_HASH_H + +#include + +/** + * @brief Compute the SHA-256 hash of the running firmware (the application partition). + * @param hash_out Pointer to a 32-byte array where the computed hash will be stored. + * @return true on success, false on failure. + */ +bool computeFirmwareHash(uint8_t* hash_out); + +#endif // FIRMWARE_HASH_H diff --git a/cam-module/README.md b/cam-module/README.md new file mode 100644 index 0000000..89f1145 --- /dev/null +++ b/cam-module/README.md @@ -0,0 +1,81 @@ +# 🔐 AdMojoModule + +> Zero-trust camera hardware with crypto-signed firmware for Web3 proof-of-views. + +## TL;DR +ESP32-CAM module that streams MJPEG + ECDSA signatures → Go Server → L2 Smart Contract validation. Implements cryptographic proof-of-view for decentralized ad networks. + +## Core Stack +- **Hardware**: ESP32-CAM (AI Thinker) +- **Crypto**: secp256k1 ECDSA (same as ETH) + SHA-256 +- **Transport**: HTTPS with signature headers +- **Verification**: On-chain via `ecrecover` + +## Trust Architecture + +``` +ESP32-CAM GoLang Server Smart Contract (L2) + │ │ │ + ├─ Hash firmware ─┐ │ │ + │ │ │ │ + ├─ Sign w/privkey ┘ │ │ + │ │ │ + ├─ Stream MJPEG + sigs ──┼───> Verify sigs ────────┤ + │ │ │ + └────────────────────────┴─────> Store proofs ─────┘ +``` + +## Cryptographic Workflow + +1. **Boot**: Calculate SHA-256 of firmware in flash +2. **Sign**: Generate secp256k1 signature (r,s,v) of hash +3. **Stream**: Serve MJPEG with HTTP headers: + ``` + X-Firmware-Hash: 64-char hex + X-Signature: 130-char hex (r||s||v) + ``` +4. **Verify**: On-chain using `ecrecover(keccak256(firmwareHash), v, r, s)` + +## Quick Deploy + +```bash +# Flash, assuming arduino-cli setup +arduino-cli compile --fqbn esp32:esp32:esp32cam AdMojoModule +arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32cam AdMojoModule + +# Access +curl -v http://192.168.1.43/ # Verify headers +``` + +## Dev Tips + +```javascript +// Verify in JS +const pubKey = web3.eth.accounts.recover( + web3.utils.sha3(firmwareHash), + signature +); +``` + +```solidity +// Verify on-chain +function verifyProofOfView(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + address signer = ecrecover(hash, v, r, s); + return trustedDevices[signer]; +} +``` + +## Roadmap + +| Now | Next | +|-----|------| +| Flash storage | ATECC608A secure element | +| MJPEG streaming | Snapshot event storage | +| Raw signatures | BLS threshold signatures | +| Basic verification | zk-proofs of on-device AI inference | +| HTTP(S) | L2 oracle integration | + +## Why This Rocks + +Traditional ad metrics: "Trust me bro" +AdMojoModule: "Verify my cryptographic proof on-chain" \ No newline at end of file diff --git a/cam-module/SecureSign.cpp b/cam-module/SecureSign.cpp new file mode 100644 index 0000000..e69de29 diff --git a/cam-module/SecureSign.h b/cam-module/SecureSign.h new file mode 100644 index 0000000..3397801 --- /dev/null +++ b/cam-module/SecureSign.h @@ -0,0 +1,19 @@ +#ifndef SECURE_SIGN_H +#define SECURE_SIGN_H + +#include + +/** + * @brief Sign the given data using the private key stored in a secure flash section. + * The signature is produced on the secp256k1 curve and is output in the Ethereum- + * compatible format (65 bytes: 32-byte r || 32-byte s || 1-byte v). + * + * @param data Pointer to the data to sign. + * @param data_len Length of the data in bytes. + * @param signature Output buffer (must be at least 65 bytes) to store the signature. + * @param sig_len On output, will be set to 65. + * @return true on success, false on failure. + */ +bool signData(const uint8_t* data, size_t data_len, uint8_t* signature, size_t* sig_len); + +#endif // SECURE_SIGN_H diff --git a/cam_module/camera_pins.h b/cam-module/camera_pins.h similarity index 100% rename from cam_module/camera_pins.h rename to cam-module/camera_pins.h diff --git a/cam_module/ci.json b/cam-module/ci.json similarity index 100% rename from cam_module/ci.json rename to cam-module/ci.json