cryptnox-sdk-esp32 1.0.0
ESP32 SDK for Cryptnox Hardware Wallet
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/*
2 * SPDX-License-Identifier: LGPL-3.0-or-later
3 * Copyright (c) 2026 Cryptnox SA
4 */
5
24
25#include <string.h>
26#include "freertos/FreeRTOS.h"
27#include "freertos/task.h"
28#include "freertos/event_groups.h"
29#include "driver/spi_master.h"
30#include "esp_log.h"
31#include "esp_wifi.h"
32#include "esp_netif.h"
33#include "esp_event.h"
34#include "nvs_flash.h"
35#include "pn532.h"
36#include "CryptnoxWallet.h"
37#include "Pn532NfcTransport.h"
38#include "ESP32Logger.h"
40#include "ESP32Platform.h"
41#include "CW_Utils.h"
42#include "config.h"
43
44/* ============================================================================
45 * 1. Interface Selection
46 * --------------------------------------------------------------------------
47 * Set exactly ONE flag to 1 and the other to 0.
48 * ========================================================================= */
49#define SPI_ENABLED 1
50#define I2C_ENABLED 0
51
52/* ── SPI wiring — ESP32-S3 dev kit + Keyestudio PN532 breakout ──── */
53#if SPI_ENABLED
54#define SPI_MOSI 11
55#define SPI_MISO 13
56#define SPI_SCLK 12
57#define SPI_MAX_TRANSFER_SZ 4096
58#define SPI_PIN_UNUSED (-1)
59#define NFC_CS 10
60#endif
61
62/* ── I²C wiring — Cheap Yellow Display (ESP32) CN1 connector ───── */
63#if I2C_ENABLED
64#define PN532_I2C_PORT 0 /* I2C_NUM_0 */
65#define PN532_SDA 27 /* CN1 SDA */
66#define PN532_SCL 22 /* CN1 SCL */
67#define PN532_IRQ (-1) /* unused */
68#define PN532_RST (-1) /* unused */
69#define PN532_I2C_HZ 100000U
70#endif
71
72static const char *const TAG = "basic_usage";
73static const uint32_t LOOP_DELAY_MS = 1000U;
74static const uint32_t WIFI_TIMEOUT_MS = 10000U;
75static const int WIFI_MAX_RETRY = 5;
76
77#define WIFI_CONNECTED_BIT BIT0
78#define WIFI_FAIL_BIT BIT1
79
80static EventGroupHandle_t s_wifi_event_group;
81static int s_retry_num = 0;
82
95static void wifi_event_handler(void *arg, esp_event_base_t event_base,
96 int32_t event_id, void *event_data)
97{
98 (void)arg;
99 (void)event_data;
100 if ((event_base == WIFI_EVENT) && (event_id == WIFI_EVENT_STA_START)) {
101 esp_wifi_connect();
102 } else if ((event_base == WIFI_EVENT) &&
103 (event_id == WIFI_EVENT_STA_DISCONNECTED)) {
105 esp_wifi_connect();
106 s_retry_num++;
107 } else {
108 xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
109 }
110 } else if ((event_base == IP_EVENT) && (event_id == IP_EVENT_STA_GOT_IP)) {
111 s_retry_num = 0;
112 xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
113 } else {
114 /* other events ignored */
115 }
116}
117
130static void wifi_start(void)
131{
132 s_wifi_event_group = xEventGroupCreate();
133 s_retry_num = 0;
134 ESP_ERROR_CHECK(esp_netif_init());
135 ESP_ERROR_CHECK(esp_event_loop_create_default());
136 (void)esp_netif_create_default_wifi_sta();
137 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
138 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
139 esp_event_handler_instance_t h_any;
140 esp_event_handler_instance_t h_ip;
141 ESP_ERROR_CHECK(esp_event_handler_instance_register(
142 WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &h_any));
143 ESP_ERROR_CHECK(esp_event_handler_instance_register(
144 IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &h_ip));
145 wifi_config_t wifi_cfg;
146 (void)memset(&wifi_cfg, 0, sizeof(wifi_cfg));
147 (void)strncpy((char *)wifi_cfg.sta.ssid, WIFI_SSID,
148 sizeof(wifi_cfg.sta.ssid) - 1U);
149 (void)strncpy((char *)wifi_cfg.sta.password, WIFI_PASSWORD,
150 sizeof(wifi_cfg.sta.password) - 1U);
151 wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
152 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
153 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
154 ESP_ERROR_CHECK(esp_wifi_start());
155 EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
157 pdFALSE, pdFALSE,
158 pdMS_TO_TICKS(WIFI_TIMEOUT_MS));
159 if ((bits & WIFI_CONNECTED_BIT) != 0U) {
160 ESP_LOGI(TAG, "WiFi connected");
161 } else {
162 ESP_LOGW(TAG, "WiFi connect failed — TRNG entropy may be reduced");
163 }
164 (void)esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, h_ip);
165 (void)esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, h_any);
166 vEventGroupDelete(s_wifi_event_group);
167}
168
169/* ============================================================================
170 * 2. Default PIN
171 * --------------------------------------------------------------------------
172 * Must match the PIN set on the card (4–9 ASCII digits).
173 * ========================================================================= */
175static const uint8_t DEFAULT_PIN[] = "000000000";
176static const size_t DEFAULT_PIN_LEN = sizeof(DEFAULT_PIN) - 1U;
177
178/******************************************************************
179 * Main loop
180 ******************************************************************/
181
192static void run_basic_usage_loop(CryptnoxWallet &wallet)
193{
194 bool keep_running = true;
195
196 while (keep_running) {
197 /* ── Step 1: Connect to card and establish secure channel ── */
198 CW_SecureSession session{};
199
200 if (!wallet.connect(session)) {
201 ESP_LOGW(TAG, "Card not detected — hold card to reader");
202 vTaskDelay(pdMS_TO_TICKS(LOOP_DELAY_MS));
203 continue;
204 }
205
206 ESP_LOGI(TAG, "Card connected and secure channel established");
207
208 /* ── Step 2: Sign a test hash ──────────────────────────── */
209 /* NOTE: Card must have a seed loaded before signing works.
210 * Use the Cryptnox CLI: cryptnox seed generate */
211 ESP_LOGI(TAG, "Signing test hash...");
212
213 uint8_t testHash[CW_HASH_SIZE];
214 (void)memset(testHash, 0x01, sizeof(testHash));
215
216 /* Build sign request — PIN included in sign payload for
217 * authentication. Alternatively call verifyPin() first
218 * and use CW_SIGN_PINLESS_K1 here. */
219 CW_SignRequest signRequest(session,
220 CW_SIGN_CURR_K1,
221 CW_SIGN_SIG_ECDSA_LOW_S,
222 CW_SIGN_WITH_PIN);
223 signRequest.hash = testHash;
224 signRequest.hashLength = static_cast<uint8_t>(CW_HASH_SIZE);
225 (void)CW_Utils::safe_memcpy(signRequest.pin, sizeof(signRequest.pin),
227
228 CW_SignResult signResult = wallet.sign(signRequest);
229
230 if (signResult.errorCode == CW_OK) {
231 ESP_LOGI(TAG, "Signature received (64 bytes raw r||s)");
232
233 /* Print first 8 bytes of R for a quick visual check */
234 ESP_LOG_BUFFER_HEX_LEVEL(TAG,
235 &signResult.signature[CW_SIG_R_OFFSET], 8U, ESP_LOG_INFO);
236 ESP_LOGI(TAG, "s:");
237 ESP_LOG_BUFFER_HEX_LEVEL(TAG,
238 &signResult.signature[CW_SIG_S_OFFSET], 8U, ESP_LOG_INFO);
239
240 ESP_LOGI(TAG, "Card processed successfully");
241
242 } else if (signResult.errorCode == CW_SIGN_PIN_INCORRECT) {
243 /* CRITICAL: do NOT retry — each wrong PIN decrements the
244 * card's retry counter. Reaching 0 permanently blocks
245 * the PIN. Fix DEFAULT_PIN before flashing again. */
246 ESP_LOGE(TAG, "Wrong PIN — halting to protect retry counter");
247 CW_Utils::secure_wipe(testHash, sizeof(testHash));
248 CW_Utils::secure_wipe(signResult.signature, sizeof(signResult.signature));
249 wallet.disconnect(session);
250 keep_running = false;
251 continue;
252
253 } else {
254 ESP_LOGE(TAG, "Sign failed: errorCode = 0x%02X",
255 static_cast<unsigned int>(signResult.errorCode));
256 }
257
258 /* ── Step 3: Securely wipe sensitive buffers ───────────── */
259 CW_Utils::secure_wipe(testHash, sizeof(testHash));
260 CW_Utils::secure_wipe(signResult.signature, sizeof(signResult.signature));
261
262 /* Always disconnect to reset the reader for the next tap. */
263 wallet.disconnect(session);
264
265 vTaskDelay(pdMS_TO_TICKS(LOOP_DELAY_MS));
266 }
267
268 /* Halt after a fatal error (wrong PIN). */
269 while (true) {
270 vTaskDelay(pdMS_TO_TICKS(LOOP_DELAY_MS));
271 }
272}
273
274/******************************************************************
275 * Entry point
276 ******************************************************************/
277
284extern "C" void app_main(void)
285{
286 esp_err_t nvs_ret = nvs_flash_init();
287 if ((nvs_ret == ESP_ERR_NVS_NO_FREE_PAGES) ||
288 (nvs_ret == ESP_ERR_NVS_NEW_VERSION_FOUND)) {
289 ESP_ERROR_CHECK(nvs_flash_erase());
290 nvs_ret = nvs_flash_init();
291 }
292 ESP_ERROR_CHECK(nvs_ret);
293 wifi_start();
294
295 pn532_t nfc = {};
296 pn532_config_t nfc_cfg = {};
297
298#if SPI_ENABLED
299 spi_bus_config_t buscfg = {};
300 buscfg.mosi_io_num = SPI_MOSI;
301 buscfg.miso_io_num = SPI_MISO;
302 buscfg.sclk_io_num = SPI_SCLK;
303 buscfg.quadwp_io_num = SPI_PIN_UNUSED;
304 buscfg.quadhd_io_num = SPI_PIN_UNUSED;
305 buscfg.max_transfer_sz = SPI_MAX_TRANSFER_SZ;
306 ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
307
309 nfc_cfg.spi_host = SPI2_HOST;
310 nfc_cfg.pin_cs = NFC_CS;
311 nfc_cfg.skip_bus_init = true;
312#endif
313
314#if I2C_ENABLED
316 nfc_cfg.i2c_port = PN532_I2C_PORT;
317 nfc_cfg.pin_sda = PN532_SDA;
318 nfc_cfg.pin_scl = PN532_SCL;
319 nfc_cfg.pin_irq = PN532_IRQ;
320 nfc_cfg.pin_rst = PN532_RST;
321 nfc_cfg.i2c_clock_hz = PN532_I2C_HZ;
322#endif
323
324 esp_err_t nfc_ret = pn532_init(&nfc, &nfc_cfg);
325 if (nfc_ret != ESP_OK) {
326 ESP_LOGE(TAG, "PN532 init failed — check wiring and interface selection");
327 return;
328 }
329
330 ESP32Logger logger;
331 (void)logger.begin(115200UL);
332
333 ESP32CryptoProvider cryptoProvider;
334 ESP32Platform platform;
335 Pn532NfcTransport nfcTransport(&nfc, logger);
336 CryptnoxWallet wallet(nfcTransport, logger, cryptoProvider, platform);
337
338 if (!wallet.begin()) {
339 ESP_LOGE(TAG, "Wallet init failed (SAMConfig)");
340 return;
341 }
342
343 /* Print PN532 firmware version as a sanity check. */
344 (void)nfcTransport.printFirmwareVersion();
345
346 ESP_LOGI(TAG, "Ready — hold Cryptnox card to reader");
347
348 run_basic_usage_loop(wallet);
349}
#define WIFI_SSID
#define WIFI_PASSWORD
#define NFC_CS
Definition main.cpp:59
static const size_t DEFAULT_PIN_LEN
Definition main.cpp:176
void app_main(void)
ESP-IDF application entry point.
Definition main.cpp:284
static EventGroupHandle_t s_wifi_event_group
Definition main.cpp:80
#define SPI_MAX_TRANSFER_SZ
Definition main.cpp:57
#define WIFI_FAIL_BIT
Definition main.cpp:78
#define SPI_MOSI
Definition main.cpp:54
static void run_basic_usage_loop(CryptnoxWallet &wallet)
Main application loop: connect, sign a test hash, wipe buffers, disconnect.
Definition main.cpp:192
#define SPI_PIN_UNUSED
Definition main.cpp:58
static const uint32_t LOOP_DELAY_MS
Definition main.cpp:73
#define SPI_MISO
Definition main.cpp:55
static const uint8_t DEFAULT_PIN[]
Demo PIN — replace with the PIN used during card initialisation.
Definition main.cpp:175
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
FreeRTOS event handler driving the Wi-Fi station state machine.
Definition main.cpp:95
#define WIFI_CONNECTED_BIT
Definition main.cpp:77
static int s_retry_num
Definition main.cpp:81
#define SPI_SCLK
Definition main.cpp:56
static void wifi_start(void)
Initialise Wi-Fi station mode and block until connected or timeout.
Definition main.cpp:130
CW_Logger implementation that writes to ESP32 UART0 via printf.
CW_Platform implementation for ESP32 using FreeRTOS.
CW_NfcTransport adapter wrapping the ESP-IDF PN532 NFC driver.
CW_CryptoProvider backed by mbedTLS and the ESP32 hardware TRNG.
CW_Logger backed by ESP32 UART0.
Definition ESP32Logger.h:48
bool begin(unsigned long baudRate=115200UL) override
Initialise UART0 at the given baud rate.
CW_Platform backed by FreeRTOS vTaskDelay.
CW_NfcTransport implementation backed by the ESP-IDF PN532 driver.
bool printFirmwareVersion() override
Query and log the PN532 firmware version.
CW_CryptoProvider implementation for ESP32 using mbedTLS and the hardware TRNG.
static const char *const TAG
#define WIFI_TIMEOUT_MS
Definition eth_rpc.cpp:28
#define WIFI_MAX_RETRY
Definition eth_rpc.cpp:27
esp_err_t pn532_init(pn532_t *dev, const pn532_config_t *config)
Initialise the PN532 and bring it to a ready state.
Definition pn532.c:657
@ PN532_TRANSPORT_SPI
Definition pn532.h:77
@ PN532_TRANSPORT_I2C
Definition pn532.h:78
Low-level PN532 NFC controller driver for ESP-IDF (SPI and I²C).
Compile-time configuration passed to pn532_init.
Definition pn532.h:111
uint32_t i2c_clock_hz
Definition pn532.h:128
spi_host_device_t spi_host
Definition pn532.h:115
pn532_transport_t transport
Definition pn532.h:112
bool skip_bus_init
Definition pn532.h:120
Opaque-like runtime state for a single PN532 instance.
Definition pn532.h:141