Send an ERC20 USDC transaction on Ethereum (EIP-1559) via Arduino, signed on-device using a Cryptnox smart card over NFC (PN532).
Send an ERC20 USDC transaction on Ethereum (EIP-1559) via Arduino, signed on-device using a Cryptnox smart card over NFC (PN532).Demonstrates:
#include <Arduino.h>
#include <SPI.h>
#include <WiFiS3.h>
#include <ArduinoHttpClient.h>
#include "config.h"
#include <CryptnoxWallet.h>
#define PN532_SS_PIN (10U)
#ifndef CARD_PIN
# define CARD_PIN "000000000"
# define CARD_PIN_LEN (9U)
#endif
#ifndef RPC_PATH
# define RPC_PATH "/"
#endif
#ifndef WIFI_CA_CERT
# define WIFI_CA_CERT \
"-----BEGIN CERTIFICATE-----\n" \
"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\n" \
"-----END CERTIFICATE-----\n"
#endif
#define ERC20_TRANSFER_SEL_0 0xa9U
#define ERC20_TRANSFER_SEL_1 0x05U
#define ERC20_TRANSFER_SEL_2 0x9cU
#define ERC20_TRANSFER_SEL_3 0xbbU
#define ERC20_INDEX_OFFSET 64U
#define YPARITY_UNKNOWN 0xFFU
#define HTTP_OK 200
#define TX_MAX_RETRIES 3U
#define TX_RETRY_DELAY_MS 2000U
#define WIFI_RETRY_MAX 20U
#define HEX_CHAR_BUF_SIZE 3U
#define ECRECOVER_V_PAD_CHARS 62U
#define ECRECOVER_V_BASE 27U
};
static const char hexChars[] =
"0123456789abcdef";
static void printHex(
const char* label,
const uint8_t* data,
size_t len) {
Serial.print(label);
char buf[3]; buf[2] = '\0';
for (size_t i = 0; i < len; i++) {
Serial.print(buf);
}
Serial.println();
}
#if defined(RPC_PROJECT_ID) && defined(RPC_API_SECRET)
#define AUTH_CRED_BUF_SIZE 128U
#define AUTH_HEADER_BUF_SIZE 200U
static bool base64Encode(const char* input, size_t inputLen, char* output, size_t outputCap) {
static const char kAlphabet[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const size_t required = (((inputLen + 2U) / 3U) * 4U) + 1U;
if (outputCap < required) {
return false;
}
size_t i = 0U;
size_t o = 0U;
while (i < inputLen) {
uint32_t u0 = static_cast<uint32_t>(static_cast<uint8_t>(input[i]));
uint32_t u1 = 0U;
uint32_t u2 = 0U;
if ((i + 1U) < inputLen) {
u1 = static_cast<uint32_t>(static_cast<uint8_t>(input[i + 1U]));
}
if ((i + 2U) < inputLen) {
u2 = static_cast<uint32_t>(static_cast<uint8_t>(input[i + 2U]));
}
output[o] = kAlphabet[static_cast<uint8_t>(u0 >> 2U)];
o++;
output[o] = kAlphabet[static_cast<uint8_t>(((u0 & 0x03U) << 4U) | (u1 >> 4U))];
o++;
if ((i + 1U) < inputLen) {
output[o] = kAlphabet[static_cast<uint8_t>(((u1 & 0x0FU) << 2U) | (u2 >> 6U))];
} else {
output[o] = '=';
}
o++;
if ((i + 2U) < inputLen) {
output[o] = kAlphabet[static_cast<uint8_t>(u2 & 0x3FU)];
} else {
output[o] = '=';
}
o++;
i += 3U;
}
output[o] = '\0';
return true;
}
static void buildBasicAuthHeader(char* buf, size_t bufSize) {
static const char kPrefix[] = "Basic ";
const size_t prefixLen = sizeof(kPrefix) - 1U;
char cred[AUTH_CRED_BUF_SIZE];
int written = snprintf(cred, sizeof(cred), "%s:%s", RPC_PROJECT_ID, RPC_API_SECRET);
if ((written < 0) || ((size_t)written >= sizeof(cred))) {
Serial.println(
F(
"[fatal] RPC_PROJECT_ID + RPC_API_SECRET exceed AUTH_CRED_BUF_SIZE"));
while (true) { }
}
if (prefixLen >= bufSize) {
Serial.println(
F(
"[fatal] AUTH_HEADER_BUF_SIZE too small for prefix"));
while (true) {}
}
reinterpret_cast<const uint8_t*>(kPrefix), prefixLen);
if (!base64Encode(cred, strlen(cred), buf + prefixLen, bufSize - prefixLen)) {
Serial.println(
F(
"[fatal] base64 output exceeds AUTH_HEADER_BUF_SIZE"));
while (true) {}
}
}
#endif
if (WiFi.status() == WL_CONNECTED) return true;
while ((retries > 0U) && (WiFi.status() != WL_CONNECTED)) {
retries--;
delay(500U);
}
return WiFi.status() == WL_CONNECTED;
}
#define RLP_ITEM_OR_FAIL(BUF, CAP, OFF, IN, IN_LEN) \
do { \
uint32_t _w = RlpEncodeItem((BUF) + (OFF), (CAP) - (OFF), \
(IN), (IN_LEN)); \
if (_w == 0U) { return 0U; } \
(OFF) += _w; \
} while (0)
size_t off = 0;
uint8_t tmp[8];
size_t tmpLen;
uint8_t addr[20];
Serial.println(
F(
"[fatal] tx.to is not a valid 40-char hex string"));
while (true) { }
}
if (off >= bufCap) { return 0U; }
buf[off++] = 0xC0;
return off;
}
static size_t rlpFinalize(uint8_t* out,
size_t outCap,
const uint8_t* buf,
size_t off) {
uint8_t header[8];
if (header_len == 0U) { return 0U; }
if ((1U + header_len + off) > outCap) {
return 0U;
}
size_t out_off = 0U;
out[out_off++] = 0x02;
out_off += header_len;
return out_off + off;
}
uint8_t buf[1024];
if (off == 0U) { return 0U; }
}
uint8_t* out, size_t outCap) {
uint8_t buf[1024];
if (off == 0U) { return 0U; }
uint32_t w;
if (w == 0U) { return 0U; }
off += w;
uint8_t tmp_r[32];
if (tmp_len == 0U) { return 0U; }
w =
RlpEncodeItem(buf + off,
sizeof(buf) - off, tmp_r, (uint32_t)tmp_len);
if (w == 0U) { return 0U; }
off += w;
uint8_t tmp_s[32];
if (tmp_len == 0U) { return 0U; }
w =
RlpEncodeItem(buf + off,
sizeof(buf) - off, tmp_s, (uint32_t)tmp_len);
if (w == 0U) { return 0U; }
off += w;
return ret;
}
Serial.println(
F(
"[fatal] ADDR_TO is not a valid 40-char hex string"));
while (true) { }
}
return 68;
}
bool sendRawTx(
const uint8_t* raw,
size_t len) {
static const char requestPrefix[] =
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_sendRawTransaction\","
"\"params\":[\"0x";
static const char requestSuffix[] = "\"]}";
bool sent = false;
for (uint8_t attempt = 0U; (attempt <
TX_MAX_RETRIES) && !sent; attempt++) {
if (attempt != 0U) {
}
Serial.println(
F(
"sendRawTx: WiFi reconnect failed"));
continue;
}
WiFiSSLClient wifiClient;
#ifndef WIFI_DISABLE_CA_PINNING
#endif
client.beginRequest();
if (err != HTTP_SUCCESS) {
Serial.print(
F(
"sendRawTx: POST failed, err="));
Serial.println(err);
client.stop();
continue;
}
client.sendHeader("Content-Type", "application/json");
client.sendHeader("Content-Length",
(int)(sizeof(requestPrefix)-1) + 2*(int)len + (int)(sizeof(requestSuffix)-1));
#if defined(RPC_PROJECT_ID) && defined(RPC_API_SECRET)
{
char authBuf[AUTH_HEADER_BUF_SIZE];
buildBasicAuthHeader(authBuf, sizeof(authBuf));
client.sendHeader("Authorization", authBuf);
}
#endif
client.beginBody();
client.print(requestPrefix);
byteHexStr[2] = '\0';
for (size_t i = 0; i < len; i++) {
byteHexStr[1] =
hexChars[raw[i] & 0x0f];
client.print(byteHexStr);
}
client.print(requestSuffix);
client.endRequest();
int status = client.responseStatusCode();
String responseBody = client.responseBody();
Serial.print(
F(
"[RPC] HTTP ")); Serial.println(status);
#if CW_DEBUG_LOGGING
Serial.print(
F(
"[RPC] ")); Serial.println(responseBody);
#endif
bool statusOk = (status ==
HTTP_OK);
bool noJsonError = (responseBody.indexOf("\"error\"") == -1);
sent = statusOk && noJsonError;
if (sent) {
int r = responseBody.indexOf("\"result\":\"0x");
if (r >= 0) {
int start = r + 10;
int end = responseBody.indexOf("\"", start);
if (end > start) {
Serial.print(
F(
"[tx] hash="));
Serial.println(responseBody.substring(start, end));
}
}
}
client.stop();
}
return sent;
}
uint8_t
determineYParity(
const uint8_t* hash,
const uint8_t* r,
const uint8_t* s) {
char hexBuf[260];
uint16_t pos = 0U;
hexBuf[pos++] = '0';
hexBuf[pos++] = 'x';
for (uint8_t i = 0U; i < 32U; i++) {
hexBuf[pos++] =
hexChars[hash[i] & 0x0f];
}
const uint16_t vOffset = pos;
hexBuf[pos++] = '0';
}
pos += 2U;
for (uint8_t i = 0U; i < 32U; i++) {
}
for (uint8_t i = 0U; i < 32U; i++) {
}
hexBuf[pos] = '\0';
static const char requestPrefix[] =
"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\","
"\"params\":[{\"to\":\"0x0000000000000000000000000000000000000001\","
"\"data\":\"";
static const char requestSuffix[] = "\"},\"latest\"]}";
const int bodyLen = (int)(sizeof(requestPrefix) - 1) + 258 + (int)(sizeof(requestSuffix) - 1);
Serial.println(
F(
"determineYParity: WiFi reconnect failed"));
continue;
}
WiFiSSLClient wifiClient;
#ifndef WIFI_DISABLE_CA_PINNING
#endif
client.beginRequest();
if (err != HTTP_SUCCESS) {
Serial.print(
F(
"determineYParity: POST failed, err="));
Serial.println(err);
client.stop();
continue;
}
client.sendHeader("Content-Type", "application/json");
client.sendHeader("Content-Length", bodyLen);
#if defined(RPC_PROJECT_ID) && defined(RPC_API_SECRET)
{
char authBuf[AUTH_HEADER_BUF_SIZE];
buildBasicAuthHeader(authBuf, sizeof(authBuf));
client.sendHeader("Authorization", authBuf);
}
#endif
client.beginBody();
client.print(requestPrefix);
client.print(hexBuf);
client.print(requestSuffix);
client.endRequest();
int status = client.responseStatusCode();
String response = client.responseBody();
client.stop();
continue;
}
int resultIdx = response.indexOf("\"result\"");
if (resultIdx < 0) {
continue;
}
int hexIdx = response.indexOf("0x", resultIdx);
if (hexIdx < 0) {
continue;
}
if (response.length() < (unsigned int)(hexIdx + 66)) {
continue;
}
String recovered = response.substring(hexIdx + 26, hexIdx + 66);
Serial.print(
F(
"[ecrecover] v=")); Serial.print(v);
Serial.print(
F(
" recovered=0x")); Serial.println(recovered);
Serial.print(
F(
"[ecrecover] expected=0x")); Serial.println(
F(
ADDR_FROM));
result = yp;
}
}
return result;
}
static const char requestPrefix[] =
"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"eth_getTransactionCount\","
"\"params\":[\"0x";
static const char requestSuffix[] = "\",\"pending\"]}";
const int bodyLen = (int)(sizeof(requestPrefix)-1) + 40 + (int)(sizeof(requestSuffix)-1);
uint8_t result = 1U;
for (uint8_t attempt = 0U; (attempt <
TX_MAX_RETRIES) && (result != 0U); attempt++) {
if (attempt != 0U) {
delay(1000U);
}
Serial.println(
F(
"fetchNonce: WiFi reconnect failed"));
continue;
}
WiFiSSLClient wifiClient;
#ifndef WIFI_DISABLE_CA_PINNING
#endif
client.beginRequest();
Serial.print(
F(
"fetchNonce: connecting to "));
if (err != HTTP_SUCCESS) {
Serial.print(
F(
"fetchNonce: POST failed, err="));
Serial.println(err);
client.stop();
continue;
}
client.sendHeader("Content-Type", "application/json");
client.sendHeader("Content-Length", bodyLen);
#if defined(RPC_PROJECT_ID) && defined(RPC_API_SECRET)
{
char authBuf[AUTH_HEADER_BUF_SIZE];
buildBasicAuthHeader(authBuf, sizeof(authBuf));
client.sendHeader("Authorization", authBuf);
}
#endif
client.beginBody();
client.print(requestPrefix);
client.print(requestSuffix);
client.endRequest();
int status = client.responseStatusCode();
String resp = client.responseBody();
client.stop();
int ri = resp.indexOf("\"result\"");
int xi = (ri >= 0) ? resp.indexOf("0x", ri) : -1;
if (xi >= 0) {
uint64_t parsed = 0U;
int digitCount = 0;
bool overflowed = false;
for (int i = xi + 2; i < (int)resp.length(); i++) {
char c = resp[i];
if (!((c>='0'&&c<='9')||(c>='a'&&c<='f')||(c>='A'&&c<='F'))) break;
if (digitCount >= 16) { overflowed = true; break; }
parsed = (parsed << 4) |
fromHex(c);
digitCount++;
}
if (!overflowed && (digitCount > 0)) {
*nonce = parsed;
result = 0U;
}
}
}
}
return result;
}
Serial.begin(115200);
delay(2000);
SPI.begin();
Serial.println(
F(
"PN532 init failed! Halting."));
while(1);
}
Serial.println(
F(
"PN532 OK"));
#ifdef WIFI_DISABLE_CA_PINNING
Serial.println(
F(
"⚠️ WIFI_DISABLE_CA_PINNING is set — TLS certificate is NOT validated."));
Serial.println(
F(
" Connection is vulnerable to MITM. DEV ONLY, do not use in production."));
#endif
Serial.print(
F(
"Connecting to WiFi"));
uint8_t retries = 20U;
while ((retries > 0U) && (WiFi.status() != WL_CONNECTED)) {
retries--;
delay(500U);
}
Serial.println();
if (WiFi.status() != WL_CONNECTED) {
Serial.println(
F(
"WiFi failed!"));
while(1);
}
delay(2000);
uint8_t calldata[68];
uint64_t fetchedNonce = 0U;
Serial.println(
F(
"fetchNonce failed! Halting."));
while(1);
}
tx2.
nonce = fetchedNonce;
static const size_t kRlpBufSize = 512U;
uint8_t rlpUnsigned[kRlpBufSize];
if ((rlpLen == 0U) || (rlpLen > kRlpBufSize)) {
Serial.print(
F(
"[fatal] rlpUnsigned overflow or empty: ")); Serial.println(rlpLen);
while (true) {}
}
uint8_t hashKeccak[32];
keccak256(
static_cast<const uint8_t*
>(rlpUnsigned), rlpLen, hashKeccak);
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,
};
Serial.println(
F(
"Place Cryptnox card on PN532 reader..."));
for (uint8_t attempt = 0U; attempt < 3U; attempt++) {
if (attempt != 0U) {
delay(1000U);
}
if (!
wallet.connect(session)) {
continue;
}
Serial.println(
F(
"Card connected, secure channel established."));
signReq.hash = hashKeccak;
signReq.derivePath = eth_path;
signReq.derivePathLength = static_cast<uint8_t>(sizeof(eth_path));
signResult =
wallet.sign(signReq);
break;
}
Serial.print(
F(
"Card rejected sign command (error=0x"));
Serial.println(
F(
") — check PIN and card initialisation. Halting."));
while(1);
}
Serial.print(
F(
"Sign attempt "));
Serial.print(attempt + 1U);
Serial.print(
F(
" failed, error=0x"));
}
Serial.print(
F(
"Sign failed: error=0x"));
while(1);
}
Serial.println(
F(
"Signed."));
const uint8_t* s = signResult.
signature + 32;
Serial.println(
F(
"yParity determination failed! Halting."));
while(1);
}
Serial.print(
F(
"yParity: "));
Serial.println(yParity);
uint8_t rlpSigned[kRlpBufSize];
size_t rlpSignedLen =
rlpEncodeSignedTx(tx2, r, s, &yParity, rlpSigned, kRlpBufSize);
if ((rlpSignedLen == 0U) || (rlpSignedLen > kRlpBufSize)) {
Serial.print(
F(
"[fatal] rlpSigned overflow or empty: ")); Serial.println(rlpSignedLen);
while (true) {}
}
Serial.println(
F(
"Sending..."));
Serial.println(
F(
"Transaction sent successfully!"));
} else {
Serial.println(
F(
"Transaction FAILED."));
}
}
void setup()
Arduino setup function.
CryptnoxWallet wallet(nfc, serialAdapter, cryptoProvider, platform)
PN532Adapter nfc(serialAdapter, PN532_SS, &SPI)
ArduinoLoggerAdapter serialAdapter
ArduinoCryptoProvider cryptoProvider
void loop()
Arduino main loop.
#define CW_SIGN_NO_KEY_LOADED
#define CW_SIGN_SIG_ECDSA_LOW_S
#define CW_SIGN_DERIVE_K1
#define CW_SIGN_PIN_INCORRECT
#define PN532_SS_PIN
SPI slave-select (CS) pin connected to the PN532 module.
#define HTTP_OK
Expected HTTP 200 OK status code.
size_t rlpEncodeSignedTx(const Tx2 &tx, const uint8_t *r, const uint8_t *s, const uint8_t *v, uint8_t *out, size_t outCap)
#define ECRECOVER_V_PAD_CHARS
Number of leading zero hex characters in the ecrecover v-field padding.
size_t encodeERC20Transfer(uint8_t *out)
Encode calldata for ERC-20 transfer(address to, uint256 amount).
static const char hexChars[]
static void printHex(const char *label, const uint8_t *data, size_t len)
#define RLP_ITEM_OR_FAIL(BUF, CAP, OFF, IN, IN_LEN)
#define YPARITY_UNKNOWN
Sentinel returned by determineYParity() when recovery fails.
#define WIFI_RETRY_MAX
Maximum WiFi reconnect poll iterations (each iteration waits 500 ms).
#define TX_MAX_RETRIES
Maximum number of send-transaction attempts before giving up.
#define ERC20_INDEX_OFFSET
#define HEX_CHAR_BUF_SIZE
Buffer size for a two-hex-char + NUL string used in byte-to-hex conversion.
static size_t rlpFinalize(uint8_t *out, size_t outCap, const uint8_t *buf, size_t off)
#define ERC20_TRANSFER_SEL_3
#define ECRECOVER_V_BASE
Base value for Ethereum ecrecover v parameter (yParity=0 → v=27, yParity=1 → v=28).
#define ERC20_TRANSFER_SEL_1
size_t rlpEncodeUnsignedTx(const Tx2 &tx, uint8_t *out, size_t outCap)
bool sendRawTx(const uint8_t *raw, size_t len)
Send a raw signed transaction via JSON-RPC.
uint8_t determineYParity(const uint8_t *hash, const uint8_t *r, const uint8_t *s)
Determine EIP-1559 yParity by calling the Ethereum ecrecover precompile.
#define TX_RETRY_DELAY_MS
Delay in ms between send-transaction retry attempts.
uint8_t fetchNonce(uint64_t *nonce)
Fetch the current nonce for ADDR_FROM via eth_getTransactionCount.
#define ERC20_TRANSFER_SEL_0
static size_t rlpEncodeTxBody(uint8_t *buf, size_t bufCap, const Tx2 &tx)
#define ERC20_TRANSFER_SEL_2
CW_CryptoProvider implementation for the Arduino UNO R4 (RA4M1).
CW_Logger implementation wrapping Arduino's HardwareSerial.
static bool safe_memcpy(uint8_t *dst, size_t dstSize, const uint8_t *src, size_t count)
Safe memcpy — validates pointers, sizes, and checks for overlap.
static void secure_wipe(uint8_t *buf, size_t len)
Securely zero a buffer, guaranteed not to be optimised away.
High-level interface for interacting with a Cryptnox Hardware Wallet over NFC.
CW_NfcTransport implementation over the Adafruit_PN532 driver.
void keccak256(const uint8_t *in, size_t inlen, uint8_t out[32])
Compute Keccak-256 hash of input data.
Keccak-256 (SHA3 variant) hash function for Ethereum.
Holds cryptographic session state for reentrant secure channel operations.
Request parameters for CryptnoxWallet::sign.
Result of CryptnoxWallet::sign.
uint8_t signature[CW_RAW_SIGNATURE_SIZE]
Ethereum EIP-1559 transaction structure.
uint64_t maxPriorityFeePerGas
size_t trimLeadingZeros(uint8_t *out, size_t out_cap, const uint8_t *in, size_t in_len)
Trims leading zeros from a byte array.
uint32_t RlpEncodeWholeHeader(uint8_t *header_output, size_t header_cap, uint32_t total_len)
Encodes the RLP list header for a sequence of items.
int fromHex(char c)
Convert a hexadecimal character to a byte value.
bool hexToBytes(const char *hex, uint8_t *out, size_t len)
Convert a hex string to a byte array.
uint32_t RlpEncodeItem(uint8_t *output, size_t output_cap, const uint8_t *input, uint32_t input_len)
Encodes a single RLP item.
uint32_t ConvertNumberToUintArray(uint8_t *str, uint64_t val)
Converts an unsigned integer into a big-endian byte array.