Skip to content

Secure element & automatic key generation

The badge stores its private keys in a TROPIC01 secure element (SE), a separate chip from the ESP32-S3 application processor. Private keys live inside the SE and the application firmware talks to it over a defined operation set: it can ask the SE to generate a key, read back a public key, or sign a message, but the operation interface exposes no way to read a private key out.

This page describes the storage model, the slot allocation, the operations the SE performs, and which keys are created automatically, all from the firmware source.

Two kinds of storage: ECC slots and R-Memory

Section titled “Two kinds of storage: ECC slots and R-Memory”

The SE provides two distinct storage areas:

AreaCountSlot 0Per-slot sizeHolds
ECC key slots32 (0-31)reservedone key pairprivate keys (signing)
R-Memory slots512 (0-511)reserved>= 444 bytesmetadata / small secrets

The counts are fixed in both the slot map and the hardware interface (ECC_SLOT_MIN/ECC_SLOT_MAX and RMEM_SLOT_MIN/RMEM_SLOT_MAX in main/tropic_slot_map.h; ECC_SLOT_COUNT = 32, RMEM_SLOT_COUNT = 512 in ISecureElement.h). Slot 0 of each area is reserved.

R-Memory slot capacity depends on the SE’s running application firmware: the firmware treats 444 bytes as the minimum guaranteed per-slot size and queries the chip at runtime for the actual size (always between 444 and 475 bytes). Use 444 bytes as the safe planning figure.

The slot map is the authoritative, compile-time allocation of ECC and R-Memory slots across modules (main/tropic_slot_map.h):

ModuleECC slotsR-Memory slots
System / attestation0 (attestation key)0 (reserved)
GPG1-31-3
CA44
FIDO25-305-31
TOTP (2FA)-32-131
Password vault-132-500
Plugins31501-511

TOTP and the password vault use only R-Memory (they store secrets, not SE key pairs). FIDO2 ends at ECC slot 30 so the single plugin ECC slot (31) does not overlap. The reserved attestation key in ECC slot 0 is covered on the dedicated attestation page.

The operation interface (ISecureElement.h) exposes:

  • Key generation (eccGenerate): create a new key pair directly in an ECC slot, on either the P-256 (secp256r1) or Ed25519 curve.
  • Public-key read-back (eccGetPublicKey): retrieve the public key of a slot. There is no matching “read private key” call.
  • Key import (eccImport) and delete (eccDelete).
  • Signing: ecdsaSign (P-256 ECDSA; the SE hashes the message internally with SHA-256, so callers must not pre-hash) and eddsaSign (Ed25519).
  • R-Memory read/write/erase, with a small header helper for module-tagged payloads.
  • Hardware TRNG: getRandom (with an ESP32 RNG fallback when no SE session is available, logged as a warning) and getRandomStrict (returns bytes only when they came from the SE TRNG, for keys and seeds).

State this precisely, because it is the heart of the badge’s security model:

  • The SE operation interface used by the firmware has no private-key read or export call. The only way to retrieve key material is eccGetPublicKey, which returns the public key. Signing is performed by handing the message to the SE (ecdsaSign / eddsaSign); the private key never enters application memory.
  • Keys can be created on-chip (eccGenerate), so for those keys no private value ever existed outside the SE.

Several keys are generated inside the SE, automatically, rather than being imported from outside:

  • Attestation / device identity key (ECC slot 0). Generated on-chip on first boot if the slot is empty, on the P-256 curve. The firmware reads back only the public key, hashes it, and stores that hash to detect later tampering or regeneration. The private key is never exported. Full lifecycle on the attestation page.
  • FIDO2 credential keys. Created on-chip via eccGenerate when a new credential is registered (mod_fido2/src/fido2_storage.cpp).
  • GPG / OpenPGP keys. Generated on-chip via eccGenerate in the GPG module and OpenPGP card path (mod_gpg/src/gpg.cpp, mod_gpg/src/openpgp/openpgp.cpp).

Because these keys are generated locally, a freshly flashed or wiped badge produces brand-new key material. There is no factory-installed key shared across devices.

The PIN record (R-Memory slot 0) is signed with the chip-bound attestation key in ECC slot 0. If slot 0 is regenerated or the stored payload is altered, the signature no longer verifies and the firmware re-initialises the PIN store to defaults instead of trusting the tampered record (PinManager::loadFromStorage). This binds the PIN data to the specific SE.

The record holds two different PIN-hash forms, which is protocol-driven, not an inconsistency: the badge PIN (which doubles as the FIDO2 CTAP2 ClientPIN) is stored as LEFT(SHA-256(PIN), 16), while the OpenPGP PW1/PW3 PINs use Iterated+Salted S2K because the OpenPGP card KDF-DO requires it. The duress PIN also uses S2K. See ADR-0004 and the PINs & lockout page.

PropertyStatus
ECC slots32, slot 0 reserved
R-Memory slots512, slot 0 reserved, >= 444 bytes each
CurvesP-256 (secp256r1), Ed25519
SigningECDSA-SHA256 (P-256), EdDSA (Ed25519)
Private-key export callNone in the operation interface
On-chip generationAttestation, FIDO2, GPG keys
Key import path existsYes (eccImport)
Hardware TRNGYes (getRandom, getRandomStrict)
Tamper/alarm stateReported by SE (ALARM_MODE)