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