|
cryptnox-sdk-arduino 1.0.0
Arduino library for Cryptnox Hardware Wallet
|
Files | |
| config.template.h | |
| keccak256.cpp | |
| Keccak-256 (SHA3 variant) hash implementation for Ethereum. | |
| keccak256.h | |
| Keccak-256 (SHA3 variant) hash function for Ethereum. | |
| UsdcSigning.ino | |
| util.cpp | |
| util.h | |
End-to-end demonstration of the Cryptnox Hardware Wallet on an Arduino UNO R4 WiFi: connect to a Wi-Fi access point, fetch the nonce and fee parameters over JSON-RPC, build and Keccak-256-hash the unsigned EIP-1559 transaction, sign it on the card (BIP-44 m/44'/60'/0'/0/0, secp256k1, canonical low-S), recover yParity, broadcast the signed transaction, and print the on-chain tx hash.
The private key never leaves the card. The Arduino only ever holds the Account derived from the card's public key, the unsigned tx hash, and the resulting (r, s).
| Component | Details |
|---|---|
| Hardware Wallet | Cryptnox Hardware Wallet, initialised and seeded |
| Wallet funding | The address derived from m/44'/60'/0'/0/0 on the card needs Sepolia ETH (for gas) and Sepolia USDC. Faucets: Sepolia ETH · Circle USDC |
| NFC reader | PN532 over SPI — see hardware setup |
| Board | Arduino UNO R4 WiFi (the Minima has no Wi-Fi) |
| RPC endpoint | A Sepolia JSON-RPC endpoint — PublicNode (no signup, default) or Infura |
| SDK | Installed via setup.bat — see installation |
Create config.h by copying the template:
Fill in at minimum: WIFI_SSID, WIFI_PASSWORD, CARD_PIN, ADDR_FROM (the address derived from m/44'/60'/0'/0/0 on the card), ADDR_TO (recipient), AMOUNT_USDC (token base units — 1 USDC = 1 000 000).
Paste the final [tx] hash into Sepolia Etherscan to watch confirmation.
WiFiSSLClient is initialised with setCACert(WIFI_CA_CERT). The sketch ships with ISRG Root X1 (Let's Encrypt — Internet Security Research Group) as a placeholder default. That root does not match PublicNode's current chain (ethereum-sepolia-rpc.publicnode.com is issued by Google Trust Services R4) nor Infura's (DigiCert), so for any real endpoint you will need to override WIFI_CA_CERT in config.h. Retrieve the correct root with:
For development only you can set WIFI_DISABLE_CA_PINNING in config.h to skip validation. The sketch prints a loud warning at boot when this is set.
EIP-1559 signatures carry a 1-bit yParity instead of EIP-155's v. The card returns only r and s, so the sketch calls the ecrecover precompile at address 0x01 with v = 27 (yParity = 0). If the recovered address matches ADDR_FROM the sketch keeps yParity = 0; otherwise it retries with v = 28 (yParity = 1). One of the two will match when the card's key and ADDR_FROM are consistent.
All fields live in config.h (gitignored). Start from config.template.h and fill in:
| Field | Required | Example |
|---|---|---|
| WIFI_SSID | yes | "MyHomeNetwork" |
| WIFI_PASSWORD | yes | "password" |
PublicNode (free, no signup, default):
Infura (free tier, requires API key):
| Field | Default | Notes |
|---|---|---|
| WIFI_CA_CERT | ISRG Root X1 PEM (placeholder bundled in the sketch) | Almost always needs overriding — set it to the root CA your RPC provider uses |
| WIFI_DISABLE_CA_PINNING | unset | Defining it disables TLS validation — DEV ONLY |
| Field | Example |
|---|---|
| CARD_PIN | "000000000" (must match cryptnox init) |
| CARD_PIN_LEN | 9U (ASCII digit count) |
| ADDR_FROM | "4aadf6f331aea39e699db17c75a4b12d993956d2" (lowercase hex, no 0x) |
ADDR_FROM must equal the address derived from m/44'/60'/0'/0/0 on the card. If they disagree, the yParity recovery loop cannot find a matching value and the sketch halts.
| Field | Default | Notes |
|---|---|---|
| ADDR_TO | "Cd7E5...c06e" | Recipient address (hex, no 0x) |
| ADDR_USDC | "1c7D4...7238" | USDC contract on Sepolia |
| CHAIN_ID_SEPOLIA | 11155111 | Hardcoded — no path to broadcast on mainnet by accident |
| AMOUNT_USDC | 1000000UL | 1 USDC (6 decimals) |
| MAX_PRIORITY_FEE | 2000000000ULL | 2 Gwei |
| MAX_FEE | 4000000000ULL | 4 Gwei |
| GAS_LIMIT_ERC20 | 60000ULL | Standard ERC-20 transfer |
After setup.bat's memory-optimisation pass:
| Section | Size |
|---|---|
| Flash (.text + .data) | ~99 KB / 256 KB (38 %) |
| RAM (.bss + .data) | ~11.6 KB / 32 KB (35 %) |
The bulk of the flash (~25 KB) is the WiFi/HTTP stack plus the bundled CA certificate (~1.4 KB PEM). The Cryptnox SDK itself, together with the RLP / Keccak-256 helpers, sits at around ~20 KB.
| Symptom | Cause | Fix |
|---|---|---|
| fetchNonce: POST failed, err=-1 | TLS handshake fails (wrong CA pinned) | Re-extract the chain with openssl s_client -showcerts -servername HOST -connect HOST:443 </dev/null and put the root in WIFI_CA_CERT. For a quick diagnosis, temporarily set WIFI_DISABLE_CA_PINNING to confirm the cert is the blocker |
| Connecting to WiFi.... (never stops) | Wrong SSID / password, or a 5 GHz-only network | UNO R4 WiFi is 2.4 GHz only — verify SSID, password, band |
| [fatal] tx.to is not a valid 40-char hex string | Bad hex in ADDR_TO | 40 hex chars, no 0x, lowercase preferred |
| yParity determination failed! | ADDR_FROM doesn't match the card's m/44'/60'/0'/0/0 address | Derive the address with the Cryptnox CLI and copy it into ADDR_FROM |
| Tx broadcast OK but tefPAST_NONCE on Etherscan | Stale nonce from the RPC | Wait a few seconds and re-run; the sketch fetches the nonce just before signing |
| Wrong PIN — halting to protect retry counter | CARD_PIN does not match the card | Fix CARD_PIN — do not keep retrying, every attempt burns one of the on-card tries (see VerifyPin) |
| Sign failed: 0x81 | Card has no seed | cryptnox seed generate |
cryptnox-sdk-arduino is dual-licensed:
For commercial inquiries, contact: conta.nosp@m.ct@c.nosp@m.ryptn.nosp@m.ox.c.nosp@m.om