Changes to ROM Specification

Comparing version 2.1 to 2.0
+458 additions -29 deletions
@@ -1,9 +1,9 @@
11 <div style="font-size: 0.85em; color: #656d76; margin-bottom: 1em; padding: 0.5em; background: #f6f8fa; border-radius: 4px;">
2-📄 Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/294bcacbd34aca6e23ce7b652885e3236f7e7afe/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>294bcac</code>
2+📄 Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/0a7d46b7cf8a9ccbfc195a8b148268df68d7b80f/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>0a7d46b</code>
33 </div>
44
55
6-# Caliptra - ROM Specification v2.0.2
6+# Caliptra - ROM Specification v2.1
77
88 *Spec Version: 1.0*
99
@@ -61,13 +61,13 @@
6161 | :------------------------------ | :------------ | :----------------------------------------------------- |
6262 | FUSE_UDS_SEED | 512 | Obfuscated UDS |
6363 | FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy |
64-| FUSE_VENDOR_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors |
64+| FUSE_VENDOR_PK_HASH | 384 | Hash of the ECC and LMS or MLDSA Manufacturer Public Key Descriptors. Stored as `[u32; 12]` — see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal). |
6565 | FUSE_ECC_REVOCATION | 4 | Manufacturer ECC Public Key Revocation Mask |
6666 | FUSE_LMS_REVOCATION | 32 | Manufacturer LMS Public Key Revocation Mask |
6767 | FUSE_MLDSA_REVOCATION | 4 | Manufacturer MLDSA Public Key Revocation Mask |
6868 | FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number |
6969 | FUSE_ANTI_ROLLBACK_DISABLE | 1 | Disable SVN checking for firmware when bit is set |
70-| FUSE_IDEVID_CERT_ATTR | 768 | FUSE containing information for generating IDEVID CSR <br> **Word 0:bits[0-2]**: ECDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 0:bits[3-5]**: MLDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 1,2,3,4,5**: ECDSA Subject Key Id <br> **Word 6,7,8,9,10**: MLDSA Subject Key Id <br> **Words 11**: UEID type as defined in [IETF RATS specification](https://www.ietf.org/archive/id/draft-ietf-rats-eat-21.html#section-4.2.1.1) <br> **Words 12,13,14,15**: Manufacturer Serial Number |
70+| FUSE_IDEVID_CERT_ATTR | 768 | FUSE containing information for generating IDEVID CSR <br> **Word 0:bits[0-2]**: ECDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 0:bits[3-5]**: MLDSA X509 Key Id Algorithm (3 bits) 0: SHA1, 1: SHA256, 2: SHA384, 3: SHA512, 4: Fuse <br> **Word 1,2,3,4,5**: ECDSA Subject Key Id <br> **Word 6,7,8,9,10**: MLDSA Subject Key Id <br> **Words 11**: UEID type as defined in the [IETF EAT specification](https://www.rfc-editor.org/rfc/rfc9711.html#section-4.2.1.1) <br> **Words 12,13,14,15**: Manufacturer Serial Number |
7171 | FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization |
7272 | FUSE_PQC_KEY_TYPE | 2 | One-hot encoded selection of PQC key type for firmware validation. <br> **Bit 0**: MLDSA <br> **Bit 1**: LMS |
7373
@@ -75,7 +75,7 @@
7575 ### Architectural Registers
7676 | Register | Width (bits) | Description |
7777 | :------------------------------ | :------------ | :----------------------------------------------------- |
78-| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash |
78+| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash. Stored as `[u32; 12]` — see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal). |
7979
8080
8181 ### Entropy Source Configuration Registers
@@ -159,7 +159,7 @@
159159 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
160160 | Reserved | 1 | Reserved |
161161 | Key Hash Count | 1 | Number of valid public key hashes |
162-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4 |
162+| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4. Each hash is stored in reversed-dword format (see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)). |
163163
164164
165165 #### PQC Manufacturer Public Key Descriptor
@@ -169,7 +169,7 @@
169169 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
170170 | Key Type | 1 | Type of the key in the descriptor <br> 0x1 - MLDSA <br> 0x3 - LMS |
171171 | Key Hash Count | 1 | Number of valid public key hashes |
172-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. LMS: n = 32, MLDSA: n = 4 |
172+| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. n = 32 for both LMS and MLDSA (the struct always allocates 32 slots; for MLDSA only the first 4 are populated and the rest are zero). Each hash is stored in reversed-dword format (see [Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)). |
173173
174174
175175 #### Header
@@ -707,24 +707,63 @@
707707
708708 #### Handling commands from mailbox
709709
710-ROM supports the following set of commands before handling the FW_DOWNLOAD command in PASSIVE mode (described in section 9.6) or RI_DOWNLOAD_FIRMWARE command in SUBSYSTEM mode. Once the FW_DOWNLOAD or RI_DOWNLOAD_FIRMWARE is issued, ROM stops processing any additional mailbox commands.
711-
712-1. **STASH_MEASUREMENT**: Up to eight measurements can be sent to the ROM for recording. Sending more than eight measurements will result in an FW_PROC_MAILBOX_STASH_MEASUREMENT_MAX_LIMIT fatal error. Format of a measurement is documented at [Stash Measurement command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#stash_measurement).
713-2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#version).
714-3. **SELF_TEST_START**: This command is used to invoke the FIPS Known-Answer-Tests (aka KAT) on demand. [Self Test Start command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#self_test_start).
715-4. **SELF_TEST_GET_RESULTS**: This command is used to check if a SELF_TEST command is in progress. [Self Test Get Results command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#self_test_get_results).
716-5. **SHUTDOWN**: This command is used clear the hardware crypto blocks including the keyvault. [Shutdown command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#shutdown).
717-6. **CAPABILITIES**: This command is used to query the ROM capabilities. Capabilities is a 128-bit value with individual bits indicating a specific capability. Currently, the only capability supported is ROM_BASE (bit 0). [Capabilities command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#capabilities).
718-7. **GET_IDEVID_CSR**: This command is used to fetch the IDevID CSR from ROM. [Fetch IDevIDCSR command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#get_idevid_csr).
719-8. **CM_DERIVE_STABLE_KEY**: This command is used to derive a stable key for Device Ownership Transfer or other flows. Note that in Caliptra 2.0 in subsystem mode, derived stable keys, their derivatives, and commands using them will be marked with a FIPS status of invalid since the UDS and FE cannot be completely zeroized. See [CM_DERIVE_STABLE_KEY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_derive_stable_key).
720-9. **CM_HMAC**: This command uses derived stable keys for Device Ownership Transfer or other flows. [CM_HMAC](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_hmac)
721-10. **ECDSA384_SIGNATURE_VERIFY**: This command verifies ECDSA384 signatures for Device Ownership Transfer or other flows. [ECDSA384_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#ecdsa384_signature_verify)
722-11. **MLDSA87_SIGNATURE_VERIFY**: This command verifies MLDSA87 signatures for Device Ownership Transfer or other flows. [MLDSA87_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#mldsa87_signature_verify)
723-12. **CM_RANDOM_GENERATE**: This command returns random numbers from Caliptra's RNG for Device Ownership Transfer or other flows. [CM_RANDOM_GENERATE](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#cm_random_generate)
710+ROM supports the following set of commands before handling the FW_DOWNLOAD command in PASSIVE mode (described in section 9.6) or RI_DOWNLOAD_FIRMWARE/RI_DOWNLOAD_ENCRYPTED_FIRMWARE command in SUBSYSTEM mode. Once the FW_DOWNLOAD, RI_DOWNLOAD_FIRMWARE, or RI_DOWNLOAD_ENCRYPTED_FIRMWARE is issued, ROM stops processing any additional mailbox commands.
711+
712+1. **STASH_MEASUREMENT**: Up to eight measurements can be sent to the ROM for recording. Sending more than eight measurements will result in an FW_PROC_MAILBOX_STASH_MEASUREMENT_MAX_LIMIT fatal error. Format of a measurement is documented at [Stash Measurement command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#stash_measurement).
713+2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#version).
714+3. **SELF_TEST_START**: This command is used to invoke the FIPS Known-Answer-Tests (aka KAT) on demand. [Self Test Start command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#self_test_start).
715+4. **SELF_TEST_GET_RESULTS**: This command is used to check if a SELF_TEST command is in progress. [Self Test Get Results command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#self_test_get_results).
716+5. **SHUTDOWN**: This command is used clear the hardware crypto blocks including the keyvault. [Shutdown command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#shutdown).
717+6. **CAPABILITIES**: This command is used to query the ROM capabilities. Capabilities is a 128-bit value with individual bits indicating a specific capability. Capabilities are documented in the [Capabilities command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#capabilities).
718+7. **GET_IDEVID_CSR**: This command is used to fetch the IDevID CSR from ROM. [Fetch IDevIDCSR command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#get_idevid_csr).
719+8. **CM_DERIVE_STABLE_KEY**: This command is used to derive a stable key for Device Ownership Transfer or other flows. [CM_DERIVE_STABLE_KEY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_derive_stable_key)
720+9. **CM_HMAC**: This command uses derived stable keys for Device Ownership Transfer or other flows. [CM_HMAC](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_hmac)
721+10. **ECDSA384_SIGNATURE_VERIFY**: This command verifies ECDSA384 signatures for Device Ownership Transfer or other flows. [ECDSA384_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#ecdsa384_signature_verify)
722+11. **MLDSA87_SIGNATURE_VERIFY**: This command verifies MLDSA87 signatures for Device Ownership Transfer or other flows. [MLDSA87_SIGNATURE_VERIFY](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#mldsa87_signature_verify)
723+12. **CM_RANDOM_GENERATE**: This command returns random numbers from Caliptra's RNG for Device Ownership Transfer or other flows. [CM_RANDOM_GENERATE](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#cm_random_generate)
724724 13. **CM_SHA**: This ROM-only command (ROM 2.0.1+ only) computes a SHA-384 or SHA-512 hash of input data in a single operation. This is useful for MCU ROM to verify signatures and hashes against Vendor PK hash without needing its own hash implementation. Unlike the runtime CM_SHA_INIT/CM_SHA_UPDATE/CM_SHA_FINAL commands, this is a one-shot operation that does not support streaming or contexts. See [CM_SHA](#cm_sha) below for details.
725-14. **GET_LDEV_ECC384_CERT**: This command fetches an LDevID ECC384 certificate signed by the ECC384 IDevID private key. [GET_LDEV_ECC384_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#get_ldev_ecc384_cert)
726-15. **GET_LDEV_MLDSA87_CERT**: This command fetches an LDevID MLDSA87 certificate signed by the MLDSA87 IDevID private key. [GET_LDEV_MLDSA87_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#get_ldev_mldsa87_cert)
727-16. **INSTALL_OWNER_PK_HASH**: This command saves the owner public key hash to persistent data. [INSTALL_OWNER_PK_HASH](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime#install_owner_pk_hash)
725+14. **GET_LDEV_ECC384_CERT**: This command fetches an LDevID ECC384 certificate signed by the ECC384 IDevID private key. [GET_LDEV_ECC384_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#get_ldev_ecc384_cert)
726+15. **GET_LDEV_MLDSA87_CERT**: This command fetches an LDevID MLDSA87 certificate signed by the MLDSA87 IDevID private key. [GET_LDEV_MLDSA87_CERT](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#get_ldev_mldsa87_cert)
727+16. **INSTALL_OWNER_PK_HASH**: This command saves the owner public key hash to persistent data. [INSTALL_OWNER_PK_HASH](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime#install_owner_pk_hash)
728+17. **OCP_LOCK_REPORT_HEK_METADATA**: This command allows the MCU to report HEK seed state and metadata to the ROM, which determines if the HEK is available. See the [OCP LOCK specification](https://github.com/chipsalliance/Caliptra/blob/main/doc/ocp_lock/releases/OCP_LOCK_Specification_v1.0_RC2.pdf) for details.
729+18. **ZEROIZE_UDS_FE**
730+
731+Zeroizes (sets to 0xFFFFFFFF) the UDS (Unique Device Secret) and/or FE (Field Entropy) partitions in the OTP fuse controller. This command is typically used during device decommissioning or ownership transfer flows.
732+
733+The command accepts a flags field where each bit controls a specific partition. Multiple partitions can be zeroized in a single command by setting multiple flag bits.
734+
735+The zeroization process follows these steps for each partition:
736+1. Clears the zeroization marker first to mask potential ECC errors during power failures
737+2. Zeroizes the seed data
738+3. Clears the partition digest
739+
740+All operations are verified to return 0xFFFFFFFF before proceeding.
741+
742+Command Code: `0x5A45_5546` ("ZEUF")
743+
744+*Table: `ZEROIZE_UDS_FE` input arguments*
745+
746+| **Name** | **Type** | **Description**
747+| -------- | -------- | ---------------
748+| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
749+| flags | u32 | Partition flags. See ZEROIZE_UDS_FE_FLAGS below.
750+
751+*Table: `ZEROIZE_UDS_FE_FLAGS` input flags*
752+
753+| **Name** | **Value** | **Description**
754+| ------------------ | --------- | ---------------
755+| ZEROIZE_UDS_FLAG | 1 << 0 | Zeroize UDS partition
756+| ZEROIZE_FE0_FLAG | 1 << 1 | Zeroize FE partition 0
757+| ZEROIZE_FE1_FLAG | 1 << 2 | Zeroize FE partition 1
758+| ZEROIZE_FE2_FLAG | 1 << 3 | Zeroize FE partition 2
759+| ZEROIZE_FE3_FLAG | 1 << 4 | Zeroize FE partition 3
760+
761+*Table: `ZEROIZE_UDS_FE` output arguments*
762+
763+| **Name** | **Type** | **Description**
764+| -------- | -------- | ---------------
765+| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian.
766+| dpe_result | u32 | Result code, 0 on success.
728767
729768 #### CM_SHA
730769
@@ -740,7 +779,7 @@
740779 | -------------- | ------------- | ---------------
741780 | chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
742781 | hash_algorithm | u32 | Hash algorithm: 1 = SHA-384, 2 = SHA-512. Value 0 is reserved and will return an error.
743-| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead).
782+| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead) in passive mode, and 16,372 bytes in subsystem mode for 2.1 (16 KB minus overhead).
744783 | input | u8[input_size]| Input data to hash. Variable size up to the mailbox capacity.
745784
746785 *Table: `CM_SHA` output arguments*
@@ -769,7 +808,11 @@
769808
770809 Following is the sequence of steps that are performed to download the firmware image into the mailbox in SUBSYSTEM mode.
771810
772-1. On receiving the RI_DOWNLOAD_FIRMWARE mailbox command, set the RI PROT_CAP2 register version to 1.1 and the `Agent Capability` field bits:
811+ROM supports two commands for firmware download in SUBSYSTEM mode:
812+- **RI_DOWNLOAD_FIRMWARE** (Command Code: `0x5249_4644` / "RIFD"): Standard firmware download. After downloading and validating the firmware, the runtime will activate the MCU firmware immediately.
813+- **RI_DOWNLOAD_ENCRYPTED_FIRMWARE** (Command Code: `0x5249_4645` / "RIFE"): Encrypted firmware download. Sets the boot mode to `EncryptedFirmware`, which signals to the runtime that the MCU firmware is encrypted and should not be activated until it has been decrypted using the `CM_AES_GCM_DECRYPT_DMA` command.
814+
815+1. On receiving the RI_DOWNLOAD_FIRMWARE or RI_DOWNLOAD_ENCRYPTED_FIRMWARE mailbox command, set the RI PROT_CAP2 register version to 1.1 and the `Agent Capability` field bits:
773816 - `Device ID`
774817 - `Device Status`
775818 - `Push C-image support`
@@ -1023,7 +1066,7 @@
10231066 - **ICCM**
10241067
10251068 ### Launch FMC
1026-The ROM initializes and populates the Firmware Handoff Table (FHT) to relay essential parameters to the FMC. The format of the FHT is documented [here](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/fmc/README.md#firmware-handoff-table). Upon successful population, the ROM transfers execution control to the FMC.
1069+The ROM initializes and populates the Firmware Handoff Table (FHT) to relay essential parameters to the FMC. The format of the FHT is documented [here](https://github.com/chipsalliance/caliptra-sw/blob/main/fmc/README.md#firmware-handoff-table). Upon successful population, the ROM transfers execution control to the FMC.
10271070
10281071 ## Warm reset flow
10291072 ROM does not perform any DICE derivations or firmware validation during warm reset.
@@ -1136,11 +1179,12 @@
11361179 ### Preamble validation: Manufacturing key validation
11371180
11381181 - fuse_ecc_revocation serves as the bitmask for revoking ECC keys.
1139- - If bit-n is set, the nth key is disabled. All other higher bits that are zeros indicate the keys are still enabled.
1182+ - If bit-n is set, the nth key is disabled. All other bits that are zeros indicate the keys are still enabled.
11401183 - If all the bits are zeros, all ECC keys remain enabled.
11411184 - Ensure that the Active Key Index in the preamble is not disabled by the fuse_ecc_revocation fuse.
11421185 - If the key is disabled, the validation process fails.
1143-- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation.
1186+ - **Note: The last key index is never revoked, regardless of the fuse value.**
1187+- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation. The last key index for PQC keys is also never revoked.
11441188
11451189 ### Preamble validation: Validate the Owner key
11461190
@@ -1149,6 +1193,391 @@
11491193 - The validation process for owner public keys involves generating a SHA2-384 hash from the owner public keys within the preamble and comparing it to the hash stored in the fuse_owner_pk_hash register.
11501194 - If the computed hash matches the value in fuse_owner_pk_hash, the owner public keys are deemed valid.
11511195 - If there is a hash mismatch, the image validation process fails.
1196+
1197+### Public key hash byte ordering (dword reversal)
1198+
1199+**Important:** Hashes and ECC key coordinates stored in the firmware manifest and fuse registers use
1200+a **reversed-dword format** rather than the standard byte order defined by the SHA specification.
1201+
1202+In standard byte order, a SHA2-384 hash is a sequence of 48 bytes exactly as output by tools like
1203+OpenSSL or Python's `hashlib`. In reversed-dword format, the same 48 bytes are grouped into 12
1204+four-byte words (dwords) and the bytes within each dword are reversed.
1205+
1206+For example, if the standard SHA2-384 hash begins with `b1 7c a8 77 66 66 57 cc d1 00 e6 92 ...`:
1207+
1208+| Standard byte order | → | Reversed-dword format |
1209+| ---------------------- | --- | ----------------------- |
1210+| `b1 7c a8 77` || `77 a8 7c b1` |
1211+| `66 66 57 cc` || `cc 57 66 66` |
1212+| `d1 00 e6 92` || `92 e6 00 d1` |
1213+| ... || ... |
1214+
1215+
1216+This reversed-dword format applies to:
1217+- **Individual public key hashes** in the ECC and PQC key descriptors within the preamble
1218+- **FUSE_VENDOR_PK_HASH** and **CPTRA_OWNER_PK_HASH** fuse/register values (which are `[u32; 12]` arrays)
1219+- **ECC public key coordinates** (X and Y), which are stored as `[u32; 12]` arrays in the preamble
1220+
1221+Note: LMS public key fields (`tree_type`, `otstype`, `id`, `digest`) follow the LMS specification
1222+encoding and are **not** subject to dword reversal. MLDSA public keys are stored as raw byte arrays
1223+and are also **not** subject to dword reversal.
1224+
1225+### Computing public key hashes: step-by-step example
1226+
1227+The following example walks through the computation of the **vendor PK descriptor hash**
1228+using the test public keys from `image/fake-keys/src/lib.rs` with PQC key type **LMS (type 3)**.
1229+
1230+#### Step 1: Hash each vendor ECC public key
1231+
1232+Each ECC-384 public key has X and Y coordinates, each stored as `[u32; 12]`. To hash a key,
1233+serialize the struct to 96 bytes by writing each `u32` word in reversed-dword format, then
1234+compute SHA2-384 of those 96 bytes.
1235+
1236+**ECC Key 0:**
1237+```
1238+X (standard byte order): c69fe67f 97ea3e42 21a7a603 6c2e070d 1657327b c3f1e7c1
1239+ 8dccb9e4 ffda5c3f 4db0a1c0 567e0973 17bf4484 39696a07
1240+Y (standard byte order): c126b913 5fc82572 8f1cd403 19109430 994fe3e8 74a8b026
1241+ be14794d 27789964 7735fde8 328afd84 cd4d4aa8 72d40b42
1242+
1243+X (reversed-dword): 7fe69fc6 423eea97 03a6a721 0d072e6c 7b325716 c1e7f1c3
1244+ e4b9cc8d 3f5cdaff c0a1b04d 73097e56 8444bf17 076a6939
1245+Y (reversed-dword): 13b926c1 7225c85f 03d41c8f 30941019 e8e34f99 26b0a874
1246+ 4d7914be 64997827 e8fd3577 84fd8a32 a84a4dcd 420bd472
1247+
1248+Input to SHA384 = X_reversed || Y_reversed (96 bytes)
1249+SHA384 (standard): 84facd34 227de869 1fbb7d33 49306e0f 250a3659 53a6cc6b
1250+ 629d4616 32f73cfd 768152bb 8a03a255 5a1b1f1f c3923faa
1251+SHA384 (reversed-dword): 34cdfa84 69e87d22 337dbb1f 0f6e3049 59360a25 6bcca653
1252+ 16469d62 fd3cf732 bb528176 55a2038a 1f1f1b5a aa3f92c3
1253+```
1254+
1255+**ECC Key 1:**
1256+```
1257+X (standard): a6309750 f0a05ddb 956a7f86 2812ec4f ec454e95 3b53dbfb
1258+ 9eb54140 15ea7507 084af93c b7fa33fe 51811ad5 e754232e
1259+Y (standard): ef5a5987 7a0ce0be 2621d2a9 8bf3c5df af7b3d6d 97f24183
1260+ a4a42038 58c39b86 272ef548 e572b937 1ecf1994 1b8d4ea7
1261+
1262+SHA384 (standard): fe89195f 7fab8ebb 2818d935 837493c2 378525ef 686ed220
1263+ 09b9a399 f23f1f42 2f5ae1f3 ba1c3083 1a68a456 9c01fc96
1264+SHA384 (reversed-dword): 5f1989fe bb8eab7f 35d91828 c2937483 ef258537 20d26e68
1265+ 99a3b909 421f3ff2 f3e15a2f 83301cba 56a4681a 96fc019c
1266+```
1267+
1268+**ECC Key 2:**
1269+```
1270+X (standard): a0d25693 c4251e48 185615b0 a6c27f6d e62c39f5 a9a32f75
1271+ 9553226a 4d1926c1 7928910f b7adc1b6 89996733 10134881
1272+Y (standard): bbdf72d7 07c08100 d54fcdad b1567bb0 0522762b 76b8dc4a
1273+ 846c175a 3fbd0501 9bdc8118 4be5f33c bb21b41d 93a8c523
1274+
1275+SHA384 (standard): f397ba45 b5801ddf b732078d ffdf792f b584a73f b055acaf
1276+ ef39f31d 5b88c7d5 2753a45a 0c76b098 90d8e335 7be87f26
1277+SHA384 (reversed-dword): 45ba97f3 df1d80b5 8d0732b7 2f79dfff 3fa784b5 afac55b0
1278+ 1df339ef d5c7885b 5aa45327 98b0760c 35e3d890 267fe87b
1279+```
1280+
1281+**ECC Key 3:**
1282+```
1283+X (standard): 002a82b6 8e03e9a0 fd3b4c14 ca2cb3e8 14350a71 0e43956d
1284+ 21694fb4 f34485e8 f0e33583 f7ea142d 50e16f8b 0225bb95
1285+Y (standard): 5802641c 7c45a4a2 408e03a6 a4100a92 50fcc468 d238cd0d
1286+ 449cc3e5 1abc25e7 0b05c426 843dcd6f 944ef6ff fa53ec5b
1287+
1288+SHA384 (standard): 8ba8acb6 b98da9dc 8ffce0bc eba86454 4acbbd6e 3f31466e
1289+ 5d532565 0bfc9e3b c8afb2b5 c33e20f5 06992143 83f33bc1
1290+SHA384 (reversed-dword): b6aca88b dca98db9 bce0fc8f 5464a8eb 6ebdcb4a 6e46313f
1291+ 6525535d 3b9efc0b b5b2afc8 f5203ec3 43219906 c13bf383
1292+```
1293+
1294+#### Step 2: Hash each vendor LMS public key
1295+
1296+Each LMS public key is a 48-byte struct: `tree_type` (u32), `otstype` (u32), `id` (16 bytes),
1297+`digest` (24 bytes). The binary serialization is hashed directly.
1298+
1299+**LMS Key 0:**
1300+```
1301+tree_type=0x0000000c, otstype=0x00000007
1302+id: 4908a17b cadb1829 1e289058 d5a8e3e8
1303+digest: 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1304+
1305+Serialized (48 bytes): 0000000c 00000007 4908a17b cadb1829 1e289058 d5a8e3e8
1306+ 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1307+SHA384 (standard): fc2c1b6f 56f732d1 fd876f3f ef757cbb a2b1c64b cc148298
1308+ d7508262 4bdf27cb 23d6b5b6 7169c46f 50b7fc19 92068fec
1309+SHA384 (reversed-dword): 6f1b2cfc d132f756 3f6f87fd bb7c75ef 4bc6b1a2 988214cc
1310+ 628250d7 cb27df4b b6b5d623 6fc46971 19fcb750 ec8f0692
1311+```
1312+
1313+**LMS Key 1:**
1314+```
1315+tree_type=0x0000000c, otstype=0x00000007
1316+id: 7cb5369d 64e4281d 046e977c 70d4d0a3
1317+digest: 8ea4701d adf7d700 0564b7d6 1d1c9587 9dd6475c 9c3aae0b
1318+
1319+SHA384 (standard): 7b5811fd 8d2b0cf8 9851f12d d2a7c239 f4f3abc5 d928dcc0
1320+ 3b4b891d abbdc67f c7b88436 432e1544 a408bc9c bb503f6b
1321+SHA384 (reversed-dword): fd11587b f80c2b8d 2df15198 39c2a7d2 c5abf3f4 c0dc28d9
1322+ 1d894b3b 7fc6bdab 3684b8c7 44152e43 9cbc08a4 6b3f50bb
1323+```
1324+
1325+**LMS Key 2:**
1326+```
1327+tree_type=0x0000000c, otstype=0x00000007
1328+id: 2bbb4b72 c5b41e05 d2fabe76 f41704bd
1329+digest: dcb53f96 24d4c7b3 c9ae4d4c 0e41e08e 3b159396 0fe6a277
1330+
1331+SHA384 (standard): 7e08a494 6933d35a 42c0d7b0 0236b10b db14c100 3f82f6a9
1332+ 7d401cb8 e420a7fa 5aab12b3 c4e96bec 49aec770 225a8f88
1333+SHA384 (reversed-dword): 94a4087e 5ad33369 b0d7c042 0bb13602 00c114db a9f6823f
1334+ b81c407d faa720e4 b312ab5a ec6be9c4 70c7ae49 888f5a22
1335+```
1336+
1337+**LMS Key 3:**
1338+```
1339+tree_type=0x0000000c, otstype=0x00000007
1340+id: 42cba2e5 575b5235 7ea7aead ef54074c
1341+digest: 5aa60e27 69251599 3ae8e21f 27ccdded 8ffcd3d2 8efbdec2
1342+
1343+SHA384 (standard): d3734fbc ee2893a3 b1b6519b 6ec78fb8 d7425327 cde1f7aa
1344+ 23012c64 c635219f d4ab1c4d 1b023252 00042884 2e463dbb
1345+SHA384 (reversed-dword): bc4f73d3 a39328ee 9b51b6b1 b88fc76e 275342d7 aaf7e1cd
1346+ 642c0123 9f2135c6 4d1cabd4 5232021b 84280400 bb3d462e
1347+```
1348+
1349+#### Step 3: Build the ECC key descriptor (196 bytes)
1350+
1351+Concatenate the 4-byte header with the 4 key hashes (each in reversed-dword format):
1352+
1353+```
1354+Header (4 bytes): 01 00 00 04 (version=1, reserved=0, key_hash_count=4)
1355+ECC key 0 hash (48 bytes, reversed-dword): 34cdfa84 69e87d22 ... aa3f92c3
1356+ECC key 1 hash (48 bytes, reversed-dword): 5f1989fe bb8eab7f ... 96fc019c
1357+ECC key 2 hash (48 bytes, reversed-dword): 45ba97f3 df1d80b5 ... 267fe87b
1358+ECC key 3 hash (48 bytes, reversed-dword): b6aca88b dca98db9 ... c13bf383
1359+
1360+Total: 4 + (4 × 48) = 196 bytes
1361+```
1362+
1363+#### Step 4: Build the PQC (LMS) key descriptor (1540 bytes)
1364+
1365+```
1366+Header (4 bytes): 01 00 03 20 (version=1, key_type=3=LMS, key_hash_count=32)
1367+LMS key 0 hash (48 bytes, reversed-dword): 6f1b2cfc d132f756 ... ec8f0692
1368+LMS key 1 hash (48 bytes, reversed-dword): fd11587b f80c2b8d ... 6b3f50bb
1369+LMS key 2 hash (48 bytes, reversed-dword): 94a4087e 5ad33369 ... 888f5a22
1370+LMS key 3 hash (48 bytes, reversed-dword): bc4f73d3 a39328ee ... bb3d462e
1371+ ... (keys 0-3 repeated 8 times to fill all 32 slots)
1372+
1373+Total: 4 + (32 × 48) = 1540 bytes
1374+```
1375+
1376+#### Step 5: Compute the vendor PK descriptor hash
1377+
1378+```
1379+Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes
1380+
1381+SHA384 (standard byte order):
1382+ b17ca877 666657cc d100e692 6c7206b6 0c995cb6 8992c6c9
1383+ baefce72 8af05441 dee1ff41 5adfc187 e1e4edb4 d3b2d909
1384+
1385+As [u32; 12] fuse register value:
1386+ [0xb17ca877, 0x666657cc, 0xd100e692, 0x6c7206b6,
1387+ 0x0c995cb6, 0x8992c6c9, 0xbaefce72, 0x8af05441,
1388+ 0xdee1ff41, 0x5adfc187, 0xe1e4edb4, 0xd3b2d909]
1389+```
1390+
1391+#### Owner PK hash
1392+
1393+The owner PK hash is SHA2-384 over the serialized `ImageOwnerPubKeys` struct, which contains:
1394+- `ecc_pub_key`: `{ x: [u32; 12], y: [u32; 12] }` — 96 bytes (in reversed-dword format)
1395+- `pqc_pub_key`: raw byte array of 2592 bytes (for LMS, only the first 48 bytes are meaningful;
1396+ the rest are zero-padded)
1397+
1398+Total: 2688 bytes. The SHA2-384 of these bytes is the owner PK hash.
1399+
1400+#### Summary of expected hash values using test keys
1401+
1402+Using the test keys from `image/fake-keys/src/lib.rs`:
1403+
1404+| Hash | PQC Type | Standard byte order (hex) |
1405+| ------ | ---------- | --------------------------- |
1406+| Vendor PK descriptor hash | LMS (type 3) | `b17ca877666657ccd100e6926c7206b60c995cb68992c6c9baefce728af05441dee1ff415adfc187e1e4edb4d3b2d909` |
1407+| Vendor PK descriptor hash | MLDSA (type 1) | `30399676a17e3e973677b3ff862f4bf2d1932d884778453c376fe00dc93fb8aa0770f3ebf3411a0853e9c57ece8a2980` |
1408+| Owner PK hash | LMS (type 3) | `1b179390e4e6c44422ed553e256c7d675cd93190cb49d88d485aa4ef3906cd492ab3ee3d3ba5f2c990ad13390fed4de5` |
1409+| Owner PK hash | MLDSA (type 1) | `48afdb073c5e0d4ee46490468ef81f2cf57249b6e76a28f5fca4de696a7d3e2ed3efc4e6774318543e95307a54988bd7` |
1410+
1411+
1412+To convert any of these standard byte order hashes to the `[u32; 12]` fuse register format, group
1413+the hex string into 8-character (4-byte) chunks and interpret each as a 32-bit word:
1414+- `b17ca877666657cc...` → `[0xb17ca877, 0x666657cc, 0xd100e692, ...]`
1415+
1416+#### Python script to compute vendor and owner PK hashes
1417+
1418+The following Python script computes the vendor PK descriptor hash and owner PK hash from
1419+ECC PEM files and LMS or MLDSA binary key files:
1420+
1421+```python
1422+#!/usr/bin/env python3
1423+"""
1424+Compute the Caliptra vendor PK descriptor hash and owner PK hash
1425+from ECC (.pem) and LMS/MLDSA (.bin) public key files.
1426+
1427+Usage:
1428+ python3 compute_pk_hashes.py --pqc-key-type <1|3> \\
1429+ --vendor-ecc-pub-keys key0.pem key1.pem key2.pem key3.pem \\
1430+ --vendor-pqc-pub-keys pqc0.bin pqc1.bin ... \\
1431+ --owner-ecc-pub-key owner.pem \\
1432+ --owner-pqc-pub-key owner_pqc.bin
1433+
1434+PQC key type: 1 = MLDSA, 3 = LMS
1435+
1436+ECC public keys are PEM files (P-384).
1437+LMS public keys are 48-byte binary files (tree_type, otstype, id, digest).
1438+MLDSA public keys are 2592-byte binary files.
1439+"""
1440+import argparse
1441+import hashlib
1442+import struct
1443+import sys
1444+
1445+from cryptography.hazmat.primitives.serialization import load_pem_public_key
1446+
1447+# Sizes
1448+ECC_PUB_KEY_BYTES = 96 # 2 x 48-byte coordinates
1449+PQC_PUB_KEY_SLOT_BYTES = 2592 # MLDSA key size; LMS keys are 48 bytes, zero-padded
1450+LMS_PUB_KEY_BYTES = 48
1451+MLDSA_PUB_KEY_BYTES = 2592
1452+HASH_BYTES = 48 # SHA2-384
1453+
1454+VENDOR_ECC_MAX_KEYS = 4
1455+VENDOR_LMS_MAX_KEYS = 32
1456+VENDOR_MLDSA_MAX_KEYS = 32 # struct always allocates 32 slots; only first 4 are populated
1457+KEY_DESCRIPTOR_VERSION = 1
1458+
1459+
1460+def ecc_pub_key_to_reversed_dwords(pem_path: str) -> bytes:
1461+ """Read an ECC P-384 PEM public key and return 96 bytes in reversed-dword format."""
1462+ with open(pem_path, 'rb') as f:
1463+ pub_key = load_pem_public_key(f.read())
1464+ nums = pub_key.public_numbers()
1465+ x_bytes = nums.x.to_bytes(48, 'big')
1466+ y_bytes = nums.y.to_bytes(48, 'big')
1467+ return to_reversed_dwords(x_bytes) + to_reversed_dwords(y_bytes)
1468+
1469+
1470+def to_reversed_dwords(standard_bytes: bytes) -> bytes:
1471+ """Convert bytes from standard byte order to reversed-dword format.
1472+
1473+ Groups the input into 4-byte dwords and reverses the bytes within each dword.
1474+ """
1475+ assert len(standard_bytes) % 4 == 0
1476+ result = bytearray()
1477+ for i in range(0, len(standard_bytes), 4):
1478+ result.extend(standard_bytes[i:i+4][::-1])
1479+ return bytes(result)
1480+
1481+
1482+def sha384_reversed_dwords(data: bytes) -> bytes:
1483+ """Compute SHA2-384 and return the hash in reversed-dword format."""
1484+ h = hashlib.sha384(data).digest()
1485+ return to_reversed_dwords(h)
1486+
1487+
1488+def build_ecc_key_descriptor(ecc_pem_paths: list) -> bytes:
1489+ """Build the ECC key descriptor: header + key hashes."""
1490+ n = len(ecc_pem_paths)
1491+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, 0, n)
1492+ hashes = b''
1493+ for path in ecc_pem_paths:
1494+ key_bytes = ecc_pub_key_to_reversed_dwords(path)
1495+ hashes += sha384_reversed_dwords(key_bytes)
1496+ # Pad to VENDOR_ECC_MAX_KEYS slots
1497+ hashes += b'\x00' * (HASH_BYTES * (VENDOR_ECC_MAX_KEYS - n))
1498+ return header + hashes
1499+
1500+
1501+def build_pqc_key_descriptor(pqc_bin_paths: list, pqc_key_type: int) -> bytes:
1502+ """Build the PQC key descriptor: header + key hashes."""
1503+ n = len(pqc_bin_paths)
1504+ max_keys = VENDOR_LMS_MAX_KEYS if pqc_key_type == 3 else VENDOR_MLDSA_MAX_KEYS
1505+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, pqc_key_type, n)
1506+ hashes = b''
1507+ for path in pqc_bin_paths:
1508+ with open(path, 'rb') as f:
1509+ key_bytes = f.read()
1510+ hashes += sha384_reversed_dwords(key_bytes)
1511+ # Pad to max slots
1512+ hashes += b'\x00' * (HASH_BYTES * (max_keys - n))
1513+ return header + hashes
1514+
1515+
1516+def build_owner_pub_keys(ecc_pem_path: str, pqc_bin_path: str) -> bytes:
1517+ """Build the serialized ImageOwnerPubKeys struct."""
1518+ ecc_bytes = ecc_pub_key_to_reversed_dwords(ecc_pem_path)
1519+ with open(pqc_bin_path, 'rb') as f:
1520+ pqc_bytes = f.read()
1521+ # Pad PQC key to full slot size
1522+ pqc_padded = pqc_bytes + b'\x00' * (PQC_PUB_KEY_SLOT_BYTES - len(pqc_bytes))
1523+ return ecc_bytes + pqc_padded
1524+
1525+
1526+def hash_to_fuse_words(standard_hash: bytes) -> list:
1527+ """Convert a standard byte order hash to [u32; 12] fuse word format."""
1528+ return [int.from_bytes(standard_hash[i:i+4], 'big') for i in range(0, 48, 4)]
1529+
1530+
1531+def main():
1532+ parser = argparse.ArgumentParser(
1533+ description='Compute Caliptra vendor PK descriptor hash and owner PK hash')
1534+ parser.add_argument('--pqc-key-type', type=int, required=True, choices=[1, 3],
1535+ help='PQC key type: 1=MLDSA, 3=LMS')
1536+ parser.add_argument('--vendor-ecc-pub-keys', nargs='+', required=True,
1537+ help='Vendor ECC P-384 public key PEM files')
1538+ parser.add_argument('--vendor-pqc-pub-keys', nargs='+', required=True,
1539+ help='Vendor PQC (LMS .bin or MLDSA .bin) public key files')
1540+ parser.add_argument('--owner-ecc-pub-key',
1541+ help='Owner ECC P-384 public key PEM file')
1542+ parser.add_argument('--owner-pqc-pub-key',
1543+ help='Owner PQC (LMS .bin or MLDSA .bin) public key file')
1544+ args = parser.parse_args()
1545+
1546+ pqc_name = {1: 'MLDSA', 3: 'LMS'}[args.pqc_key_type]
1547+
1548+ # Build descriptors
1549+ ecc_desc = build_ecc_key_descriptor(args.vendor_ecc_pub_keys)
1550+ pqc_desc = build_pqc_key_descriptor(args.vendor_pqc_pub_keys, args.pqc_key_type)
1551+ vendor_pub_key_info = ecc_desc + pqc_desc
1552+
1553+ # Vendor PK descriptor hash (standard byte order)
1554+ vendor_hash = hashlib.sha384(vendor_pub_key_info).digest()
1555+ vendor_hex = vendor_hash.hex()
1556+ vendor_words = hash_to_fuse_words(vendor_hash)
1557+
1558+ print(f"PQC key type: {args.pqc_key_type} ({pqc_name})")
1559+ print()
1560+ print(f"Vendor PK descriptor hash (standard byte order):")
1561+ print(f" {vendor_hex}")
1562+ print(f"Vendor PK descriptor hash (fuse [u32; 12]):")
1563+ print(f" {['0x{:08x}'.format(w) for w in vendor_words]}")
1564+
1565+ if args.owner_ecc_pub_key and args.owner_pqc_pub_key:
1566+ owner_bytes = build_owner_pub_keys(args.owner_ecc_pub_key, args.owner_pqc_pub_key)
1567+ owner_hash = hashlib.sha384(owner_bytes).digest()
1568+ owner_hex = owner_hash.hex()
1569+ owner_words = hash_to_fuse_words(owner_hash)
1570+
1571+ print()
1572+ print(f"Owner PK hash (standard byte order):")
1573+ print(f" {owner_hex}")
1574+ print(f"Owner PK hash (fuse [u32; 12]):")
1575+ print(f" {['0x{:08x}'.format(w) for w in owner_words]}")
1576+
1577+
1578+if __name__ == '__main__':
1579+ main()
1580+```
11521581
11531582 ## Preamble validation steps
11541583