Skip to content

Secure element (TROPIC01)

This is the developer reference for the TROPIC01 secure element (SE): the authoritative slot allocation, the per-slot R-Memory size, the ISecureElement operation set, and the supported curves. For the user-facing security model (key non-exportability, on-chip generation, tamper-evidence), see Secure element & automatic key generation.

The badge stores private keys inside the TROPIC01, a chip separate from the ESP32-S3 application processor. Two distinct storage areas are exposed: ECC key slots for key pairs and R-Memory slots for metadata and small secrets.

AreaCountSlot index rangeReserved slot
ECC key slots320-310
R-Memory slots5120-5110

The counts and bounds are fixed in two sources that must agree:

  • main/tropic_slot_map.h: ECC_SLOT_MIN = 0 / ECC_SLOT_MAX = 31 (lines 18-19), RMEM_SLOT_MIN = 0 / RMEM_SLOT_MAX = 511 (lines 22-23).
  • ISecureElement.h: ECC_SLOT_COUNT = 32 (line 61), RMEM_SLOT_COUNT = 512 (line 62).

Slot 0 of each area is reserved: ECC_SLOT_RESERVED = 0 (tropic_slot_map.h line 20) and RMEM_SLOT_RESERVED = 0 (line 24). The lowest allocatable R-Memory slot is RMEM_SLOT_MIN_ALLOC = 1 (line 25).

The per-slot R-Memory capacity depends on the TROPIC01 Application FW version, so the firmware exposes both a stable minimum and a runtime query (ISecureElement.h):

ConstantValueMeaning
RMEM_SLOT_SIZE444Minimum guaranteed slot size across all supported FW versions (line 67)
RMEM_SLOT_SIZE_MAX475Stack-buffer ceiling sized for the largest known FW (line 69)
getRmemSlotSize()runtimeActual size reported by the running chip; always >= RMEM_SLOT_SIZE and <= RMEM_SLOT_SIZE_MAX (lines 244-249)

Use 444 bytes for static layouts that must remain stable across reflashes; query getRmemSlotSize() for the real runtime capability. RMEM_NAME_LEN = 16 (line 70) is the fixed length of the R-Memory header name field.

The slot map is the compile-time, authoritative allocation across modules (main/tropic_slot_map.h). Each module owns disjoint ECC and R-Memory ranges:

ModuleModule IDECC slotsR-Memory slots
System / attestation00 (reserved key slot)0 (reserved)
GPG21-31-3
CA344
FIDO245-305-31
2FA (TOTP)5-32-131
Password vault6-132-500
Plugins731501-511

Source lines in main/tropic_slot_map.h:

  • Module IDs: MODULE_ID_MOD_SYSTEM 0 (line 30), MODULE_ID_MOD_GPG 2 (line 31), MODULE_ID_MOD_CA 3 (line 32), MODULE_ID_MOD_FIDO2 4 (line 33), MODULE_ID_MOD_2FA 5 (line 34), MODULE_ID_MOD_PASSWORD 6 (line 35), MODULE_ID_PLUGIN_POOL 7 (line 36); MODULE_ID_UNKNOWN 255 (line 37).
  • ECC ranges: GPG 1-3 (lines 40-41), CA 4-4 (lines 42-43), FIDO2 5-30 (lines 44-45), Plugins 31-31 (lines 47-48).
  • R-Memory ranges: GPG 1-3 (lines 51-52), CA 4-4 (lines 53-54), FIDO2 5-31 (lines 55-56), 2FA 32-131 (lines 57-58), Password 132-500 (lines 59-60), Plugins 501-511 (lines 64-65).

Notes from the source:

  • 2FA (TOTP) and the password vault hold no ECC key pairs; they appear only in the R-Memory map (TROPIC_RMEM_SLOT_MAP, lines 74-80) and are absent from the ECC map (TROPIC_ECC_SLOT_MAP, lines 68-72).
  • FIDO2 ECC ends at slot 30, so the single plugin ECC slot 31 (the last physical slot) does not overlap (tropic_slot_map.h lines 45-48).
  • The plugin R-Memory range 501-511 is a pool whose named sub-slots are assigned dynamically at runtime, while the range bounds are validated centrally (lines 61-65).
  • Module IDs are permanent on-device identifiers and are never reassigned; new modules take new IDs (lines 27-29).

The reserved attestation key in ECC slot 0 and the PIN record in R-Memory slot 0 are detailed on the secure-element security page and the attestation page.

EccCurve (ISecureElement.h lines 12-15) defines two curves:

CurveEnumModule-level byte
NIST P-256 (secp256r1)EccCurve::P2561
Ed25519EccCurve::ED255190

The byte mapping is fixed by curveByte() (lines 22-24, returns 0 for Ed25519, 1 otherwise) and curveFromByte() (lines 31-33, 0 maps to Ed25519, otherwise P-256).

All operations are declared on ISecureElement (cdc_hal), a pure-virtual interface. Results are reported via the SeResult enum (lines 38-47): OK, ERROR, SESSION_REQUIRED, SLOT_EMPTY, SLOT_OCCUPIED, INVALID_PARAM, ALARM_MODE (tamper detected), NOT_SUPPORTED.

A secure session is required before operations (lines 74-94):

MethodSignaturePurpose
sessionStartbool sessionStart()Open a secure session (line 79)
sessionEndvoid sessionEnd()Close the session (line 84)
isSessionActivebool isSessionActive() constQuery session state (line 89)
sleepvoid sleep()Put the chip to sleep (line 94)

When no session is available, operations return SESSION_REQUIRED.

MethodSignature (lines)Purpose
eccGenerateSeResult eccGenerate(uint8_t slot, EccCurve curve) (103)Generate a key pair on-chip in the given slot and curve
eccImportSeResult eccImport(uint8_t slot, const uint8_t* privKey, EccCurve curve) (111)Import a 32-byte private key
eccGetPublicKeySeResult eccGetPublicKey(uint8_t slot, uint8_t* pubKey, EccCurve* curve = nullptr) (119)Read the public key (65 bytes P-256, 32 bytes Ed25519)
eccDeleteSeResult eccDelete(uint8_t slot) (124)Delete the key in a slot
eccSlotUsedbool eccSlotUsed(uint8_t slot) const (129)Check whether a slot holds a key

There is no private-key read or export call. The only key-material read-back is eccGetPublicKey, which returns the public key.

MethodSignature (lines)Notes
ecdsaSignSeResult ecdsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen, uint8_t* sig, size_t* sigLen) (142-143)P-256 ECDSA; the SE hashes the message internally with SHA-256, so callers MUST NOT pre-hash. Output is raw R||S (64 bytes).
eddsaSignSeResult eddsaSign(uint8_t slot, const uint8_t* msg, size_t msgLen, uint8_t* sig) (152-153)Ed25519; output 64 bytes.
MethodSignature (lines)Purpose
rmemReadSeResult rmemRead(uint16_t slot, uint8_t* data, uint16_t maxLen, uint16_t* actualLen) (164-165)Read a slot
rmemWriteSeResult rmemWrite(uint16_t slot, const uint8_t* data, uint16_t len) (173)Write a slot
rmemEraseSeResult rmemErase(uint16_t slot) (178)Erase a slot
rmemSlotUsedbool rmemSlotUsed(uint16_t slot) const (183)Check whether a slot holds data
rmemWriteWithHeaderSeResult rmemWriteWithHeader(uint16_t slot, uint8_t moduleId, const char* name, uint8_t flags, const uint8_t* payload, uint16_t payloadLen) (199-201)Write a slot with the common header plus payload
rmemReadWithHeaderSeResult rmemReadWithHeader(uint16_t slot, RMemHeader* headerOut, uint8_t* payloadOut, uint16_t payloadMax, uint16_t* payloadLenOut) (206-208)Read header plus payload

The packed RMemHeader (lines 187-194) carries magic, checksum, moduleId, flags, a char name[RMEM_NAME_LEN] (16 bytes), and payloadLen.

MethodSignature (lines)Behaviour
getRandombool getRandom(uint8_t* buffer, uint16_t size) (219)Hardware TRNG, with an ESP32 TRNG fallback when no SE session is available (a WARN is logged on fallback)
getRandomStrictbool getRandomStrict(uint8_t* buffer, uint16_t size) (229)Hardware TRNG only; returns false and leaves the buffer untouched if the SE TRNG is unreachable. Use for keys and seeds.
MethodSignature (lines)Purpose
getChipIdbool getChipId(uint8_t* serialNum, uint8_t size) (236)Read the chip serial number
getFwVersionbool getFwVersion(uint8_t riscvVer[4], uint8_t spectVer[4]) (242)Read RISC-V and SPECT FW versions (index 3 major, 2 minor, 1 patch, 0 build)
getRmemSlotSizeuint16_t getRmemSlotSize() const (249)Runtime R-Memory slot size

The instance is obtained via the factory getSecureElementInstance() (ISecureElement.h line 253).

ConcernFile
Slot allocation (authoritative)main/tropic_slot_map.h
Operation interface, curves, sizescomponents/cdc_hal/include/cdc_hal/ISecureElement.h
User-facing security model/security/secure-element-keys/