OTBN connects to other components in an OpenTitan system. This section lists requirements on those interfaces that go beyond the physical connectivity.
OTBN has two EDN connections: edn_urnd
and edn_rnd
. What kind of randomness is provided on the EDN connections is configurable at runtime, but unknown to OTBN. To maintain its security properties, OTBN requires the following configuration for the two EDN connections:
edn_urnd
. For performance reasons, requests on this EDN connection should be answered quickly.edn_rnd
must provide AIS31-compliant class PTG.3 random numbers. The randomness from this interface is made available through the RND
WSR and intended to be used for key generation.The OTBN processor core has access to two dedicated memories: an instruction memory (IMEM), and a data memory (DMEM). Each memory is 4 kiB in size.
The memory layout follows the Harvard architecture. Both memories are byte-addressed, with addresses starting at 0.
The instruction memory (IMEM) is 32b wide and provides the instruction stream to the OTBN processor. It cannot be read from or written to by user code through load or store instructions.
The data memory (DMEM) is 256b wide and read-write accessible from the base and big number instruction subsets of the OTBN processor core. There are four instructions that can access data memory. In the base instruction subset, there are {{#otbn-insn-ref LW}} (load word) and {{#otbn-insn-ref SW}} (store word). These access 32b-aligned 32b words. In the big number instruction subset, there are {{#otbn-insn-ref BN.LID}} (load indirect) and {{#otbn-insn-ref BN.SID}} (store indirect). These access 256b-aligned 256b words.
Both memories can be accessed through OTBN's register interface (DMEM
and IMEM
). All memory accesses through the register interface must be word-aligned 32b word accesses.
When OTBN is in any state other than idle, reads return zero and writes have no effect. Furthermore, a memory access when OTBN is neither idle nor locked will cause OTBN to generate a fatal error with code ILLEGAL_BUS_ACCESS
. A host processor can check whether OTBN is busy by reading the STATUS
register.
The underlying memories used to implement the IMEM and DMEM may not grant all access requests (see Memory Scrambling for details). A request won‘t be granted if new scrambling keys have been requested for the memory that aren’t yet available. Functionally it should be impossible for either OTBN or a host processor to make a memory request whilst new scrambling keys are unavailable. OTBN is in the busy state whilst keys are requested so OTBN will not execute any programs and a host processor access will generated an ILLEGAL_BUS_ACCESS
fatal error. Should a request not be granted due to a fault, a BAD_INTERNAL_STATE
fatal error will be raised.
While DMEM is 4kiB, only the first 3kiB (at addresses 0x0
to 0xbff
) is visible through the register interface. This is to allow OTBN applications to store sensitive information in the other 1kiB, making it harder for that information to leak back to Ibex.
Each memory write through the register interface updates a checksum. See the Memory Load Integrity section for more details.
OTBN employs an instruction prefetch stage to enable pre-decoding of instructions to enable the blanking SCA hardening measure. Its operation is entirely transparent to software. It does not speculate and will only prefetch where the next instruction address can be known. This results in a stall cycle for all conditional branches and jumps as the result is neither predicted nor known ahead of time. Instruction bits held in the prefetch buffer are unscrambled but use the integrity protection described in Data Integrity Protection.
OTBN is connected to the Entropy Distribution Network (EDN) which can provide random numbers via the RND
and URND
CSRs and WSRs.
RND
provides bits taken directly from the EDN connected via edn_rnd
. The EDN interface provides 32b of entropy per transaction and comes from a different clock domain to the OTBN core. A FIFO is used to synchronize the incoming package to the OTBN clock domain. Synchronized packages are then set starting from bottom up to a single WLEN
value of 256b. In order to service a single EDN request, a total of 8 transactions are required from EDN interface.
The RND
CSR and WSR take their bits from the same source. A read from the RND
CSR returns the bottom 32b; the other 192b are discarded. On a read from the RND
CSR or WSR, OTBN will stall while it waits for data. It will resume execution on the cycle after it receives the final word of data from the EDN.
As an EDN request can take time, RND
is backed by a single-entry cache containing the result of the most recent EDN request in OTBN core level. Writing any value to the RND_PREFETCH
CSR initiates a prefetch. This requests data from the EDN, storing it in the cache, and can hide the EDN latency. Writes to RND_PREFETCH
will be ignored whilst a prefetch is in progress or when the cache is already full. If the cache is full, a read from RND
returns immediately with the contents of the cache, which is then emptied. If the cache is not full, a read from RND
will block as described above until OTBN receives the final word of data from the EDN.
OTBN discards any data that is in the cache at the start of an operation. If there is still a pending prefetch when an OTBN operation starts, the results of the prefetch will also discarded.
URND
provides bits from a local XoShiRo256++ PRNG within OTBN; reads from it never stall. This PRNG is seeded once from the EDN connected via edn_urnd
when OTBN starts execution. Each new execution of OTBN will reseed the URND
PRNG. The PRNG state is advanced every cycle when OTBN is running.
The PRNG has a long cycle length but has a fixed point: the sequence of numbers will get stuck if the state ever happens to become zero. This will never happen in normal operation. If a fault causes the state to become zero, OTBN raises a BAD_INTERNAL_STATE
fatal error.
OTBN can be in different operational states. After reset (init), OTBN performs a secure wipe of the internal state and then becomes idle. OTBN is busy for as long it is performing an operation. OTBN is locked if a fatal error was observed or after handling an RMA request.
The current operational state is reflected in the STATUS
register.
STATUS
register is set to BUSY_SEC_WIPE_INT
.STATUS
register is set to IDLE
.STATUS
register is set to one of the values starting with BUSY_
.STATUS
register is set to LOCKED
.OTBN transitions into the busy state as result of host software issuing a command; OTBN is then said to perform an operation. OTBN transitions out of the busy state whenever the operation has completed. In the STATUS
register the different BUSY_*
values represent the operation that is currently being performed.
A transition out of the busy state is signaled by the done
interrupt (INTR_STATE.done
).
The locked state is a terminal state; transitioning out of it requires an OTBN reset.
OTBN understands a set of commands to perform certain operations. Commands are issued by writing to the CMD
register.
The EXECUTE
command starts the execution of the application contained in OTBN's instruction memory.
The SEC_WIPE_DMEM
command securely wipes the data memory.
The SEC_WIPE_IMEM
command securely wipes the instruction memory.
Software execution on OTBN is triggered by host software by issuing the EXECUTE
command. The software then runs to completion, without the ability for host software to interrupt or inspect the execution.
STATUS
to BUSY_EXECUTE
.URND
CSR and WSR, is re-seeded from the EDN.ERR_BITS
register is set to indicate either a successful execution (value 0
), or to indicate the error that was observed (a non-zero value).done
interrupt (INTR_STATE.done
), and reflected in the STATUS
register.OTBN is able to detect a range of errors, which are classified as software errors or fatal errors. A software error is an error in the code that OTBN executes. In the absence of an attacker, these errors are due to a programmer's mistake. A fatal error is typically the violation of a security property. All errors and their classification are listed in the List of Errors.
Whenever an error is detected, OTBN reacts locally, and informs the OpenTitan system about it by raising an alert. OTBN generally does not try to recover from errors itself, and provides no error handling support to code that runs on it.
OTBN gives host software the option to recover from some errors by restarting the operation. All software errors are treated as recoverable, unless CTRL.software_errs_fatal
is set, and are handled as described in the section Reaction to Recoverable Errors. When CTRL.software_errs_fatal
is set, software errors become fatal errors.
Fatal errors are treated as described in the section Reaction to Fatal Errors.
Recoverable errors can be the result of a programming error in OTBN software. Recoverable errors can only occur during the execution of software on OTBN, and not in other situations in which OTBN might be busy.
The following actions are taken when OTBN detects a recoverable error:
ERR_BITS
register is set to a non-zero value that describes the error.INTR_STATE.done
.STATUS
register is set to IDLE
.The host software can start another operation on OTBN after a recoverable error was detected.
Fatal errors are generally seen as a sign of an intrusion, resulting in more drastic measures to protect the secrets stored within OTBN. Fatal errors can occur at any time, even when an OTBN operation isn't in progress.
The following actions are taken when OTBN detects a fatal error:
ERR_BITS
register is set to a non-zero value that describes the error.INTR_STATE.done
.STATUS
register is set to LOCKED
.Note that OTBN can detect some errors even when it isn‘t running. One example of this is an error caused by an integrity error when reading or writing OTBN’s memories over the bus. In this case, the ERR_BITS
register will not change. This avoids race conditions with the host processor‘s error handling software. However, every error that OTBN detects when it isn’t running is fatal. This means that the cause will be reflected in FATAL_ALERT_CAUSE
, as described below in Alerts. This way, no alert is generated without setting an error code somewhere.
An alert is a reaction to an error that OTBN detected. OTBN has two alerts, one recoverable and one fatal.
A recoverable alert is a one-time triggered alert caused by recoverable errors. The error that caused the alert can be determined by reading the ERR_BITS
register.
A fatal alert is a continuously triggered alert caused by fatal errors. The error that caused the alert can be determined by reading the FATAL_ALERT_CAUSE
register. If OTBN was running, this value will also be reflected in the ERR_BITS
register. A fatal alert can only be cleared by resetting OTBN through the rst_ni
line.
The host CPU can clear the ERR_BITS
when OTBN is not running. Writing any value to ERR_BITS
clears this register to zero. Write attempts while OTBN is running are ignored.
OTBN receives and reacts to escalation signals from the life cycle controller. An incoming life cycle escalation is a fatal error of type lifecycle_escalation
and treated as described in the section Fatal Errors.
OTBN exposes a single-bit idle_o
signal, intended to be used by the clock manager to clock-gate the block when it is not in use. This signal is in the same clock domain as clk_i
. The idle_o
signal is high when OTBN is idle, and low otherwise.
OTBN also exposes another version of the idle signal as idle_otp_o
. This works analogously, but is in the same clock domain as clk_otp_i
.
TODO: Specify interactions between idle_o
, idle_otp_o
and the clock manager fully.
OTBN stores and operates on data (state) in its dedicated memories, register files, and internal registers. OTBN's data integrity protection is designed to protect all data stored and transmitted within OTBN from modifications through physical attacks.
During transmission, the integrity of data is protected with an integrity protection code. Data at rest in the instruction and data memories is additionally scrambled.
In the following, the Integrity Protection Code and the scrambling algorithm are discussed, followed by their application to individual storage elements.
OTBN uses the same integrity protection code everywhere to provide overarching data protection without regular re-encoding. The code is applied to 32b data words, and produces 39b of encoded data.
The code used is an (39,32) Hsiao “single error correction, double error detection” (SECDED) error correction code (ECC) [CHEN08]. It has a minimum Hamming distance of four, resulting in the ability to detect at least three errors in a 32 bit word. The code is used for error detection only; no error correction is performed.
Contents of OTBN's instruction and data memories are scrambled while at rest. The data is bound to the address and scrambled before being stored in memory. The addresses are randomly remapped.
Note that data stored in other temporary memories within OTBN, including the register files, is not scrambled.
Scrambling is used to obfuscate the memory contents and to diffuse the data. Obfuscation makes passive probing more difficult, while diffusion makes active fault injection attacks more difficult.
The scrambling mechanism is described in detail in the section “Scrambling Primitive” of the SRAM Controller Technical Specification.
When OTBN comes out of reset, its memories have default scrambling keys. The host processor can request new keys for each memory by issuing a secure wipe of DMEM and a secure wipe of IMEM.
A fatal error is raised whenever a data integrity violation is detected, which results in an immediate stop of all processing and the issuing of a fatal alert. The section Error Handling and Reporting describes the error handling in more detail.
OTBN contains two register files: the 32b GPRs and the 256b WDRs. The data stored in both register files is protected with the Integrity Protection Code. Neither the register file contents nor register addresses are scrambled.
The GPRs x2
to x31
store a 32b data word together with the Integrity Protection Code, resulting in 39b of stored data. (x0
, the zero register, and x1
, the call stack, require special treatment.)
Each 256b Wide Data Register (WDR) stores a 256b data word together with the Integrity Protection Code, resulting in 312b of stored data. The integrity protection is done separately for each of the eight 32b sub-words within a 256b word.
The register files can consume data protected with the Integrity Protection Code, or add it on demand. Whenever possible the Integrity Protection Code is preserved from its source and written directly to the register files without recalculation, in particular in the following cases:
ACC
and MOD
WSRs and a WDR. (TODO: Not yet implemented.)MOD0
to MOD7
CSRs and a GPR. (TODO: Not yet implemented.)In all other cases the register files add the Integrity Protection Code to the incoming data before storing the data word.
The integrity protection bits are checked on every read from the register files, even if the integrity protection is not removed from the data.
Detected integrity violations in a register file raise a fatal reg_error
.
OTBN's data memory is 256b wide, but allows for 32b word accesses. To facilitate such accesses, all integrity protection in the data memory is done on a 32b word granularity.
All data entering or leaving the data memory block is protected with the Integrity Protection Code; this code is not re-computed within the memory block.
Before being stored in SRAM, the data word with the attached Integrity Protection Code, as well as the address are scrambled according to the memory scrambling algorithm. The scrambling is reversed on a read.
The ephemeral memory scrambling key and the nonce are provided by the OTP block. They are set once when OTBN block is reset, and changed whenever a secure wipe of the data memory is performed.
The Integrity Protection Code is checked on every memory read, even though the code remains attached to the data. A further check must be performed when the data is consumed. Detected integrity violations in the data memory raise a fatal dmem_error
.
All data entering or leaving the instruction memory block is protected with the Integrity Protection Code; this code is not re-computed within the memory block.
Before being stored in SRAM, the instruction word with the attached Integrity Protection Code, as well as the address are scrambled according to the memory scrambling algorithm. The scrambling is reversed on a read.
The ephemeral memory scrambling key and the nonce are provided by the OTP block. They are set once when OTBN block is reset, and changed whenever a secure wipe of the instruction memory is performed.
The Integrity Protection Code is checked on every memory read, even though the code remains attached to the data. A further check must be performed when the data is consumed. Detected integrity violations in the data memory raise a fatal imem_error
.
As well as the integrity protection discussed above for the memories and bus interface, OTBN has a second layer of integrity checking to allow a host processor to ensure that a program has been loaded correctly. This is visible through the LOAD_CHECKSUM
register. The register exposes a cumulative CRC checksum which is updated on every write to either memory.
This is intended as a light-weight way to implement a more efficient “write and read back” check. It isn't a cryptographically secure MAC, so cannot spot an attacker who can completely control the bus. However, in this case the attacker would be equally able to control responses from OTBN, so any such check could be subverted.
The CRC used is the 32-bit CRC-32-IEEE checksum. This standard choice of generating polynomial makes it compatible with other tooling and libraries, such as the crc32 function in the python ‘binascii’ module and the crc instructions in the RISC-V bitmanip specification [SYMBIOTIC21]. The stream over which the checksum is computed is the stream of writes that have been seen since the last write to LOAD_CHECKSUM
. Each write is treated as a 48b value, {imem, idx, wdata}
. Here, imem
is a single bit flag which is one for writes to IMEM and zero for writes to DMEM. The idx
value is the index of the word within the memory, zero extended from 10b to 15b. Finally, wdata
is the 32b word that was written. Writes that are less than 32b or not aligned on a 32b boundary are ignored and not factored into the CRC calculation.
The host processor can also write to the register. Typically, this will be to clear the value to 32'h00000000
, the traditional starting value for a 32-bit CRC. Note the internal representation of the CRC is inverted from the register visible version. This is done to maintain compatibility with existing CRC-32-IEEE tooling and libraries.
To use this functionality, the host processor should set LOAD_CHECKSUM
to a known value (traditionally, 32'h00000000
). Next, it should write the program to be loaded to OTBN's IMEM and DMEM over the bus. Finally, it should read back the value of LOAD_CHECKSUM
and compare it with an expected value.
Applications running on OTBN may store sensitive data in the internal registers or the memory. In order to prevent an untrusted application from reading any leftover data, OTBN provides the secure wipe operation. This operation can be applied to:
The three forms of secure wipe can be triggered in different ways.
A secure wipe of either the instruction or the data memory can be triggered from from host software by issuing a SEC_WIPE_DMEM
or SEC_WIPE_IMEM
command.
A secure wipe of instruction memory, data memory, and all internal state is performed automatically when handling a fatal error. In addition, it can be triggered by the Life Cycle Controller before RMA entry using the lc_rma_req/ack
interface. In both cases OTBN enters the locked state afterwards and needs to be reset.
A secure wipe of the internal state only is triggered automatically after reset and when OTBN ends the software execution, either successfully, or unsuccessfully due to a recoverable error.
If OTBN cannot complete a secure wipe of the internal state (e.g., due to failing to obtain the required randomness), it immediately becomes locked. In this case, OTBN must be reset and will then retry the secure wipe. The secure wipe after reset must succeed before OTBN can be used.
The wiping is performed by securely replacing the memory scrambling key, making all data stored in the memory unusable. The key replacement is a two-step process:
Host software can initiate a data memory secure wipe by issuing the SEC_WIPE_DMEM
command.
The wiping is performed by securely replacing the memory scrambling key, making all instructions stored in the memory unusable. The key replacement is a two-step process:
Host software can initiate a data memory secure wipe by issuing the SEC_WIPE_IMEM
command.
OTBN provides a mechanism to securely wipe all internal state, excluding the instruction and data memories.
The following state is wiped:
The wiping procedure is a two-step process:
Note that after internal secure wipe, the state of registers is undefined. In order to prevent mismatches between ISS and RTL, software needs to initialise a register with a full-word write before using its value.
Loop and call stack pointers are reset.
Host software cannot explicitly trigger an internal secure wipe; it is performed automatically after reset and at the end of an EXECUTE
operation.
OTBN is a specialized coprocessor which is used from the host CPU. This section describes how to interact with OTBN from the host CPU to execute an existing OTBN application. The section Writing OTBN applications describes how to write such applications.
The high-level sequence by which the host processor should use OTBN is as follows.
LOAD_CHECKSUM
.IMEM
, starting at address 0.DMEM
window.LOAD_CHECKSUM
and perform an integrity check.EXECUTE
command. Now neither data nor instruction memory may be accessed from the host CPU. After it has been started the OTBN application runs to completion without further interaction with the host.ERR_BITS
register.DMEM
, as mandated by the calling convention of the loaded application.OTBN applications are run to completion. The host CPU can determine if an application has completed by either polling STATUS
or listening for an interrupt.
STATUS
register. The operation is complete if STATUS
is IDLE
or LOCKED
, otherwise the operation is in progress. When STATUS
has become LOCKED
a fatal error has occurred and OTBN must be reset to perform further operations.done
interrupt to determine if the operation has completed. The standard sequence of working with interrupts has to be followed, i.e. the interrupt has to be enabled, an interrupt service routine has to be registered, etc. The DIF contains helpers to do so conveniently.Note: This operation sequence only covers functional aspects. Depending on the application additional steps might be necessary, such as deleting secrets from the memories.
A higher-level driver for the OTBN block is available at sw/device/lib/runtime/otbn.h
(API documentation).
Another driver for OTBN is part of the silicon creator code at sw/device/silicon_creator/lib/drivers/otbn.h
.
OTBN applications are (small) pieces of software written in OTBN assembly. The full instruction set is described in the ISA manual, and example software is available in the sw/otbn
directory of the OpenTitan source tree.
A hands-on user guide to develop OTBN software can be found in the section Writing and building software for OTBN.
OTBN comes with a toolchain consisting of an assembler, a linker, and helper tools such as objdump. The toolchain wraps a RV32 GCC toolchain and supports many of its features.
The following tools are available:
otbn_as.py
: The OTBN assembler.otbn_ld.py
: The OTBN linker.otbn_objdump.py
: objdump for OTBN.Other tools from the RV32 toolchain can be used directly, such as objcopy.
Passing data between the host CPU and OTBN is done through the first 2kiB of data memory (DMEM). No standard or required calling convention exists, every application is free to pass data in and out of OTBN in whatever format it finds convenient. All data passing must be done when OTBN is idle; otherwise both the instruction and the data memory are inaccessible from the host CPU.
The software running on OTBN signals completion by executing the {{#otbn-insn-ref ECALL}} instruction.
Once OTBN has executed the {{#otbn-insn-ref ECALL}} instruction, the following things happen:
ERR_BITS
register is set to 0, indicating a successful operation.INTR_STATE.done
and clearing STATUS
.The first 2kiB of DMEM can be used to pass data back to the host processor, e.g. a “return value” or an “exit code”. Refer to the section Passing of data between the host CPU and OTBN for more information.
OTBN provides two hardware loop instructions: {{#otbn-insn-ref LOOP}} and {{#otbn-insn-ref LOOPI}}.
OTBN permits loop nesting and branches and jumps inside loops. However, it doesn‘t have support for early termination of loops: there’s no way to pop an entry from the loop stack without executing the last instruction of the loop the correct number of times. It can also only pop one level of the loop stack per instruction.
To avoid polluting the loop stack and avoid surprising behaviour, the programmer must ensure that:
OTBN does not detect these conditions being violated, so no error will be signaled should they occur.
(Note indentation in the code examples is for clarity and has no functional impact.)
The following loops are well nested:
LOOP x2, 3 LOOP x3, 1 ADDI x4, x4, 1 # The NOP ensures that the outer and inner loops end on different instructions NOP # Both inner and outer loops call some_fn, which returns to # the body of the loop LOOP x2, 5 JAL x1, some_fn LOOP x3, 2 JAL x1, some_fn ADDI x4, x4, 1 NOP # Control flow leaves the immediate body of the outer loop but eventually # returns to it LOOP x2, 4 BEQ x4, x5, some_label branch_back: LOOP x3, 1 ADDI x6, x6, 1 NOP some_label: ... JAL x0, branch_back
The following loops are not well nested:
# Both loops end on the same instruction LOOP x2, 2 LOOP x3, 1 ADDI x4, x4, 1 # Inner loop jumps into outer loop body (executing the outer loop end # instruction before the inner loop has finished) LOOP x2, 5 LOOP x3, 3 ADDI x4, x4 ,1 BEQ x4, x5, outer_body ADD x6, x7, x8 outer_body: SUBI x9, x9, 1
The big number instruction subset of OTBN generally operates on WLEN bit numbers. {{#otbn-insn-ref BN.MULQACC}} operates with WLEN/4 bit operands (with a full WLEN accumulator). This section outlines two techniques to perform larger multiplies by composing multiple {{#otbn-insn-ref BN.MULQACC}} instructions.
This instruction sequence multiplies the lower half of w0
by the upper half of w0
placing the result in w1
.
BN.MULQACC.Z w0.0, w0.2, 0 BN.MULQACC w0.0, w0.3, 64 BN.MULQACC w0.1, w0.2, 64 BN.MULQACC.WO w1, w0.1, w0.3, 128
The shift out functionality can be used to perform larger multiplications without extra adds. The table below shows how two registers w0
and w1
can be multiplied together to give a result in w2
and w3
. The cells on the right show how the result is built up a0:a3 = w0.0:w0.3
and b0:b3 = w1.0:w1.3
. The sum of a column represents WLEN/4 bits of a destination register, where c0:c3 = w2.0:w2.3
and d0:d3 = w3.0:w3.3
. Each cell with a multiply in takes up two WLEN/4-bit columns to represent the WLEN/2-bit multiply result. The current accumulator in each instruction is represented by highlighted cells where the accumulator value will be the sum of the highlighted cell and all cells above it.
The outlined technique can be extended to arbitrary bit widths but requires unrolled code with all operands in registers.
Code snippets giving examples of 256x256 and 384x384 multiplies can be found in sw/otbn/code-snippets/mul256.s
and sw/otbn/code-snippets/mul384.s
.
[CHEN08] L. Chen, “Hsiao-Code Check Matrices and Recursively Balanced Matrices,” arXiv:0803.1217 [cs], Mar. 2008 [Online]. Available: http://arxiv.org/abs/0803.1217
[SYMBIOTIC21] RISC-V Bitmanip Extension v0.93 Available: https://github.com/riscv/riscv-bitmanip/releases/download/v0.93/bitmanip-0.93.pdf