OTP fuses¶
Here, OTP means one-time-programmable chip fuses, not Yubico one-time passwords.
PicoKeys uses the same logical idea on RP2350/RP2354 and ESP32-S3 class devices, but the hardware names are different:
- RP2350/RP2354: OTP rows and page locks.
- ESP32-S3: eFuse key blocks, purpose fields, secure-boot enable bits, revoke bits, and debug-disable bits.
The source of truth for the current implementation is pico-keys-sdk/src/fs/otp.c and pico-keys-sdk/src/fs/otp.h.
Danger
These writes are permanent. A wrong OTP/eFuse write can make a board unrecoverable or unable to boot unsigned/development firmware.
Two different OTP meanings¶
Do not confuse these:
- Chip OTP/eFuse: irreversible hardware state used for root keys, secure boot, lockout, and sealing.
- Pico FIDO OTP slots: Yubico OTP, HOTP, static password, and challenge-response credentials.
This page is only about chip OTP/eFuse state.
Logical keys¶
PicoKeys initializes two 32-byte hardware-backed values when the relevant OTP/eFuse storage is empty:
| Logical name | Code name | Purpose |
|---|---|---|
| Key 1 | otp_key_1 |
Random 32-byte key used as the device root / MKEK masking material. |
| Key 2 | otp_key_2 |
Generated secp256k1 private key used by rescue / secure operations that need a device private key. |
The exact storage differs by platform.
RP2350 / RP2354¶
For RP2350-class builds, the SDK defines:
| Logical value | OTP row | Notes |
|---|---|---|
otp_key_1 / MKEK |
0xE90 |
New MKEK row, 32 bytes. |
otp_key_2 / DEVK |
0xE80 |
New device-key row, 32 bytes. |
| old MKEK migration source | 0xEF0 |
Read if present, migrated to 0xE90, then invalidated. |
| old DEVK migration source | 0xED0 |
Read if present, migrated to 0xE80, then invalidated. |
After writing each new key, the firmware writes anti-imaging chaff:
| Key row | Chaff row |
|---|---|
0xE90 |
0xEB0 |
0xE80 |
0xEA0 |
The chaff is the bitwise complement of the raw key data, written at row + 32.
RP2350 page lock¶
After writing or migrating the MKEK/DEVK material, the firmware locks the OTP page containing those rows:
| Page | Rows covered | Why |
|---|---|---|
page 58 |
0xE80 to 0xEBF |
Protects MKEK/DEVK and chaff rows from later direct access according to the page lock policy. |
The code calls otp_lock_page(OTP_MKEK_ROW >> 6), and 0xE90 >> 6 is page 58.
RP2350 secure boot¶
When secure boot is enabled through otp_enable_secure_boot(), these RP2350 OTP areas are modified:
| Region / constant | What is burned |
|---|---|
OTP_DATA_BOOTKEY0_0_ROW + 0x10 * bootkey |
The 32-byte secure-boot public-key hash used by the boot ROM. |
OTP_DATA_BOOT_FLAGS1_ROW plus redundancy rows R1 and R2 |
Marks the selected boot key as valid. If secure lock is requested, marks every other key invalid. |
OTP_DATA_CRIT1_ROW plus redundancy rows R1 through R7 |
Sets SECURE_BOOT_ENABLE. If secure lock is requested, also sets DEBUG_DISABLE, GLITCH_DETECTOR_ENABLE, and maximum glitch-detector sensitivity bits. |
OTP_DATA_PAGE1_LOCK1_ROW |
If secure lock is requested, makes page 1 bootloader-read-only. |
OTP_DATA_PAGE2_LOCK1_ROW |
If secure lock is requested, makes page 2 bootloader-read-only. |
The secure-boot key hash currently used by the SDK is the fixed 32-byte value also present in config/rp2350/secure_boot.json.
RP2350 anti-rollback¶
The current PicoKeys SDK code inspected here enables secure boot and secure lock, but this documentation should not claim a PicoKeys-specific rollback fuse map unless the provisioning tool for the target firmware exposes and documents it.
The RP2350 boot ROM has rollback support, but burning rollback state is a separate production decision from burning MKEK/DEVK and secure-boot state.
ESP32-S3¶
ESP32-S3 does not use RP2350-style OTP rows. It uses eFuse key blocks and named eFuse fields.
For ESP builds, the SDK defines:
| Logical value | eFuse storage | Key purpose |
|---|---|---|
otp_key_1 |
EFUSE_BLK_KEY3 |
Written with ESP_EFUSE_KEY_PURPOSE_USER. |
otp_key_2 |
EFUSE_BLK_KEY4 |
Written with ESP_EFUSE_KEY_PURPOSE_USER. |
After writing either key block, the firmware write-protects:
- the key block itself with
esp_efuse_set_key_dis_write() - the key-purpose field with
esp_efuse_set_keypurpose_dis_write()
ESP32-S3 secure boot¶
When otp_enable_secure_boot() is called on ESP32-S3:
| eFuse state | What is burned |
|---|---|
unused key block selected by esp_efuse_find_unused_key_block() |
Stores the fixed 32-byte secure-boot digest used by PicoKeys. |
| selected key block purpose | Set to SECURE_BOOT_DIGEST0, SECURE_BOOT_DIGEST1, or SECURE_BOOT_DIGEST2 depending on bootkey index. |
ESP_EFUSE_SECURE_BOOT_EN |
Enables secure boot if not already enabled. |
If a digest for the requested index already exists, the code verifies that it matches the expected PicoKeys digest before continuing.
ESP32-S3 secure lock¶
If secure lock is requested, the SDK additionally burns:
| eFuse state | Effect |
|---|---|
| digest revoke bits for every non-selected digest index | Revokes all secure-boot digest slots except the selected one. |
| selected secure-boot key block write-disable | Prevents rewriting the secure-boot digest block. |
| selected secure-boot key-purpose write-disable | Prevents changing the digest block purpose. |
ESP_EFUSE_SOFT_DIS_JTAG, when available |
Disables software JTAG. |
ESP_EFUSE_HARD_DIS_JTAG, when available |
Disables hardware JTAG. |
ESP_EFUSE_DIS_USB_JTAG, when available |
Disables USB JTAG. |
ESP_EFUSE_DIS_USB_SERIAL_JTAG, when available |
Disables USB serial/JTAG. |
ESP_EFUSE_DIS_PAD_JTAG, when available |
Disables pad JTAG. |
The code intentionally leaves ROM download mode enabled: the call to esp_efuse_disable_rom_download_mode() is present but commented out.
Summary¶
RP2350 / RP2354 burns¶
0xE90: MKEK /otp_key_10xE80: DEVK /otp_key_20xEB0: complement chaff for0xE900xEA0: complement chaff for0xE80- page 58 lock for the MKEK/DEVK page
- secure-boot bootkey row for the selected boot key
BOOT_FLAGS1valid bit for selected boot key- optional
BOOT_FLAGS1invalid bits for other boot keys when secure lock is requested CRIT1.SECURE_BOOT_ENABLE- optional
CRIT1.DEBUG_DISABLE,CRIT1.GLITCH_DETECTOR_ENABLE, and glitch sensitivity when secure lock is requested - optional page 1 and page 2 bootloader-read-only locks when secure lock is requested
ESP32-S3 burns¶
EFUSE_BLK_KEY3:otp_key_1, purposeUSEREFUSE_BLK_KEY4:otp_key_2, purposeUSER- write-disable for KEY3/KEY4 and their key-purpose fields
- one secure-boot digest key block, purpose
SECURE_BOOT_DIGEST0/1/2 ESP_EFUSE_SECURE_BOOT_EN- optional revoke bits for the non-selected secure-boot digests when secure lock is requested
- optional write-disable for the selected secure-boot digest block and purpose
- optional JTAG / USB-JTAG / USB-serial-JTAG disable bits when secure lock is requested
Conservative operating rule¶
Before any fuse-burning workflow:
- confirm the exact chip family
- confirm the exact firmware build and provisioning command
- test on non-critical hardware
- record the before and after state
If you cannot satisfy those four conditions, do not burn fuses.