#include <M5Unified.h>
#include <M5GFX.h>
#include <Wire.h>
#include <SparkFun_ATECCX08a_Arduino_Library.h>
constexpr uint8_t UNIT_ID_I2C_ADDR = 0x35;
constexpr uint16_t BUTTON_H = 34;
constexpr uint16_t BUTTON_W = 98;
constexpr uint16_t BUTTON_GAP = 12;
constexpr uint16_t BUTTON_ROW1_X = 1;
constexpr uint16_t BUTTON_ROW1_Y = 164;
constexpr uint16_t BUTTON_ROW2_Y = 204;
constexpr uint16_t BUTTON_ROW2_X = 56;
constexpr uint8_t BUTTON_COUNT = 5;
constexpr uint8_t SIGNING_SLOT = 0;
const char SIGNING_MESSAGE[] = "Unit ID local authentication demo";
M5Canvas canvas(&M5.Display);
ATECCX08A unitId;
String deviceSerial;
String resultTitle = "READY";
String resultLine1 = "Touch a function below";
String resultLine2 = "ATECC608B local control";
String resultData;
bool unitReady = false;
bool configLocked = false;
bool dataLocked = false;
bool slot0Locked = false;
bool publicKeyReady = false;
bool signatureReady = false;
uint8_t publicKey[64] = {};
uint8_t signedDigest[32] = {};
uint8_t signature[64] = {};
String toHex(const uint8_t *data, size_t length)
{
String result;
result.reserve(length * 2);
for (size_t i = 0; i < length; ++i)
{
if (data[i] < 0x10) result += '0';
result += String(data[i], HEX);
}
result.toUpperCase();
return result;
}
void drawHexLines(const String &value, int y)
{
canvas.setTextSize(0.8f);
for (size_t offset = 0; offset < value.length(); offset += 32)
{
canvas.setCursor(8, y + static_cast<int>(offset / 32) * 14);
canvas.print(value.substring(offset, offset + 32));
}
canvas.setTextSize(1.0f);
}
bool digestMessage(uint8_t digest[32])
{
return unitId.sha256(reinterpret_cast<uint8_t *>(const_cast<char *>(SIGNING_MESSAGE)),
sizeof(SIGNING_MESSAGE) - 1, digest);
}
void drawButton(uint8_t index, const char *label, uint16_t color)
{
const bool secondRow = index >= 3;
const uint8_t rowIndex = secondRow ? index - 3 : index;
const int x = secondRow ? BUTTON_ROW2_X + rowIndex * (BUTTON_W + BUTTON_GAP)
: BUTTON_ROW1_X + rowIndex * (BUTTON_W + BUTTON_GAP);
const int y = secondRow ? BUTTON_ROW2_Y : BUTTON_ROW1_Y;
canvas.fillRoundRect(x, y, BUTTON_W, BUTTON_H, 7, color);
canvas.setTextColor(TFT_BLACK, color);
canvas.drawCenterString(label, x + BUTTON_W / 2, y + 9);
}
void drawScreen()
{
canvas.fillSprite(TFT_BLACK);
canvas.setFont(&fonts::FreeMonoBold9pt7b);
canvas.setTextSize(1);
canvas.fillRoundRect(90, 1, 140, 22, 6, 0x1082);
canvas.setTextColor(TFT_CYAN, 0x1082);
canvas.drawCenterString("Unit ID Test", canvas.width() / 2, 5);
canvas.setTextColor(TFT_DARKGREY, TFT_BLACK);
canvas.drawFastHLine(8, 25, 74, TFT_DARKGREY);
canvas.drawFastHLine(238, 25, 74, TFT_DARKGREY);
canvas.fillRoundRect(4, 30, canvas.width() - 8, 126, 8, 0x1082);
canvas.setTextColor(TFT_WHITE, 0x1082);
canvas.drawString(resultTitle, 10, 39);
const bool compactSuccess = resultTitle.endsWith("OK") ||
resultTitle == "LOCAL VERIFIED";
canvas.setTextColor(compactSuccess ? TFT_WHITE : TFT_LIGHTGRAY, 0x1082);
canvas.drawString(resultLine1, 10, 60);
if (!compactSuccess)
{
canvas.drawString(resultLine2, 10, 80);
}
if (resultData.length() > 0)
{
canvas.setTextColor(TFT_GREEN, 0x1082);
drawHexLines(resultData, compactSuccess ? 86 : 100);
}
drawButton(0, "INFO", TFT_DARKGREY);
drawButton(1, "RANDOM", TFT_ORANGE);
drawButton(2, "SHA256", TFT_YELLOW);
drawButton(3, "SIGN", TFT_GREEN);
drawButton(4, "VERIFY", TFT_BLUE);
canvas.pushSprite(0, 0);
}
void showDeviceInfo()
{
resultTitle = unitReady ? "DEVICE READY" : "UNIT NOT FOUND";
resultData = "";
if (!unitReady)
{
resultLine1 = "Check PORT.A and Unit ID power";
resultLine2 = "I2C address: 0x35";
drawScreen();
return;
}
if (!unitId.readConfigZone(false))
{
resultTitle = "CONFIG READ FAILED";
resultLine1 = "Unit ID responded, config did not";
resultLine2 = "Check ATECC608B state";
drawScreen();
return;
}
deviceSerial = toHex(unitId.serialNumber, 9);
configLocked = unitId.configLockStatus;
dataLocked = unitId.dataOTPLockStatus;
slot0Locked = unitId.slot0LockStatus;
publicKeyReady = unitId.generatePublicKey(SIGNING_SLOT, false);
if (publicKeyReady)
{
memcpy(publicKey, unitId.publicKey64Bytes, sizeof(publicKey));
}
resultLine1 = String("ID ") + deviceSerial;
resultLine2 = String("C:") + (configLocked ? "L" : "O") +
" D:" + (dataLocked ? "L" : "O") +
" S0:" + (slot0Locked ? "L" : "O") +
(publicKeyReady ? " KEY OK" : " KEY N/A");
drawScreen();
Serial.println();
Serial.println("ATECC608B local Unit ID information");
Serial.println(String("DEVICE_ID=") + deviceSerial);
Serial.println(String("CONFIG=") + (configLocked ? "LOCKED" : "OPEN"));
Serial.println(String("DATA_OTP=") + (dataLocked ? "LOCKED" : "OPEN"));
Serial.println(String("SLOT0=") + (slot0Locked ? "LOCKED" : "OPEN"));
Serial.println(String("PUBLIC_KEY=") +
(publicKeyReady ? toHex(publicKey, sizeof(publicKey)) : "UNAVAILABLE"));
}
void runRandom()
{
uint8_t randomData[32] = {};
resultTitle = "HARDWARE RANDOM";
resultLine1 = "ATECC608B hardware RNG";
resultLine2 = "Generating 32 bytes...";
resultData = "";
drawScreen();
if (!unitReady || !unitId.updateRandom32Bytes(false))
{
resultTitle = "RANDOM FAILED";
resultLine1 = "Check Unit ID connection";
resultLine2 = "";
}
else
{
memcpy(randomData, unitId.random32Bytes, sizeof(randomData));
resultTitle = "RANDOM OK";
resultLine2 = "32 bytes generated";
resultData = toHex(randomData, sizeof(randomData));
Serial.println(String("RANDOM=") + resultData);
}
drawScreen();
}
void runSha256()
{
uint8_t digest[32] = {};
resultTitle = "SHA-256";
resultLine1 = "Local test message";
resultLine2 = "ATECC608B local digest";
resultData = "";
drawScreen();
if (!unitReady || !digestMessage(digest))
{
resultTitle = "SHA256 FAILED";
resultLine1 = "Check Unit ID connection";
resultLine2 = "";
}
else
{
resultTitle = "SHA256 OK";
resultData = toHex(digest, sizeof(digest));
Serial.println(String("SHA256=") + resultData);
}
drawScreen();
}
void runSignature()
{
resultTitle = "ECDSA SIGNATURE";
resultLine1 = "Signing with Slot 0...";
resultLine2 = "Private key stays in Unit ID";
resultData = "";
drawScreen();
signatureReady = false;
if (!unitReady || !digestMessage(signedDigest) ||
!unitId.createSignature(signedDigest, SIGNING_SLOT))
{
resultTitle = "SIGNATURE FAILED";
resultLine1 = "Slot 0 signing unavailable";
resultLine2 = "Check Unit ID configuration";
}
else
{
memcpy(signature, unitId.signature, sizeof(signature));
signatureReady = true;
resultTitle = "SIGNATURE OK";
resultLine1 = "ECDSA signature created";
resultLine2 = "Private key never leaves Unit ID";
resultData = toHex(signature, sizeof(signature));
Serial.println(String("SIGNATURE=") + resultData);
}
drawScreen();
}
void runLocalVerification()
{
resultTitle = "LOCAL VERIFY";
resultLine1 = "Verifying last signature...";
resultLine2 = "No network or server used";
resultData = "";
drawScreen();
if (!unitReady || !signatureReady)
{
resultTitle = "VERIFY UNAVAILABLE";
resultLine1 = "Touch SIGN first";
resultLine2 = "";
drawScreen();
return;
}
if (!publicKeyReady)
{
publicKeyReady = unitId.generatePublicKey(SIGNING_SLOT, false);
if (publicKeyReady)
{
memcpy(publicKey, unitId.publicKey64Bytes, sizeof(publicKey));
}
}
const bool verified = publicKeyReady &&
unitId.verifySignature(signedDigest, signature, publicKey);
resultTitle = verified ? "LOCAL VERIFIED" : "LOCAL REJECTED";
resultLine1 = verified ? "Signature is valid" : "Signature check failed";
resultLine2 = "ATECC608B verified on CoreS3";
Serial.println(verified ? "LOCAL VERIFY=OK" : "LOCAL VERIFY=FAILED");
drawScreen();
}
void setup()
{
Serial.begin(115200);
auto cfg = M5.config();
cfg.output_power = true;
cfg.external_imu = false;
cfg.external_rtc = false;
M5.begin(cfg);
M5.Display.setRotation(1);
canvas.createSprite(M5.Display.width(), M5.Display.height());
canvas.setFont(&fonts::FreeMonoBold9pt7b);
canvas.setTextSize(1);
const int sda = M5.getPin(m5::pin_name_t::port_a_sda);
const int scl = M5.getPin(m5::pin_name_t::port_a_scl);
if (sda < 0 || scl < 0)
{
resultTitle = "PORT.A ERROR";
resultLine1 = "CoreS3 I2C pins unavailable";
resultLine2 = "";
drawScreen();
return;
}
Wire.setBufferSize(256);
Wire.begin(sda, scl, 100000U);
unitReady = unitId.begin(UNIT_ID_I2C_ADDR, Wire, Serial);
if (unitReady)
{
Serial.println("ATECC608B wake-up successful");
}
else
{
Serial.println("ATECC608B not found; check PORT.A wiring and power");
}
showDeviceInfo();
}
void loop()
{
M5.update();
const auto touch = M5.Touch.getDetail();
if (!touch.wasClicked())
{
delay(10);
return;
}
int button = -1;
if (touch.y >= BUTTON_ROW1_Y && touch.y < BUTTON_ROW1_Y + BUTTON_H)
{
if (touch.x >= BUTTON_ROW1_X &&
touch.x < BUTTON_ROW1_X + 3 * BUTTON_W + 2 * BUTTON_GAP)
{
button = (touch.x - BUTTON_ROW1_X) / (BUTTON_W + BUTTON_GAP);
}
}
else if (touch.y >= BUTTON_ROW2_Y && touch.y < BUTTON_ROW2_Y + BUTTON_H)
{
if (touch.x >= BUTTON_ROW2_X && touch.x < BUTTON_ROW2_X + 2 * BUTTON_W + BUTTON_GAP)
{
button = 3 + (touch.x - BUTTON_ROW2_X) / (BUTTON_W + BUTTON_GAP);
}
}
if (button < 0 || button >= BUTTON_COUNT) return;
switch (button)
{
case 0: showDeviceInfo(); break;
case 1: runRandom(); break;
case 2: runSha256(); break;
case 3: runSignature(); break;
case 4: runLocalVerification(); break;
default: break;
}
}