Cryptnox ESP32 example: build, sign, and broadcast a USDC ERC-20 transfer.
Cryptnox ESP32 example: build, sign, and broadcast a USDC ERC-20 transfer.Wiring & prerequisites:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "CryptnoxWallet.h"
#include "CW_Utils.h"
extern "C" {
}
#include "config.h"
static const char *
const TAG =
"usdc_signing";
#define SPI_ENABLED 1
#define I2C_ENABLED 0
#if SPI_ENABLED
#define SPI_MOSI 11
#define SPI_MISO 13
#define SPI_SCLK 12
#define SPI_MAX_TRANSFER_SZ 4096
#define SPI_PIN_UNUSED (-1)
#define NFC_CS 10
#endif
#if I2C_ENABLED
#define PN532_I2C_PORT 0
#define PN532_SDA 27
#define PN532_SCL 22
#define PN532_IRQ (-1)
#define PN532_RST (-1)
#define PN532_I2C_HZ 100000U
#endif
#define TX_BUF_SIZE 300U
{
const char *p = hex;
if ((p[0] == '0') && ((p[1] == 'x') || (p[1] == 'X'))) {
p += 2;
}
(void)memset(out, 0, 20U);
size_t i;
for (i = 0U; (i < 20U) && (p[0] != '\0') && (p[1] != '\0'); i++) {
uint8_t hi = (uint8_t)((*p >= 'a') ? (*p - 'a' + 10) :
(*p >= 'A') ? (*p - 'A' + 10) : (*p - '0'));
p++;
uint8_t lo = (uint8_t)((*p >= 'a') ? (*p - 'a' + 10) :
(*p >= 'A') ? (*p - 'A' + 10) : (*p - '0'));
p++;
out[i] = (uint8_t)((hi << 4U) | lo);
}
}
{
(void)memset(out, 0, 68U);
uint8_t addr[20];
(void)memcpy(out + 4U + 12U, addr, 20U);
size_t j;
for (j = 0U; j < 8U; j++) {
out[67U - j] = (uint8_t)((amount >> (8U * j)) & 0xFFU);
}
}
{
uint8_t card_pin[CW_MAX_PIN_LENGTH] = {};
: CW_MAX_PIN_LENGTH;
(void)CW_Utils::safe_memcpy(card_pin, sizeof(card_pin),
reinterpret_cast<const uint8_t *
>(
CARD_PIN),
pin_len);
uint8_t calldata[68];
while (true) {
ESP_LOGI(
TAG,
"Hold Cryptnox card to reader to sign...");
CW_SecureSession session;
bool connected = wallet.connect(session);
if (!connected) {
vTaskDelay(pdMS_TO_TICKS(100U));
continue;
}
uint64_t nonce = 0U;
ESP_LOGE(
TAG,
"Failed to get nonce — retrying in 5 s");
wallet.disconnect(session);
vTaskDelay(pdMS_TO_TICKS(5000U));
continue;
}
if (unsigned_len == 0U) {
ESP_LOGE(
TAG,
"RLP encode unsigned overflow");
wallet.disconnect(session);
vTaskDelay(pdMS_TO_TICKS(2000U));
continue;
}
uint8_t hash[CW_HASH_SIZE];
ESP_LOGI(
TAG,
"Hash to sign:");
ESP_LOG_BUFFER_HEX_LEVEL(
TAG, hash, CW_HASH_SIZE, ESP_LOG_INFO);
static const uint8_t eth_path[20] = {
0x80U, 0x00U, 0x00U, 0x2CU,
0x80U, 0x00U, 0x00U, 0x3CU,
0x80U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U,
0x00U, 0x00U, 0x00U, 0x00U,
};
CW_SignRequest req(session,
CW_SIGN_DERIVE_K1,
CW_SIGN_SIG_ECDSA_LOW_S,
CW_SIGN_WITH_PIN);
req.hash = hash;
req.hashLength = static_cast<uint8_t>(CW_HASH_SIZE);
req.derivePath = eth_path;
req.derivePathLength = static_cast<uint8_t>(sizeof(eth_path));
(void)CW_Utils::safe_memcpy(req.pin, sizeof(req.pin),
card_pin, CW_MAX_PIN_LENGTH);
CW_SignResult result = wallet.sign(req);
wallet.disconnect(session);
if (result.errorCode != CW_OK) {
ESP_LOGE(
TAG,
"Sign failed: 0x%02X",
static_cast<unsigned int>(result.errorCode));
vTaskDelay(pdMS_TO_TICKS(2000U));
continue;
}
uint8_t sig_r[CW_HASH_SIZE];
uint8_t sig_s[CW_HASH_SIZE];
(void)CW_Utils::safe_memcpy(sig_r, sizeof(sig_r),
result.signature + CW_SIG_R_OFFSET, CW_HASH_SIZE);
(void)CW_Utils::safe_memcpy(sig_s, sizeof(sig_s),
result.signature + CW_SIG_S_OFFSET, CW_HASH_SIZE);
ESP_LOG_BUFFER_HEX_LEVEL(
TAG, sig_r, CW_HASH_SIZE, ESP_LOG_INFO);
ESP_LOG_BUFFER_HEX_LEVEL(
TAG, sig_s, CW_HASH_SIZE, ESP_LOG_INFO);
ESP_LOGI(
TAG,
"v = %u",
static_cast<unsigned int>(v));
signed_tx, sizeof(signed_tx));
if (signed_len == 0U) {
ESP_LOGE(
TAG,
"RLP encode signed overflow");
vTaskDelay(pdMS_TO_TICKS(2000U));
continue;
}
ESP_LOGI(
TAG,
"Signed tx (%u bytes):", (
unsigned int)signed_len);
ESP_LOG_BUFFER_HEX_LEVEL(
TAG, signed_tx, signed_len, ESP_LOG_INFO);
char tx_hash[68] = { 0 };
tx_hash, sizeof(tx_hash))) {
ESP_LOGI(
TAG,
"TX broadcast OK: %s", tx_hash);
} else {
ESP_LOGE(
TAG,
"TX broadcast failed");
}
vTaskDelay(pdMS_TO_TICKS(15000U));
}
}
{
esp_err_t nvs_ret = nvs_flash_init();
if ((nvs_ret == ESP_ERR_NVS_NO_FREE_PAGES) ||
(nvs_ret == ESP_ERR_NVS_NEW_VERSION_FOUND)) {
ESP_ERROR_CHECK(nvs_flash_erase());
nvs_ret = nvs_flash_init();
}
ESP_ERROR_CHECK(nvs_ret);
#if SPI_ENABLED
spi_bus_config_t buscfg = {};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
#endif
#if I2C_ENABLED
#endif
if (nfc_ret != ESP_OK) {
ESP_LOGE(
TAG,
"PN532 init failed");
return;
}
(void)logger.
begin(115200UL);
CryptnoxWallet wallet(nfcTransport, logger, cryptoProvider, platform);
if (!wallet.begin()) {
ESP_LOGE(
TAG,
"Wallet begin (SAMConfig) failed");
return;
}
#if defined(RPC_PROJECT_ID) && defined(RPC_API_SECRET)
#endif
ESP_LOGE(
TAG,
"WiFi connect failed — check config.h credentials");
return;
}
ESP_LOGI(
TAG,
"Ready — will sign USDC transfer each card tap");
}
void app_main(void)
ESP-IDF application entry point.
#define SPI_MAX_TRANSFER_SZ
CW_Logger implementation that writes to ESP32 UART0 via printf.
CW_NfcTransport adapter wrapping the ESP-IDF PN532 NFC driver.
static const uint8_t TRANSFER_SELECTOR[4]
static void signing_loop(CryptnoxWallet &wallet)
Main application loop: sign and broadcast a USDC transfer each card tap.
static void build_usdc_calldata(uint8_t out[68], const char *to_hex, uint64_t amount)
Build the 68-byte ABI-encoded calldata for a USDC transfer call.
static void parse_address(const char *hex, uint8_t out[20])
Parse a hex string into a 20-byte Ethereum address.
CW_CryptoProvider backed by mbedTLS and the ESP32 hardware TRNG.
CW_Logger backed by ESP32 UART0.
bool begin(unsigned long baudRate=115200UL) override
Initialise UART0 at the given baud rate.
CW_NfcTransport implementation backed by the ESP-IDF PN532 driver.
CW_CryptoProvider implementation for ESP32 using mbedTLS and the hardware TRNG.
static const char *const TAG
size_t eth_rlp_encode_signed(const eth_tx_t *tx, uint8_t v, const uint8_t r[32], const uint8_t s[32], uint8_t *out, size_t out_max)
size_t eth_rlp_encode_unsigned(const eth_tx_t *tx, uint8_t *out, size_t out_max)
bool eth_rpc_send_raw_tx(const uint8_t *tx, size_t tx_len, char *tx_hash_out, size_t tx_hash_max)
bool eth_rpc_wifi_connect(const char *ssid, const char *password)
bool eth_rpc_get_nonce(uint64_t *nonce_out)
void eth_rpc_init(const char *rpc_url, const char *from_addr)
void eth_rpc_set_auth(const char *project_id, const char *api_secret)
uint8_t eth_rpc_ecrecover_parity(const uint8_t hash[32], const uint8_t r[32], const uint8_t s[32])
esp_err_t pn532_init(pn532_t *dev, const pn532_config_t *config)
Initialise the PN532 and bring it to a ready state.
void keccak256(const uint8_t *input, size_t length, uint8_t digest[32])
Low-level PN532 NFC controller driver for ESP-IDF (SPI and I²C).
uint64_t max_priority_fee
Compile-time configuration passed to pn532_init.
spi_host_device_t spi_host
pn532_transport_t transport
Opaque-like runtime state for a single PN532 instance.