Changes to ROM Specification

Comparing version 2.1 to 2.0
+970 additions -43 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/8f13b41132ddd85c3c2fa2c8ce5a766758b20fd8/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>8f13b41</code>
2+πŸ“„ Source: <a href="https://github.com/chipsalliance/caliptra-sw/blob/6980f7d09b351299e61d7242ac7a8c3c4e8cb695/rom/dev/README.md" target="_blank">chipsalliance/caliptra-sw/rom/dev/README.md</a> @ <code>6980f7d</code>
33 </div>
44
55
6-# Caliptra - ROM Specification v2.0.3
6+# Caliptra - ROM Specification v2.1
77
88 *Spec Version: 1.0*
99
@@ -59,23 +59,28 @@
5959 ### Fuse Registers
6060 | Register | Width (bits) | Description |
6161 | :------------------------------ | :------------ | :----------------------------------------------------- |
62-| FUSE_UDS_SEED | 512 | Obfuscated UDS |
63-| 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 |
62+| FUSE_UDS_SEED | 512 | Obfuscated UDS. Stored as `[u32; 16]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
63+| FUSE_FIELD_ENTROPY | 256 | Obfuscated Field Entropy. Stored as `[u32; 8]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
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 |
68-| FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number |
68+| FUSE_FIRMWARE_SVN | 128 | Firmware Security Version Number. 128-bit bitmap β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
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 |
71-| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization |
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 |
71+| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | 512 | SHA-512 digest of secret value for manufacturing debug unlock authorization. Stored as `[u32; 16]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
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 |
73+| FUSE_HEK_SEED | 256 | OCP HEK Seed. Stored as `[u32; 8]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
74+| FUSE_SOC_MANIFEST_SVN | 128 | SoC Manifest Security Version Number. 128-bit bitmap β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
75+| FUSE_SOC_MANIFEST_MAX_SVN | 8 | Maximum SoC Manifest Security Version Number |
76+| FUSE_SOC_STEPPING_ID | 16 | SoC Stepping Identifier |
77+| FUSE_IDEVID_MANUF_HSM_ID | 128 | Manufacturer HSM Identifier. Stored as `[u32; 4]` β€” see [Fuse value byte ordering](#fuse-value-byte-ordering). |
7378
7479
7580 ### Architectural Registers
7681 | Register | Width (bits) | Description |
7782 | :------------------------------ | :------------ | :----------------------------------------------------- |
78-| CPTRA_OWNER_PK_HASH | 384 | Owner ECC and LMS or MLDSA Public Key Hash |
83+| 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). |
7984
8085
8186 ### Subsystem Generic Strap Allocation
@@ -106,21 +111,44 @@
106111
107112 | Register | Field/Bits | Description |
108113 | :------------------------------- | :------------ | :------------------------------------------------------ |
109-| SS_STRAP_GENERIC[2] | [15:0] | Health test window size for FIPS mode (default: 512). This is the window size for all health tests when entropy is tested in FIPS mode. In single-bit mode entropy_src internally tests four times this many samples on the selected lane. |
114+| SS_STRAP_GENERIC[2] | [15:0] | Health test window size for FIPS mode (default: 1024). This is the window size for all health tests when entropy is tested in FIPS mode. In single-bit mode entropy_src internally tests four times this many samples on the selected lane. |
110115 | SS_STRAP_GENERIC[2] | [16] | Entropy source single-bit mode. When set to 1, ROM enables `rng_bit_enable` and clears `threshold_scope`. |
111116 | SS_STRAP_GENERIC[2] | [18:17] | Entropy source single-bit mode `rng_bit_sel`. Selects which RNG bit stream to process when single-bit mode is enabled. |
112117 | SS_STRAP_GENERIC[2] | [31] | Entropy bypass mode. When set to 1, enables bypass mode (`es_type`) to allow entropy characterization directly without passing through conditioning. |
113-| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [15:0] | Adaptive Proportion test high threshold (default: 1536). The test fails if any window has more than this threshold of 1's. |
114-| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [31:16] | Adaptive Proportion test low threshold (default: 512). The test fails if any window has less than this threshold of 1's. |
118+| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [15:0] | Adaptive Proportion test high threshold (default: 75% of the FIPS window, i.e. 768 for the default window). The test fails if any window has more than this threshold of 1's. |
119+| CPTRA_I_TRNG_ENTROPY_CONFIG_0 | [31:16] | Adaptive Proportion test low threshold (default: 25% of the FIPS window, i.e. 256 for the default window). The test fails if any window has less than this threshold of 1's. |
115120 | CPTRA_I_TRNG_ENTROPY_CONFIG_1 | [15:0] | Repetition Count test threshold (default: 41). The test fails if an RNG wire repeats the same bit this many times in a row. |
116121 | CPTRA_I_TRNG_ENTROPY_CONFIG_1 | [31:16] | Alert threshold (default: 2). Number of health check failures before an alert is triggered. |
117122
118123
119124 **Notes:**
120125 - If any threshold value is set to 0, the ROM uses the default value specified above.
126+- The Adaptive Proportion default thresholds are derived from the FIPS window (75% high, 25% low). In single-bit mode entropy_src scales the health-test window by four, so when the default window is used the ROM scales these defaults to match (high: 3072, low: 1024). An explicit threshold or window supplied by the SoC is used as-is.
121127 - These configuration values are stored in persistent storage after first read to prevent malicious modification (reloaded on cold reset).
122128 - In debug mode (`debug_locked == false`), entropy source configuration registers remain unlocked for characterization.
123129 - In production mode, ROM locks the entropy source configuration after programming to prevent modification.
130+
131+### Stable Owner Key Root Derivation
132+
133+The Stable Owner Key feature is only available in subsystem mode when OCP LOCK is disabled and the following subsystem strap is set:
134+
135+| Register | Field/Bits | Description |
136+| :------------------------------- | :--------- | :------------------------------------------------------ |
137+| SS_STRAP_GENERIC[3] | [0] | Stable Owner Key enable. When set to 1, ROM derives the Stable Owner Root Key from the HEK seed and allows `CM_DERIVE_STABLE_KEY` with `key_type = OwnerKey` when the other availability requirements are met. When clear, Stable Owner Key derivation is disabled. |
138+
139+
140+When the feature is available, ROM derives the Stable Owner Root Key during the IDevID stage before clearing DOE secrets:
141+
142+1. DOE decrypts the obfuscated HEK seed into `KEY_ID_HEK_SEED` (`KeyId14`) with HMAC block usage.
143+2. HKDF-Extract uses HMAC-SHA512 with salt `stable_owner_root_key`, zero-padded to 64 bytes, and reads `KEY_ID_HEK_SEED` as HMAC block data. The resulting PRK overwrites `KEY_ID_HEK_SEED` with HMAC key usage.
144+3. HKDF-Expand uses HMAC-SHA512 with the PRK and label `stable_owner_root_key` to populate `KEY_ID_STABLE_OWNER` (`KeyId15`) with AES key usage.
145+4. ROM write-locks `KEY_ID_STABLE_OWNER` and erases the temporary `KEY_ID_HEK_SEED` slot.
146+
147+If subsystem mode is not active, the strap is clear, or OCP LOCK is enabled, ROM skips this derivation and `CM_DERIVE_STABLE_KEY` with `key_type = OwnerKey` is unavailable.
148+
149+The following diagram summarizes the ROM-populated stable roots, including IDevID, LDevID, and the optional Owner root:
150+
151+![Stable Root Key Derivation](../images/caliptra-sw/rom/dev/doc/svg/stable-root-derivation.svg)
124152
125153 For a comprehensive overview of the SOC interface registers, please refer to the following link::
126154 https://chipsalliance.github.io/caliptra-rtl/main/external-regs/?p=caliptra_top_reg.generic_and_fuse_reg
@@ -183,7 +211,7 @@
183211 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
184212 | Reserved | 1 | Reserved |
185213 | Key Hash Count | 1 | Number of valid public key hashes |
186-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. ECDSA: n = 4 |
214+| 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)). |
187215
188216
189217 #### PQC Manufacturer Public Key Descriptor
@@ -193,7 +221,7 @@
193221 | Key Descriptor Version | 2 | Version of the Key Descriptor. The value must be 0x1 for Caliptra 2.x |
194222 | Key Type | 1 | Type of the key in the descriptor <br> 0x1 - MLDSA <br> 0x3 - LMS |
195223 | Key Hash Count | 1 | Number of valid public key hashes |
196-| Public Key Hash(es) | 48 * n | List of valid and invalid (if any) SHA2-384 public key hashes. LMS: n = 32, MLDSA: n = 4 |
224+| 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)). |
197225
198226
199227 #### Header
@@ -405,10 +433,10 @@
405433 | Unlock Level | 1 | Debug unlock Level (Number 1-8). |
406434 | Reserved | 3 | Reserved field. |
407435 | Challenge | 48 | Random number sent in `AUTH_DEBUG_UNLOCK_CHALLENGE` mailbox command payload. |
408-| ECC Public Key | 96 | ECC P-384 public key used to verify the Message Signature <br> **X-Coordinate:** Public Key X-Coordinate (48 bytes, big endian) <br> **Y-Coordinate:** Public Key Y-Coordinate (48 bytes, big endian) |
409-| MLDSA Public Key | 2592 | MLDSA-87 public key used to verify the Message Signature. |
410-| ECC Signature | 96 | ECC P-384 signature of the Message hashed using SHA2-384. <br> **R-Coordinate:** Random Point (48 bytes) <br> **S-Coordinate:** Proof (48 bytes). |
411-| MLDSA Signature | 4628 | MLDSA signature of the Message hashed using SHA2-512. (4627 bytes + 1 Reserved byte). |
436+| ECC Public Key | 96 | ECC P-384 public key used to verify the Message Signature <br> **X-Coordinate:** Public Key X-Coordinate (48 bytes) <br> **Y-Coordinate:** Public Key Y-Coordinate (48 bytes). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
437+| MLDSA Public Key | 2592 | MLDSA-87 public key used to verify the Message Signature. See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
438+| ECC Signature | 96 | ECC P-384 signature of the Message hashed using SHA2-384. <br> **R-Coordinate:** Random Point (48 bytes) <br> **S-Coordinate:** Proof (48 bytes). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
439+| MLDSA Signature | 4628 | MLDSA-87 signature of the Message hashed using SHA2-512 (4627 bytes + 1 Reserved byte). See [Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields). |
412440
413441
414442 7. On receiving this payload, ROM performs the following validations:
@@ -417,7 +445,7 @@
417445 - Calculates the address of the public key hash fuse as follows: <br>
418446 **SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET register value + ( (Debug Unlock Level - 1) * SHA2-384 hash size (48 bytes) )**
419447 - Retrieves the SHA2-384 hash (48 bytes) from the calculated address using DMA assist.
420- - Computes the SHA2-384 hash of the message formed by concatenating the ECC and MLDSA public keys in the payload.
448+ - Computes the SHA2-384 hash of the message formed by concatenating the ECC and MLDSA public keys in the payload. See [Production debug unlock public key hashes: byte ordering](#production-debug-unlock-public-key-hashes-byte-ordering) for the exact byte order and fuse programming details.
421449 - Compares the retrieved and computed hashes. It the comparison fails, the ROM blocks the debug unlock request by setting the registers outlined in step 3.
422450 - Upon hash comparison failure, the ROM exits the payload validation flow and completes the mailbox command.
423451
@@ -585,11 +613,11 @@
585613 | Field | Size (bytes) | Description |
586614 | ---------------- | -------------- | ------------------------------------------------------------------------------------------------- |
587615 | Marker | 4 | Magic Number marking the start of the CSR payload. The value must be 0x435352 (β€˜CSR’ in ASCII). |
588-| Size | 4 | Size of the entire CSR payload. Current size is 8272 bytes. |
616+| Size | 4 | Size of the entire CSR payload. Current size is 8784 bytes. |
589617 | ECC CSR Size | 4 | Size of the ECC CSR in bytes. |
590618 | ECC CSR | 512 | ECC CSR buffer. Actual CSR size is indicated by 'ECC CSR Size'. |
591619 | MLDSA CSR Size | 4 | Size of the MLDSA CSR in bytes. |
592-| MLDSA CSR | 7680 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. |
620+| MLDSA CSR | 8192 | MLDSA CSR bytes. Actual CSR size is indicated by 'MLDSA CSR Size'. |
593621 | CSR MAC | 64 | HMAC-512 MAC, computed over the envelope bytes up to but not including this field. |
594622
595623
@@ -612,6 +640,105 @@
612640 | πŸ”’IDevID Cert MLDSA Signature |
613641 | πŸ”’IDevID MLDSA Pub Key |
614642
643+
644+#### UEID (Unique Endpoint Identifier)
645+
646+The UEID is a 17-byte identifier that is embedded (as an X.509 extension) in the
647+IDevID CSR, the LDevID certificate, and the FMC Alias certificate. Its value is
648+derived entirely from fuses.
649+
650+##### Source fuses
651+
652+The UEID is assembled from 5 consecutive 32-bit words of the
653+`FUSE_IDEVID_CERT_ATTR` fuse bank (see the [Fuse Registers](#fuse-registers)
654+table):
655+
656+| Fuse word | `IdevidCertAttr` variant | Usage in UEID |
657+| ----------- | ------------------------------- | ----------------------------------------- |
658+| 11 | `UeidType` | UEID type byte (see RFC 9711 Β§4.2.1.1) |
659+| 12 | `ManufacturerSerialNumber1` | First 4 bytes of the endpoint serial |
660+| 13 | `ManufacturerSerialNumber2` | Next 4 bytes of the endpoint serial |
661+| 14 | `ManufacturerSerialNumber3` | Next 4 bytes of the endpoint serial |
662+| 15 | `ManufacturerSerialNumber4` | Last 4 bytes of the endpoint serial |
663+
664+
665+Only the low byte of word 11 is used; the high 3 bytes of that word are
666+discarded. Each of the four serial-number words is written to the UEID buffer
667+in **little-endian** order (the natural byte order of the u32 register).
668+
669+##### Byte layout
670+
671+```
672+ byte 0 byte 1 ─ byte 4 byte 5 ─ byte 8 byte 9 ─ byte 12 byte 13 ─ byte 16
673+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
674+ β”‚ UeidType β”‚ β”‚ MfgSerialNum1 β”‚ β”‚ MfgSerialNum2 β”‚ β”‚ MfgSerialNum3 β”‚ β”‚ MfgSerialNum4 β”‚
675+ β”‚ (byte 0) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚ β”‚ (LE u32) β”‚
676+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
677+```
678+
679+This assembly is implemented in `caliptra_drivers::FuseBank::ueid` in
680+`drivers/src/fuse_bank.rs`, returning a `[u8; 17]`.
681+
682+##### Placement in the certificate / CSR
683+
684+The 17-byte UEID is placed in the TCG DICE "Ueid" X.509 extension (OID
685+`2.23.133.5.4.4`, not marked critical). The extension's `extnValue`
686+`OCTET STRING` contains a DER-encoded `SEQUENCE { ueid OCTET STRING }`, as
687+defined by the TCG DICE specification. The DER bytes written into the TBS
688+template are:
689+
690+| DER bytes | Meaning |
691+| --------------------------- | --------------------------------------------------------- |
692+| `30 1F` | `SEQUENCE`, length 31 β€” the `Extension` |
693+| `06 06 67 81 05 05 04 04` | `OID 2.23.133.5.4.4` (`tcg-dice-Ueid`) |
694+| `04 15` | `OCTET STRING`, length 21 β€” the `extnValue` wrapper |
695+| `30 13` | inner `SEQUENCE`, length 19 β€” the `TcgUeid` structure |
696+| `04 11` | inner `OCTET STRING`, length 17 β€” the UEID value |
697+| `XX XX … XX` (17 B) | the 17 UEID bytes assembled above |
698+
699+
700+The template slot for the 17 UEID bytes sits at a fixed offset in the TBS
701+template (e.g. `UEID_OFFSET = 312` for `InitDevIdCsrTbsEcc384`); the ROM copies
702+the UEID returned by `FuseBank::ueid` directly into that slot with no further
703+transformation. See `x509/gen/src/x509.rs::make_tcg_ueid_ext` for the generator
704+and `x509/build/*` for the resulting pre-baked templates.
705+
706+##### End-to-end example
707+
708+Given the following example fuse values (as programmed by the integration test
709+`cert_test_with_ueid` in `rom/dev/tests/rom_integration_tests/test_image_validation.rs`):
710+
711+| Fuse word | Field | Value |
712+| ----------- | -------------------------------- | --------------- |
713+| 11 | `UeidType` | `0x0000_0001` |
714+| 12 | `ManufacturerSerialNumber1` | `0x0403_0201` |
715+| 13 | `ManufacturerSerialNumber2` | `0x0807_0605` |
716+| 14 | `ManufacturerSerialNumber3` | `0x0C0B_0A09` |
717+| 15 | `ManufacturerSerialNumber4` | `0x100F_0E0D` |
718+
719+
720+Step-by-step:
721+
722+1. `FuseBank::ueid` reads the five fuse words and takes the low byte of word 11:
723+ `ueid_type = 0x01`.
724+2. Each serial-number word is converted to little-endian bytes:
725+ - `0x04030201 β†’ 01 02 03 04`
726+ - `0x08070605 β†’ 05 06 07 08`
727+ - `0x0C0B0A09 β†’ 09 0A 0B 0C`
728+ - `0x100F0E0D β†’ 0D 0E 0F 10`
729+3. The 17-byte UEID is:
730+ `01 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10`
731+ (byte 0 is the type; bytes 1–16 are the endpoint serial).
732+4. The UEID is wrapped in the DER framing shown above and emitted verbatim in
733+ the IDevID CSR, LDevID certificate, and FMC Alias certificate. The resulting
734+ bytes on the wire for the Ueid extension are:
735+ `30 1F 06 06 67 81 05 05 04 04 04 15 30 13 04 11 01 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10`.
736+
737+The `cert_test_with_ueid` test programs exactly these fuses, boots the ROM,
738+retrieves the IDevID ECC CSR, LDevID cert, and FMC Alias cert from the UART
739+log, and asserts that the hex-encoded bytes
740+`010102030405060708090A0B0C0D0E0F10` appear in all three β€” confirming both the
741+fuse-to-UEID assembly and the DER placement described here.
615742
616743 ### Local Device ID DICE layer
617744
@@ -731,24 +858,63 @@
731858
732859 #### Handling commands from mailbox
733860
734-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.
735-
736-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).
737-2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main-2.x/runtime/README.md#version).
738-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).
739-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).
740-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).
741-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).
742-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).
743-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).
744-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)
745-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)
746-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)
747-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)
861+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.
862+
863+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).
864+2. **VERSION**: Get version info about the module. [Version command](https://github.com/chipsalliance/caliptra-sw/blob/main/runtime/README.md#version).
865+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).
866+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).
867+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).
868+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).
869+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).
870+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)
871+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)
872+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)
873+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)
874+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)
748875 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.
749-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)
750-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)
751-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)
876+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)
877+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)
878+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)
879+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.
880+18. **ZEROIZE_UDS_FE**
881+
882+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.
883+
884+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.
885+
886+The zeroization process follows these steps for each partition:
887+1. Clears the zeroization marker first to mask potential ECC errors during power failures
888+2. Zeroizes the seed data
889+3. Clears the partition digest
890+
891+All operations are verified to return 0xFFFFFFFF before proceeding.
892+
893+Command Code: `0x5A45_5546` ("ZEUF")
894+
895+*Table: `ZEROIZE_UDS_FE` input arguments*
896+
897+| **Name** | **Type** | **Description**
898+| -------- | -------- | ---------------
899+| chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
900+| flags | u32 | Partition flags. See ZEROIZE_UDS_FE_FLAGS below.
901+
902+*Table: `ZEROIZE_UDS_FE_FLAGS` input flags*
903+
904+| **Name** | **Value** | **Description**
905+| ------------------ | --------- | ---------------
906+| ZEROIZE_UDS_FLAG | 1 << 0 | Zeroize UDS partition
907+| ZEROIZE_FE0_FLAG | 1 << 1 | Zeroize FE partition 0
908+| ZEROIZE_FE1_FLAG | 1 << 2 | Zeroize FE partition 1
909+| ZEROIZE_FE2_FLAG | 1 << 3 | Zeroize FE partition 2
910+| ZEROIZE_FE3_FLAG | 1 << 4 | Zeroize FE partition 3
911+
912+*Table: `ZEROIZE_UDS_FE` output arguments*
913+
914+| **Name** | **Type** | **Description**
915+| -------- | -------- | ---------------
916+| chksum | u32 | Checksum over other output arguments, computed by Caliptra. Little endian.
917+| dpe_result | u32 | Result code, 0 on success.
752918
753919 #### CM_SHA
754920
@@ -764,7 +930,7 @@
764930 | -------------- | ------------- | ---------------
765931 | chksum | u32 | Checksum over other input arguments, computed by the caller. Little endian.
766932 | hash_algorithm | u32 | Hash algorithm: 1 = SHA-384, 2 = SHA-512. Value 0 is reserved and will return an error.
767-| input_size | u32 | Size of input data in bytes. Maximum 262,132 bytes (256 KB minus 12-byte header overhead).
933+| input_size | u32 | Size of input data in bytes. Maximum 16,372 bytes in subsystem mode (16 KB minus 12-byte header overhead). Passive mode supports up to 262,132 bytes (256 KB minus overhead) when using a passive-mode mailbox.
768934 | input | u8[input_size]| Input data to hash. Variable size up to the mailbox capacity.
769935
770936 *Table: `CM_SHA` output arguments*
@@ -793,7 +959,11 @@
793959
794960 Following is the sequence of steps that are performed to download the firmware image into the mailbox in SUBSYSTEM mode.
795961
796-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:
962+ROM supports two commands for firmware download in SUBSYSTEM mode:
963+- **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.
964+- **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.
965+
966+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:
797967 - `Device ID`
798968 - `Device Status`
799969 - `Recovery memory access / INDIRECT_CTRL support`
@@ -1049,7 +1219,7 @@
10491219 - **ICCM**
10501220
10511221 ### Launch FMC
1052-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.
1222+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.
10531223
10541224 ## Warm reset flow
10551225 ROM does not perform any DICE derivations or firmware validation during warm reset.
@@ -1087,6 +1257,13 @@
10871257 ROM performs the same initialization sequence as specified [here](#Initialization)
10881258
10891259 ### Error handling
1260+Fatal error reporting can be configured by the following subsystem strap:
1261+
1262+| Register | Field/Bits | Description |
1263+| :------------------------------- | :--------- | :------------------------------------------------------ |
1264+| SS_STRAP_GENERIC[3] | [1] | Wait for device reset before fatal error reporting. When set to 1 in subsystem mode, ROM waits for the recovery interface `DEVICE_RESET.RESET_CTRL` field to be set to `0x1` (`Reset Device`) before updating `CPTRA_FW_ERROR_FATAL` in the fatal error handler. When clear, ROM reports fatal errors immediately. |
1265+
1266+
10901267 The ROM executes the following operations:
10911268 - Updates the `cptra_fw_error_fatal` and `cptra_fw_error_non_fatal` registers with the error code ROM_UNKNOWN_RESET_FLOW (0x01040020) error code.
10921269 - Zeroizes the following cryptographic hardware modules:
@@ -1162,11 +1339,12 @@
11621339 ### Preamble validation: Manufacturing key validation
11631340
11641341 - fuse_ecc_revocation serves as the bitmask for revoking ECC keys.
1165- - If bit-n is set, the nth key is disabled. All other higher bits that are zeros indicate the keys are still enabled.
1342+ - If bit-n is set, the nth key is disabled. All other bits that are zeros indicate the keys are still enabled.
11661343 - If all the bits are zeros, all ECC keys remain enabled.
11671344 - Ensure that the Active Key Index in the preamble is not disabled by the fuse_ecc_revocation fuse.
11681345 - If the key is disabled, the validation process fails.
1169-- Repeat the above procedure for LMS or MLDSA keys using the fuse_lms_revocation or fuse_mldsa_revocation fuses, respectively, for key revocation.
1346+ - **Note: The last key index is never revoked, regardless of the fuse value.**
1347+- 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.
11701348
11711349 ### Preamble validation: Validate the Owner key
11721350
@@ -1175,6 +1353,755 @@
11751353 - 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.
11761354 - If the computed hash matches the value in fuse_owner_pk_hash, the owner public keys are deemed valid.
11771355 - If there is a hash mismatch, the image validation process fails.
1356+
1357+### Public key hash byte ordering (dword reversal)
1358+
1359+**Important:** Hashes and ECC key coordinates stored in the firmware manifest and fuse registers use
1360+a **reversed-dword format** rather than the standard byte order defined by the SHA specification.
1361+
1362+In standard byte order, a SHA2-384 hash is a sequence of 48 bytes exactly as output by tools like
1363+OpenSSL or Python's `hashlib`. In reversed-dword format, the same 48 bytes are grouped into 12
1364+four-byte words (dwords) and the bytes within each dword are reversed.
1365+
1366+For example, if the standard SHA2-384 hash begins with `b1 7c a8 77 66 66 57 cc d1 00 e6 92 ...`:
1367+
1368+| Standard byte order | β†’ | Reversed-dword format |
1369+| ---------------------- | --- | ----------------------- |
1370+| `b1 7c a8 77` || `77 a8 7c b1` |
1371+| `66 66 57 cc` || `cc 57 66 66` |
1372+| `d1 00 e6 92` || `92 e6 00 d1` |
1373+| ... || ... |
1374+
1375+
1376+This reversed-dword format applies to:
1377+- **Individual public key hashes** in the ECC and PQC key descriptors within the preamble
1378+- **FUSE_VENDOR_PK_HASH** and **CPTRA_OWNER_PK_HASH** fuse/register values (which are `[u32; 12]` arrays)
1379+- **ECC public key coordinates** (X and Y), which are stored as `[u32; 12]` arrays in the preamble
1380+
1381+Note: LMS public key fields (`tree_type`, `otstype`, `id`, `digest`) follow the LMS specification
1382+encoding and are **not** subject to dword reversal. MLDSA public keys are stored as raw byte arrays
1383+and are also **not** subject to dword reversal.
1384+
1385+For a detailed description of byte ordering conventions for all mailbox cryptographic fields
1386+(including ECC, ML-DSA, and SHA digest fields with OpenSSL examples), see the
1387+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields)
1388+section in the Runtime README.
1389+
1390+### Computing public key hashes: step-by-step example
1391+
1392+The following example walks through the computation of the **vendor PK descriptor hash**
1393+using the test public keys from `image/fake-keys/src/lib.rs` with PQC key type **LMS (type 3)**.
1394+
1395+#### Step 1: Hash each vendor ECC public key
1396+
1397+Each ECC-384 public key has X and Y coordinates, each stored as `[u32; 12]`. To hash a key,
1398+serialize the struct to 96 bytes by writing each `u32` word in reversed-dword format, then
1399+compute SHA2-384 of those 96 bytes.
1400+
1401+**ECC Key 0:**
1402+```
1403+X (standard byte order): c69fe67f 97ea3e42 21a7a603 6c2e070d 1657327b c3f1e7c1
1404+ 8dccb9e4 ffda5c3f 4db0a1c0 567e0973 17bf4484 39696a07
1405+Y (standard byte order): c126b913 5fc82572 8f1cd403 19109430 994fe3e8 74a8b026
1406+ be14794d 27789964 7735fde8 328afd84 cd4d4aa8 72d40b42
1407+
1408+X (reversed-dword): 7fe69fc6 423eea97 03a6a721 0d072e6c 7b325716 c1e7f1c3
1409+ e4b9cc8d 3f5cdaff c0a1b04d 73097e56 8444bf17 076a6939
1410+Y (reversed-dword): 13b926c1 7225c85f 03d41c8f 30941019 e8e34f99 26b0a874
1411+ 4d7914be 64997827 e8fd3577 84fd8a32 a84a4dcd 420bd472
1412+
1413+Input to SHA384 = X_reversed || Y_reversed (96 bytes)
1414+SHA384 (standard): 84facd34 227de869 1fbb7d33 49306e0f 250a3659 53a6cc6b
1415+ 629d4616 32f73cfd 768152bb 8a03a255 5a1b1f1f c3923faa
1416+SHA384 (reversed-dword): 34cdfa84 69e87d22 337dbb1f 0f6e3049 59360a25 6bcca653
1417+ 16469d62 fd3cf732 bb528176 55a2038a 1f1f1b5a aa3f92c3
1418+```
1419+
1420+**ECC Key 1:**
1421+```
1422+X (standard): a6309750 f0a05ddb 956a7f86 2812ec4f ec454e95 3b53dbfb
1423+ 9eb54140 15ea7507 084af93c b7fa33fe 51811ad5 e754232e
1424+Y (standard): ef5a5987 7a0ce0be 2621d2a9 8bf3c5df af7b3d6d 97f24183
1425+ a4a42038 58c39b86 272ef548 e572b937 1ecf1994 1b8d4ea7
1426+
1427+SHA384 (standard): fe89195f 7fab8ebb 2818d935 837493c2 378525ef 686ed220
1428+ 09b9a399 f23f1f42 2f5ae1f3 ba1c3083 1a68a456 9c01fc96
1429+SHA384 (reversed-dword): 5f1989fe bb8eab7f 35d91828 c2937483 ef258537 20d26e68
1430+ 99a3b909 421f3ff2 f3e15a2f 83301cba 56a4681a 96fc019c
1431+```
1432+
1433+**ECC Key 2:**
1434+```
1435+X (standard): a0d25693 c4251e48 185615b0 a6c27f6d e62c39f5 a9a32f75
1436+ 9553226a 4d1926c1 7928910f b7adc1b6 89996733 10134881
1437+Y (standard): bbdf72d7 07c08100 d54fcdad b1567bb0 0522762b 76b8dc4a
1438+ 846c175a 3fbd0501 9bdc8118 4be5f33c bb21b41d 93a8c523
1439+
1440+SHA384 (standard): f397ba45 b5801ddf b732078d ffdf792f b584a73f b055acaf
1441+ ef39f31d 5b88c7d5 2753a45a 0c76b098 90d8e335 7be87f26
1442+SHA384 (reversed-dword): 45ba97f3 df1d80b5 8d0732b7 2f79dfff 3fa784b5 afac55b0
1443+ 1df339ef d5c7885b 5aa45327 98b0760c 35e3d890 267fe87b
1444+```
1445+
1446+**ECC Key 3:**
1447+```
1448+X (standard): 002a82b6 8e03e9a0 fd3b4c14 ca2cb3e8 14350a71 0e43956d
1449+ 21694fb4 f34485e8 f0e33583 f7ea142d 50e16f8b 0225bb95
1450+Y (standard): 5802641c 7c45a4a2 408e03a6 a4100a92 50fcc468 d238cd0d
1451+ 449cc3e5 1abc25e7 0b05c426 843dcd6f 944ef6ff fa53ec5b
1452+
1453+SHA384 (standard): 8ba8acb6 b98da9dc 8ffce0bc eba86454 4acbbd6e 3f31466e
1454+ 5d532565 0bfc9e3b c8afb2b5 c33e20f5 06992143 83f33bc1
1455+SHA384 (reversed-dword): b6aca88b dca98db9 bce0fc8f 5464a8eb 6ebdcb4a 6e46313f
1456+ 6525535d 3b9efc0b b5b2afc8 f5203ec3 43219906 c13bf383
1457+```
1458+
1459+#### Step 2: Hash each vendor LMS public key
1460+
1461+Each LMS public key is a 48-byte struct: `tree_type` (u32), `otstype` (u32), `id` (16 bytes),
1462+`digest` (24 bytes). The binary serialization is hashed directly.
1463+
1464+**LMS Key 0:**
1465+```
1466+tree_type=0x0000000c, otstype=0x00000007
1467+id: 4908a17b cadb1829 1e289058 d5a8e3e8
1468+digest: 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1469+
1470+Serialized (48 bytes): 0000000c 00000007 4908a17b cadb1829 1e289058 d5a8e3e8
1471+ 64ad3eb8 be6864f1 7ccda38b de35edaa 6c0da527 645407c6
1472+SHA384 (standard): fc2c1b6f 56f732d1 fd876f3f ef757cbb a2b1c64b cc148298
1473+ d7508262 4bdf27cb 23d6b5b6 7169c46f 50b7fc19 92068fec
1474+SHA384 (reversed-dword): 6f1b2cfc d132f756 3f6f87fd bb7c75ef 4bc6b1a2 988214cc
1475+ 628250d7 cb27df4b b6b5d623 6fc46971 19fcb750 ec8f0692
1476+```
1477+
1478+**LMS Key 1:**
1479+```
1480+tree_type=0x0000000c, otstype=0x00000007
1481+id: 7cb5369d 64e4281d 046e977c 70d4d0a3
1482+digest: 8ea4701d adf7d700 0564b7d6 1d1c9587 9dd6475c 9c3aae0b
1483+
1484+SHA384 (standard): 7b5811fd 8d2b0cf8 9851f12d d2a7c239 f4f3abc5 d928dcc0
1485+ 3b4b891d abbdc67f c7b88436 432e1544 a408bc9c bb503f6b
1486+SHA384 (reversed-dword): fd11587b f80c2b8d 2df15198 39c2a7d2 c5abf3f4 c0dc28d9
1487+ 1d894b3b 7fc6bdab 3684b8c7 44152e43 9cbc08a4 6b3f50bb
1488+```
1489+
1490+**LMS Key 2:**
1491+```
1492+tree_type=0x0000000c, otstype=0x00000007
1493+id: 2bbb4b72 c5b41e05 d2fabe76 f41704bd
1494+digest: dcb53f96 24d4c7b3 c9ae4d4c 0e41e08e 3b159396 0fe6a277
1495+
1496+SHA384 (standard): 7e08a494 6933d35a 42c0d7b0 0236b10b db14c100 3f82f6a9
1497+ 7d401cb8 e420a7fa 5aab12b3 c4e96bec 49aec770 225a8f88
1498+SHA384 (reversed-dword): 94a4087e 5ad33369 b0d7c042 0bb13602 00c114db a9f6823f
1499+ b81c407d faa720e4 b312ab5a ec6be9c4 70c7ae49 888f5a22
1500+```
1501+
1502+**LMS Key 3:**
1503+```
1504+tree_type=0x0000000c, otstype=0x00000007
1505+id: 42cba2e5 575b5235 7ea7aead ef54074c
1506+digest: 5aa60e27 69251599 3ae8e21f 27ccdded 8ffcd3d2 8efbdec2
1507+
1508+SHA384 (standard): d3734fbc ee2893a3 b1b6519b 6ec78fb8 d7425327 cde1f7aa
1509+ 23012c64 c635219f d4ab1c4d 1b023252 00042884 2e463dbb
1510+SHA384 (reversed-dword): bc4f73d3 a39328ee 9b51b6b1 b88fc76e 275342d7 aaf7e1cd
1511+ 642c0123 9f2135c6 4d1cabd4 5232021b 84280400 bb3d462e
1512+```
1513+
1514+#### Step 3: Build the ECC key descriptor (196 bytes)
1515+
1516+Concatenate the 4-byte header with the 4 key hashes (each in reversed-dword format):
1517+
1518+```
1519+Header (4 bytes): 01 00 00 04 (version=1, reserved=0, key_hash_count=4)
1520+ECC key 0 hash (48 bytes, reversed-dword): 34cdfa84 69e87d22 ... aa3f92c3
1521+ECC key 1 hash (48 bytes, reversed-dword): 5f1989fe bb8eab7f ... 96fc019c
1522+ECC key 2 hash (48 bytes, reversed-dword): 45ba97f3 df1d80b5 ... 267fe87b
1523+ECC key 3 hash (48 bytes, reversed-dword): b6aca88b dca98db9 ... c13bf383
1524+
1525+Total: 4 + (4 Γ— 48) = 196 bytes
1526+```
1527+
1528+#### Step 4: Build the PQC (LMS) key descriptor (1540 bytes)
1529+
1530+```
1531+Header (4 bytes): 01 00 03 20 (version=1, key_type=3=LMS, key_hash_count=32)
1532+LMS key 0 hash (48 bytes, reversed-dword): 6f1b2cfc d132f756 ... ec8f0692
1533+LMS key 1 hash (48 bytes, reversed-dword): fd11587b f80c2b8d ... 6b3f50bb
1534+LMS key 2 hash (48 bytes, reversed-dword): 94a4087e 5ad33369 ... 888f5a22
1535+LMS key 3 hash (48 bytes, reversed-dword): bc4f73d3 a39328ee ... bb3d462e
1536+ ... (keys 0-3 repeated 8 times to fill all 32 slots)
1537+
1538+Total: 4 + (32 Γ— 48) = 1540 bytes
1539+```
1540+
1541+#### Step 5: Compute the vendor PK descriptor hash
1542+
1543+```
1544+Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes
1545+
1546+SHA384 (standard byte order):
1547+ b17ca877 666657cc d100e692 6c7206b6 0c995cb6 8992c6c9
1548+ baefce72 8af05441 dee1ff41 5adfc187 e1e4edb4 d3b2d909
1549+
1550+As [u32; 12] fuse register value:
1551+ [0xb17ca877, 0x666657cc, 0xd100e692, 0x6c7206b6,
1552+ 0x0c995cb6, 0x8992c6c9, 0xbaefce72, 0x8af05441,
1553+ 0xdee1ff41, 0x5adfc187, 0xe1e4edb4, 0xd3b2d909]
1554+```
1555+
1556+### Computing public key hashes: MLDSA step-by-step example
1557+
1558+The following example walks through the same computation as the LMS example above, but
1559+using PQC key type **MLDSA (type 1)** with the test keys from `image/fake-keys/src/lib.rs`.
1560+
1561+#### MLDSA Step 1: Hash each vendor ECC public key
1562+
1563+The ECC keys and their hashes are identical to the LMS example β€” see
1564+[Step 1 above](#step-1-hash-each-vendor-ecc-public-key). The ECC key descriptor is
1565+independent of the PQC key type.
1566+
1567+#### MLDSA Step 2: Hash each vendor MLDSA public key
1568+
1569+Each MLDSA-87 public key is a 2592-byte array (`[u32; 648]`). When serialized via
1570+`as_bytes()`, each `u32` word is written in little-endian byte order β€” for example, the
1571+Rust value `0x3bf1c072` becomes bytes `72 c0 f1 3b` in memory. Unlike LMS keys, MLDSA
1572+keys are not subject to any additional encoding β€” these raw bytes are hashed directly
1573+with SHA2-384.
1574+
1575+**MLDSA Key 0:**
1576+```
1577+Size: 2592 bytes (648 u32 words)
1578+First 24 bytes: 72c0f13b 7d937e22 69b6988d 6daadc3a e78acd11 940cfc0d ...
1579+
1580+SHA384 (standard): f1097978 0adae470 dcd4eeb8 5749a2e4 2e70c055 ebac46e4
1581+ 07c2c404 b46473d8 189117ed 8c83dde4 9f941e6a 1b6c6d4c
1582+SHA384 (reversed-dword): 787909f1 70e4da0a b8eed4dc e4a24957 55c0702e e446aceb
1583+ 04c4c207 d87364b4 ed179118 e4dd838c 6a1e949f 4c6d6c1b
1584+```
1585+
1586+**MLDSA Key 1:**
1587+```
1588+Size: 2592 bytes (648 u32 words)
1589+First 24 bytes: f432346c 096d0ec9 04f8d925 1512236b e3fd1ccb bda9ed3a ...
1590+
1591+SHA384 (standard): a57b6f71 ffab9844 de49e9f7 ad61476b 7446e140 517d07b1
1592+ 81447acb a6d7166f 7b89f199 b6e36174 2d0ab01c 540d26de
1593+SHA384 (reversed-dword): 716f7ba5 4498abff f7e949de 6b4761ad 40e14674 b1077d51
1594+ cb7a4481 6f16d7a6 99f1897b 7461e3b6 1cb00a2d de260d54
1595+```
1596+
1597+**MLDSA Key 2:**
1598+```
1599+Size: 2592 bytes (648 u32 words)
1600+First 24 bytes: 2bc91a00 7d3e5a4f e6b3f2ec cb1aaa0d 278d9786 44b25fed ...
1601+
1602+SHA384 (standard): 7f2f3c55 e8dd2481 bbee17c1 5d5773a8 01a9c0a6 84b30e47
1603+ 0ae67ecd 1ec3e7ac 19273c71 feb6bb99 10d26dd0 4ace4298
1604+SHA384 (reversed-dword): 553c2f7f 8124dde8 c117eebb a873575d a6c0a901 470eb384
1605+ cd7ee60a ace7c31e 713c2719 99bbb6fe d06dd210 9842ce4a
1606+```
1607+
1608+**MLDSA Key 3:**
1609+```
1610+Size: 2592 bytes (648 u32 words)
1611+First 24 bytes: 378dcb02 a6db3481 d51e9913 14da1567 a211290e f4c3d02f ...
1612+
1613+SHA384 (standard): 79fbeb0a 6ebc354b ccf48dd1 5b6c9142 a62af0c5 198c0de1
1614+ 365fbcb0 b2463ee5 103ccae3 4504ab83 04b37886 5c9a28ae
1615+SHA384 (reversed-dword): 0aebfb79 4b35bc6e d18df4cc 42916c5b c5f02aa6 e10d8c19
1616+ b0bc5f36 e53e46b2 e3ca3c10 83ab0445 8678b304 ae289a5c
1617+```
1618+
1619+#### MLDSA Step 3: Build the ECC key descriptor (196 bytes)
1620+
1621+Same as the LMS example β€” the ECC descriptor is independent of PQC key type. See
1622+[Step 3 above](#step-3-build-the-ecc-key-descriptor-196-bytes).
1623+
1624+#### MLDSA Step 4: Build the PQC (MLDSA) key descriptor (1540 bytes)
1625+
1626+The PQC key descriptor struct always has 32 hash slots (`VENDOR_PQC_MAX_KEY_COUNT`).
1627+For MLDSA, only 4 keys are populated; the remaining 28 slots are zero-filled.
1628+
1629+```
1630+Header (4 bytes): 01 00 01 04 (version=1, key_type=1=MLDSA, key_hash_count=4)
1631+MLDSA key 0 hash (48 bytes, reversed-dword): 787909f1 70e4da0a ... 4c6d6c1b
1632+MLDSA key 1 hash (48 bytes, reversed-dword): 716f7ba5 4498abff ... de260d54
1633+MLDSA key 2 hash (48 bytes, reversed-dword): 553c2f7f 8124dde8 ... 9842ce4a
1634+MLDSA key 3 hash (48 bytes, reversed-dword): 0aebfb79 4b35bc6e ... ae289a5c
1635+ ... (keys 4-31 are zero-filled)
1636+
1637+Total: 4 + (32 Γ— 48) = 1540 bytes
1638+```
1639+
1640+#### MLDSA Step 5: Compute the vendor PK descriptor hash
1641+
1642+```
1643+Input = ECC descriptor (196 bytes) || PQC descriptor (1540 bytes) = 1736 bytes
1644+
1645+SHA384 (standard byte order):
1646+ 30399676 a17e3e97 3677b3ff 862f4bf2 d1932d88 4778453c
1647+ 376fe00d c93fb8aa 0770f3eb f3411a08 53e9c57e ce8a2980
1648+
1649+As [u32; 12] fuse register value:
1650+ [0x30399676, 0xa17e3e97, 0x3677b3ff, 0x862f4bf2,
1651+ 0xd1932d88, 0x4778453c, 0x376fe00d, 0xc93fb8aa,
1652+ 0x0770f3eb, 0xf3411a08, 0x53e9c57e, 0xce8a2980]
1653+```
1654+
1655+#### Owner PK hash
1656+
1657+The owner PK hash is SHA2-384 over the serialized `ImageOwnerPubKeys` struct, which contains:
1658+- `ecc_pub_key`: `{ x: [u32; 12], y: [u32; 12] }` β€” 96 bytes (in reversed-dword format)
1659+- `pqc_pub_key`: raw byte array of 2592 bytes (for LMS, only the first 48 bytes are meaningful;
1660+ the rest are zero-padded)
1661+
1662+Total: 2688 bytes. The SHA2-384 of these bytes is the owner PK hash.
1663+
1664+#### Summary of expected hash values using test keys
1665+
1666+Using the test keys from `image/fake-keys/src/lib.rs`:
1667+
1668+| Hash | PQC Type | Standard byte order (hex) |
1669+| ------ | ---------- | --------------------------- |
1670+| Vendor PK descriptor hash | LMS (type 3) | `b17ca877666657ccd100e6926c7206b60c995cb68992c6c9baefce728af05441dee1ff415adfc187e1e4edb4d3b2d909` |
1671+| Vendor PK descriptor hash | MLDSA (type 1) | `30399676a17e3e973677b3ff862f4bf2d1932d884778453c376fe00dc93fb8aa0770f3ebf3411a0853e9c57ece8a2980` |
1672+| Owner PK hash | LMS (type 3) | `1b179390e4e6c44422ed553e256c7d675cd93190cb49d88d485aa4ef3906cd492ab3ee3d3ba5f2c990ad13390fed4de5` |
1673+| Owner PK hash | MLDSA (type 1) | `48afdb073c5e0d4ee46490468ef81f2cf57249b6e76a28f5fca4de696a7d3e2ed3efc4e6774318543e95307a54988bd7` |
1674+
1675+
1676+To convert any of these standard byte order hashes to the `[u32; 12]` fuse register format, group
1677+the hex string into 8-character (4-byte) chunks and interpret each as a 32-bit word:
1678+- `b17ca877666657cc...` β†’ `[0xb17ca877, 0x666657cc, 0xd100e692, ...]`
1679+
1680+#### Python script to compute vendor and owner PK hashes
1681+
1682+The following Python script computes the vendor PK descriptor hash and owner PK hash from
1683+ECC PEM files and LMS or MLDSA binary key files:
1684+
1685+```python
1686+#!/usr/bin/env python3
1687+"""
1688+Compute the Caliptra vendor PK descriptor hash and owner PK hash
1689+from ECC (.pem) and LMS/MLDSA (.bin) public key files.
1690+
1691+Usage:
1692+ python3 compute_pk_hashes.py --pqc-key-type <1|3> \\
1693+ --vendor-ecc-pub-keys key0.pem key1.pem key2.pem key3.pem \\
1694+ --vendor-pqc-pub-keys pqc0.bin pqc1.bin ... \\
1695+ --owner-ecc-pub-key owner.pem \\
1696+ --owner-pqc-pub-key owner_pqc.bin
1697+
1698+PQC key type: 1 = MLDSA, 3 = LMS
1699+
1700+ECC public keys are PEM files (P-384).
1701+LMS public keys are 48-byte binary files (tree_type, otstype, id, digest).
1702+MLDSA public keys are 2592-byte binary files.
1703+"""
1704+import argparse
1705+import hashlib
1706+import struct
1707+import sys
1708+
1709+from cryptography.hazmat.primitives.serialization import load_pem_public_key
1710+
1711+# Sizes
1712+ECC_PUB_KEY_BYTES = 96 # 2 x 48-byte coordinates
1713+PQC_PUB_KEY_SLOT_BYTES = 2592 # MLDSA key size; LMS keys are 48 bytes, zero-padded
1714+LMS_PUB_KEY_BYTES = 48
1715+MLDSA_PUB_KEY_BYTES = 2592
1716+HASH_BYTES = 48 # SHA2-384
1717+
1718+VENDOR_ECC_MAX_KEYS = 4
1719+VENDOR_LMS_MAX_KEYS = 32
1720+VENDOR_MLDSA_MAX_KEYS = 32 # struct always allocates 32 slots; only first 4 are populated
1721+KEY_DESCRIPTOR_VERSION = 1
1722+
1723+
1724+def ecc_pub_key_to_reversed_dwords(pem_path: str) -> bytes:
1725+ """Read an ECC P-384 PEM public key and return 96 bytes in reversed-dword format."""
1726+ with open(pem_path, 'rb') as f:
1727+ pub_key = load_pem_public_key(f.read())
1728+ nums = pub_key.public_numbers()
1729+ x_bytes = nums.x.to_bytes(48, 'big')
1730+ y_bytes = nums.y.to_bytes(48, 'big')
1731+ return to_reversed_dwords(x_bytes) + to_reversed_dwords(y_bytes)
1732+
1733+
1734+def to_reversed_dwords(standard_bytes: bytes) -> bytes:
1735+ """Convert bytes from standard byte order to reversed-dword format.
1736+
1737+ Groups the input into 4-byte dwords and reverses the bytes within each dword.
1738+ """
1739+ assert len(standard_bytes) % 4 == 0
1740+ result = bytearray()
1741+ for i in range(0, len(standard_bytes), 4):
1742+ result.extend(standard_bytes[i:i+4][::-1])
1743+ return bytes(result)
1744+
1745+
1746+def sha384_reversed_dwords(data: bytes) -> bytes:
1747+ """Compute SHA2-384 and return the hash in reversed-dword format."""
1748+ h = hashlib.sha384(data).digest()
1749+ return to_reversed_dwords(h)
1750+
1751+
1752+def build_ecc_key_descriptor(ecc_pem_paths: list) -> bytes:
1753+ """Build the ECC key descriptor: header + key hashes."""
1754+ n = len(ecc_pem_paths)
1755+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, 0, n)
1756+ hashes = b''
1757+ for path in ecc_pem_paths:
1758+ key_bytes = ecc_pub_key_to_reversed_dwords(path)
1759+ hashes += sha384_reversed_dwords(key_bytes)
1760+ # Pad to VENDOR_ECC_MAX_KEYS slots
1761+ hashes += b'\x00' * (HASH_BYTES * (VENDOR_ECC_MAX_KEYS - n))
1762+ return header + hashes
1763+
1764+
1765+def build_pqc_key_descriptor(pqc_bin_paths: list, pqc_key_type: int) -> bytes:
1766+ """Build the PQC key descriptor: header + key hashes."""
1767+ n = len(pqc_bin_paths)
1768+ max_keys = VENDOR_LMS_MAX_KEYS if pqc_key_type == 3 else VENDOR_MLDSA_MAX_KEYS
1769+ header = struct.pack('<HBB', KEY_DESCRIPTOR_VERSION, pqc_key_type, n)
1770+ hashes = b''
1771+ for path in pqc_bin_paths:
1772+ with open(path, 'rb') as f:
1773+ key_bytes = f.read()
1774+ hashes += sha384_reversed_dwords(key_bytes)
1775+ # Pad to max slots
1776+ hashes += b'\x00' * (HASH_BYTES * (max_keys - n))
1777+ return header + hashes
1778+
1779+
1780+def build_owner_pub_keys(ecc_pem_path: str, pqc_bin_path: str) -> bytes:
1781+ """Build the serialized ImageOwnerPubKeys struct."""
1782+ ecc_bytes = ecc_pub_key_to_reversed_dwords(ecc_pem_path)
1783+ with open(pqc_bin_path, 'rb') as f:
1784+ pqc_bytes = f.read()
1785+ # Pad PQC key to full slot size
1786+ pqc_padded = pqc_bytes + b'\x00' * (PQC_PUB_KEY_SLOT_BYTES - len(pqc_bytes))
1787+ return ecc_bytes + pqc_padded
1788+
1789+
1790+def hash_to_fuse_words(standard_hash: bytes) -> list:
1791+ """Convert a standard byte order hash to [u32; 12] fuse word format."""
1792+ return [int.from_bytes(standard_hash[i:i+4], 'big') for i in range(0, 48, 4)]
1793+
1794+
1795+def main():
1796+ parser = argparse.ArgumentParser(
1797+ description='Compute Caliptra vendor PK descriptor hash and owner PK hash')
1798+ parser.add_argument('--pqc-key-type', type=int, required=True, choices=[1, 3],
1799+ help='PQC key type: 1=MLDSA, 3=LMS')
1800+ parser.add_argument('--vendor-ecc-pub-keys', nargs='+', required=True,
1801+ help='Vendor ECC P-384 public key PEM files')
1802+ parser.add_argument('--vendor-pqc-pub-keys', nargs='+', required=True,
1803+ help='Vendor PQC (LMS .bin or MLDSA .bin) public key files')
1804+ parser.add_argument('--owner-ecc-pub-key',
1805+ help='Owner ECC P-384 public key PEM file')
1806+ parser.add_argument('--owner-pqc-pub-key',
1807+ help='Owner PQC (LMS .bin or MLDSA .bin) public key file')
1808+ args = parser.parse_args()
1809+
1810+ pqc_name = {1: 'MLDSA', 3: 'LMS'}[args.pqc_key_type]
1811+
1812+ # Build descriptors
1813+ ecc_desc = build_ecc_key_descriptor(args.vendor_ecc_pub_keys)
1814+ pqc_desc = build_pqc_key_descriptor(args.vendor_pqc_pub_keys, args.pqc_key_type)
1815+ vendor_pub_key_info = ecc_desc + pqc_desc
1816+
1817+ # Vendor PK descriptor hash (standard byte order)
1818+ vendor_hash = hashlib.sha384(vendor_pub_key_info).digest()
1819+ vendor_hex = vendor_hash.hex()
1820+ vendor_words = hash_to_fuse_words(vendor_hash)
1821+
1822+ print(f"PQC key type: {args.pqc_key_type} ({pqc_name})")
1823+ print()
1824+ print(f"Vendor PK descriptor hash (standard byte order):")
1825+ print(f" {vendor_hex}")
1826+ print(f"Vendor PK descriptor hash (fuse [u32; 12]):")
1827+ print(f" {['0x{:08x}'.format(w) for w in vendor_words]}")
1828+
1829+ if args.owner_ecc_pub_key and args.owner_pqc_pub_key:
1830+ owner_bytes = build_owner_pub_keys(args.owner_ecc_pub_key, args.owner_pqc_pub_key)
1831+ owner_hash = hashlib.sha384(owner_bytes).digest()
1832+ owner_hex = owner_hash.hex()
1833+ owner_words = hash_to_fuse_words(owner_hash)
1834+
1835+ print()
1836+ print(f"Owner PK hash (standard byte order):")
1837+ print(f" {owner_hex}")
1838+ print(f"Owner PK hash (fuse [u32; 12]):")
1839+ print(f" {['0x{:08x}'.format(w) for w in owner_words]}")
1840+
1841+
1842+if __name__ == '__main__':
1843+ main()
1844+```
1845+
1846+### Fuse value byte ordering
1847+
1848+This section documents the byte ordering convention for every multi-word fuse
1849+register. It uses the same style as the
1850+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields)
1851+section in the Runtime README: examples show the relationship between standard
1852+tool output (e.g. OpenSSL, Python `hashlib`) and the `u32` word values written
1853+to fuse registers.
1854+
1855+> **When adding a new multi-word fuse**, add an entry to the appropriate
1856+> category below so that SoC integrators have a single reference for all fuse
1857+> byte ordering.
1858+
1859+#### SHA digest fuses (big-endian words / reversed-dword)
1860+
1861+The following fuse registers store SHA digest values as `[u32; N]` arrays using
1862+the same **reversed-dword format** described in
1863+[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal).
1864+Each 4-byte group from the standard hash output (as produced by `openssl dgst`
1865+or Python's `hashlib`) is byte-reversed when stored as a `u32` word.
1866+
1867+| Fuse Register | Array Type | Hash Algorithm |
1868+| --- | --- | --- |
1869+| FUSE_VENDOR_PK_HASH | `[u32; 12]` | SHA2-384 of vendor public key descriptors |
1870+| FUSE_MANUF_DEBUG_UNLOCK_TOKEN | `[u32; 16]` | SHA-512 of the manufacturing debug unlock token |
1871+
1872+
1873+Example β€” suppose `openssl dgst -sha512` produces a digest starting with:
1874+
1875+```
1876+openssl output: 86 9B A8 D5 AD 0F CF 82 02 E5 60 80 ...
1877+ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~
1878+Fuse register[0]: 0x869BA8D5 [1]: 0xAD0FCF82 [2]: 0x02E56080 ...
1879+```
1880+
1881+Each 4-byte group from the OpenSSL output maps directly to one fuse register
1882+word as a big-endian `u32` β€” the first byte of the group is the most-significant
1883+byte of the word.
1884+
1885+On the little-endian RISC-V bus the bytes within each register word appear
1886+reversed at byte addresses:
1887+
1888+```
1889+Fuse byte address: 0 1 2 3 4 5 6 7 8 9 A B ...
1890+Byte value: D5 A8 9B 86 82 CF 0F AD 80 60 E5 02 ...
1891+ ── register[0] ── ─── register[1] ── ── register[2] ──
1892+```
1893+
1894+##### Manufacturing debug unlock token: step-by-step
1895+
1896+1. Choose a 32-byte random secret (the raw token). This is what the SoC sends
1897+ over the mailbox to unlock debug.
1898+
1899+2. Compute SHA-512 of the raw token:
1900+ ```
1901+ $ printf '\xd8\x92\x2c\x55\x79\x2b\x73\x7f\x29\x13\xf3\xe5\xcb\xe6\x54\x75' \
1902+ '\x62\x52\x01\x6e\xae\xe9\x63\xa1\xdd\x4e\x75\x3a\xf7\x87\xf0\x96' \
1903+ | openssl dgst -sha512 -binary | xxd -p -c 64
1904+ 869ba8d5ad0fcf8202e560803281da659812ffa2fc28c2d5154cb645ee0c38ec
1905+ 4fd9dd8bb0be7deb193f625381383a91ab40bd920fcd9425919e63723c0bf7a8
1906+ ```
1907+
1908+3. Split into 4-byte groups and interpret each as a big-endian `u32` to get the
1909+ fuse word values:
1910+ ```
1911+ Fuse [u32; 16] = {
1912+ 0x869BA8D5, 0xAD0FCF82, 0x02E56080, 0x3281DA65,
1913+ 0x9812FFA2, 0xFC28C2D5, 0x154CB645, 0xEE0C38EC,
1914+ 0x4FD9DD8B, 0xB0BE7DEB, 0x193F6253, 0x81383A91,
1915+ 0xAB40BD92, 0x0FCD9425, 0x919E6372, 0x3C0BF7A8,
1916+ }
1917+ ```
1918+
1919+4. MCU or SoC manager writes these 16 words into the `FUSE_MANUF_DEBUG_UNLOCK_TOKEN` registers from fuses.
1920+
1921+#### Architectural register: CPTRA_OWNER_PK_HASH (big-endian words)
1922+
1923+**CPTRA_OWNER_PK_HASH** (`[u32; 12]`) uses the same reversed-dword format as
1924+FUSE_VENDOR_PK_HASH. See
1925+[Public key hash byte ordering](#public-key-hash-byte-ordering-dword-reversal)
1926+for details and worked examples.
1927+
1928+##### Production debug unlock public key hashes: byte ordering
1929+
1930+The production debug unlock flow uses SHA2-384 hashes of the concatenated
1931+ECC and MLDSA public keys to authenticate debug unlock tokens. These hashes
1932+are stored in the MCI register bank at addresses computed from
1933+`SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET`.
1934+
1935+**Hash input construction:**
1936+
1937+The hash is SHA2-384 over the raw mailbox wire bytes of the concatenated
1938+ECC and MLDSA public keys from the `AUTH_DEBUG_UNLOCK_TOKEN` payload.
1939+The mailbox wire format for each key type is:
1940+
1941+- **ECC public key (96 bytes)**: Each 4-byte group of the X and Y
1942+ coordinates is **dword-reversed** from the standard OpenSSL output.
1943+
1944+ ```
1945+ openssl ec output: AB CD EF 01 23 45 67 89 ... (X, 48 bytes)
1946+ 11 22 33 44 55 66 77 88 ... (Y, 48 bytes)
1947+
1948+ Hash input (= mailbox wire bytes):
1949+ 01 EF CD AB 89 67 45 23 ... (X, dword-reversed)
1950+ 44 33 22 11 88 77 66 55 ... (Y, dword-reversed)
1951+ ```
1952+
1953+- **MLDSA public key (2592 bytes)**: The native MLDSA key bytes are
1954+ used **as-is** β€” no conversion.
1955+
1956+ ```
1957+ MLDSA keygen output: 72 C0 F1 3B 7D 93 7E 22 ...
1958+
1959+ Hash input (= mailbox wire bytes):
1960+ 72 C0 F1 3B 7D 93 7E 22 ... (identical)
1961+ ```
1962+
1963+To compute the same hash offline for fuse provisioning, reconstruct the
1964+mailbox wire bytes: dword-reverse the ECC coordinates, keep MLDSA native,
1965+concatenate, and hash:
1966+
1967+```
1968+ECC dword-reversed: 01 EF CD AB 89 67 45 23 ... (96 bytes)
1969+MLDSA native: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes)
1970+
1971+hash_input = ECC_dword_reversed || MLDSA_native_bytes (2688 bytes)
1972+
1973+$ openssl dgst -sha384 -binary combined.bin | xxd -p -c 48
1974+β†’ 3f7a2b91c4e8d0f5...
1975+```
1976+
1977+**Provisioning: OpenSSL example**
1978+
1979+To prepare `combined_keys.bin`, dword-reverse the ECC raw coordinates
1980+and concatenate with the native MLDSA key bytes. Then compute the hash:
1981+
1982+```
1983+$ openssl dgst -sha384 -binary combined_keys.bin | xxd -p -c 48
1984+3f7a2b91c4e8d0f5a1b2c3d4e5f60718293a4b5c6d7e8f90a0b1c2d3e4f5061728394a5b6c
1985+```
1986+
1987+Map the digest output to fuse register words β€” each 4-byte group becomes
1988+one `u32` fuse word (same convention as all other SHA digest fuses):
1989+
1990+```
1991+openssl output: 3f 7a 2b 91 c4 e8 d0 f5 a1 b2 c3 d4 ...
1992+ ~~~~~~~~~~~ ~~~~~~~~~~~ ~~~~~~~~~~~
1993+Fuse word[0]: 0x3F7A2B91 [1]: 0xC4E8D0F5 [2]: 0xA1B2C3D4 ...
1994+```
1995+
1996+Write these 12 words to the MCI register bank at offset:
1997+
1998+```
1999+SS_PROD_DEBUG_UNLOCK_AUTH_PK_HASH_REG_BANK_OFFSET + ((level - 1) * 48)
2000+```
2001+
2002+**Mailbox payload: preparing fields from OpenSSL output**
2003+
2004+The `AUTH_DEBUG_UNLOCK_TOKEN` mailbox command fields use the byte order
2005+conventions described in
2006+[Byte order of cryptographic fields](../../runtime/README.md#byte-order-of-cryptographic-fields).
2007+The table below summarizes how to convert OpenSSL tool output into the
2008+mailbox payload bytes for each field:
2009+
2010+- **ECC P-384 public key (big-endian words)**: Extract the raw X and Y
2011+ coordinates (48 bytes each) from the PEM key, then **dword-reverse**
2012+ each 4-byte group before writing to the mailbox.
2013+
2014+ ```
2015+ # Extract raw X||Y from PEM (96 bytes, big-endian):
2016+ $ openssl ec -pubin -in key.pem -outform DER 2>/dev/null \
2017+ | tail -c 96 | xxd -p -c 48
2018+
2019+ OpenSSL raw bytes: AB CD EF 01 23 45 67 89 ... (X, 48 bytes)
2020+ 11 22 33 44 55 66 77 88 ... (Y, 48 bytes)
2021+
2022+ Mailbox bytes: 01 EF CD AB 89 67 45 23 ... (X, dword-reversed)
2023+ 44 33 22 11 88 77 66 55 ... (Y, dword-reversed)
2024+ ```
2025+
2026+- **MLDSA-87 public key (little-endian words)**: Copy the raw key bytes
2027+ produced by an MLDSA implementation (e.g. OpenSSL 3.5+, `fips204` crate)
2028+ **directly** into the mailbox β€” no conversion needed.
2029+
2030+ ```
2031+ MLDSA key bytes: 72 C0 F1 3B 7D 93 7E 22 ... (2592 bytes)
2032+ Mailbox bytes: 72 C0 F1 3B 7D 93 7E 22 ... (identical)
2033+ ```
2034+
2035+- **ECC P-384 signature (big-endian words)**: Same treatment as the public
2036+ key β€” dword-reverse each 4-byte group of the R and S coordinates.
2037+
2038+- **MLDSA-87 signature (little-endian words)**: Copy raw signature bytes
2039+ directly β€” no conversion needed. The trailing byte (byte 4628) is
2040+ reserved and should be zero.
2041+
2042+**Note:** The hash used for fuse provisioning is computed over the exact
2043+same bytes that appear on the mailbox wire. There is no additional
2044+transformation β€” the SHA accelerator's internal endianness handling is
2045+transparent and produces `SHA384(wire_bytes)`. Therefore the provisioning
2046+hash and the runtime verification hash are both computed over
2047+`ECC_dword_reversed || MLDSA_native`.
2048+
2049+#### SVN fuses (little-endian 128-bit bitmap)
2050+
2051+**FUSE_FIRMWARE_SVN** and **FUSE_SOC_MANIFEST_SVN** are 128-bit one-hot encoded bitmaps stored
2052+as `[u32; 4]`. These are **not** cryptographic values β€” the security version
2053+number equals the bit position of the highest set bit.
2054+
2055+The four words form a little-endian 128-bit integer: word\[0\] contains bits
2056+0–31, word\[1\] contains bits 32–63, and so on.
2057+
2058+Example β€” to program SVN 7, set bits 0 through 6:
2059+
2060+```
2061+FUSE_FIRMWARE_SVN[0] = 0x0000007F (bits 0-6 set)
2062+FUSE_FIRMWARE_SVN[1] = 0x00000000
2063+FUSE_FIRMWARE_SVN[2] = 0x00000000
2064+FUSE_FIRMWARE_SVN[3] = 0x00000000
2065+```
2066+
2067+Example β€” SVN 40 means bits 0 through 39 are set:
2068+
2069+```
2070+FUSE_FIRMWARE_SVN[0] = 0xFFFFFFFF (bits 0-31 set)
2071+FUSE_FIRMWARE_SVN[1] = 0x000000FF (bits 32-39 set)
2072+FUSE_FIRMWARE_SVN[2] = 0x00000000
2073+FUSE_FIRMWARE_SVN[3] = 0x00000000
2074+```
2075+
2076+#### Obfuscated seed fuses (big-endian words)
2077+
2078+**FUSE_UDS_SEED** (`[u32; 16]`), **FUSE_FIELD_ENTROPY** (`[u32; 8]`), and
2079+**FUSE_HEK_SEED** (`[u32; 8]`) are obfuscated secret values. They use the same
2080+**big-endian word** ordering as SHA digest fuses β€” each `u32` word maps to 4
2081+bytes in big-endian order.
2082+
2083+These values are consumed through an AES de-obfuscation step and are typically
2084+programmed by the manufacturing toolchain. If replicating values for test or
2085+simulation, use the same big-endian word convention when converting between byte
2086+arrays and `[u32; N]` arrays.
2087+
2088+#### Scalar and per-word fuses (no byte-ordering concern)
2089+
2090+The following fuse registers are single words or per-word indexed values with no
2091+multi-word byte ordering:
2092+
2093+| Register | Width | Notes |
2094+| --- | --- | --- |
2095+| FUSE_ECC_REVOCATION | 4 bits | Bitmask |
2096+| FUSE_LMS_REVOCATION | 32 bits | Bitmask |
2097+| FUSE_MLDSA_REVOCATION | 4 bits | Bitmask |
2098+| FUSE_ANTI_ROLLBACK_DISABLE | 1 bit | Boolean |
2099+| FUSE_PQC_KEY_TYPE | 2 bits | One-hot encoded |
2100+| FUSE_SOC_STEPPING_ID | 16 bits | Scalar |
2101+| FUSE_SOC_MANIFEST_MAX_SVN | 8 bits | Scalar |
2102+| FUSE_IDEVID_CERT_ATTR | 24 Γ— u32 | Per-word indexed; each word accessed individually |
2103+| FUSE_IDEVID_MANUF_HSM_ID | 4 Γ— u32 | Opaque identifier, used as-is |
2104+
11782105
11792106 ## Preamble validation steps
11802107