blob: 746d21e30c8a265dabf9dd5b0349d7ffa81b7d8e [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// KMAC control and padding logic
`include "prim_assert.sv"
module kmac_core
import kmac_pkg::*;
#(
// EnMasking: Enable masking security hardening inside keccak_round
// If it is enabled, the result digest will be two set of 1600bit.
parameter bit EnMasking = 0,
localparam int Share = (EnMasking) ? 2 : 1 // derived parameter
) (
input clk_i,
input rst_ni,
// From Message FIFO
input fifo_valid_i,
input [MsgWidth-1:0] fifo_data_i [Share],
input [MsgStrbW-1:0] fifo_strb_i,
output logic fifo_ready_o,
// to SHA3 Core
output logic msg_valid_o,
output logic [MsgWidth-1:0] msg_data_o [Share],
output logic [MsgStrbW-1:0] msg_strb_o,
input msg_ready_i,
// Configurations
// If kmac_en is cleared, Core logic doesn't function but forward incoming
// message to SHA3 core
input kmac_en_i,
input sha3_pkg::sha3_mode_e mode_i,
input sha3_pkg::keccak_strength_e strength_i,
// Key input from CSR
input [MaxKeyLen-1:0] key_data_i [Share],
input key_len_e key_len_i,
// Controls : same to SHA3 core
input start_i,
input process_i,
input prim_mubi_pkg::mubi4_t done_i,
// Control to SHA3 core
output logic process_o,
// Life cycle
input lc_ctrl_pkg::lc_tx_t lc_escalate_en_i,
output logic sparse_fsm_error_o,
output logic key_index_error_o
);
import sha3_pkg::KeccakMsgAddrW;
import sha3_pkg::KeccakCountW;
import sha3_pkg::KeccakRate;
import sha3_pkg::L128;
import sha3_pkg::L224;
import sha3_pkg::L256;
import sha3_pkg::L384;
import sha3_pkg::L512;
/////////////////
// Definitions //
/////////////////
// Encoding generated with:
// $ ./util/design/sparse-fsm-encode.py -d 3 -m 5 -n 6 \
// -s 401658243 --language=sv
//
// Hamming distance histogram:
//
// 0: --
// 1: --
// 2: --
// 3: |||||||||||||||||||| (50.00%)
// 4: |||||||||||||||| (40.00%)
// 5: |||| (10.00%)
// 6: --
//
// Minimum Hamming distance: 3
// Maximum Hamming distance: 5
// Minimum Hamming weight: 1
// Maximum Hamming weight: 4
//
localparam int StateWidth = 6;
typedef enum logic [StateWidth-1:0] {
StKmacIdle = 6'b011000,
// Secret Key pushing stage
// The key is sliced by prim_slicer. This state pushes the sliced data into
// SHA3 hashing engine. When it hits the block size limit,
// (same as in sha3pad) the state machine moves to Message.
StKey = 6'b010111,
// Incoming Message
// The core does nothing but forwarding the incoming message to SHA3 hashing
// engine by turning off `en_kmac_datapath`.
StKmacMsg = 6'b001110,
// Wait till done signal
StKmacFlush = 6'b101011,
// Terminal Error
StTerminalError = 6'b100000
} kmac_st_e ;
/////////////
// Signals //
/////////////
// represents encode_string(K)
logic [MaxEncodedKeyW-1:0] encoded_key [Share];
// Key slice address
// This signal controls the 64 bit output of the sliced secret_key.
logic [sha3_pkg::KeccakMsgAddrW-1:0] key_index;
logic inc_keyidx, clr_keyidx;
// `sent_blocksize` indicates that the encoded key is sent to sha3 hashing
// engine. If this hits at StKey stage, the state moves to message state.
logic [sha3_pkg::KeccakCountW-1:0] block_addr_limit;
logic sent_blocksize;
// Internal message signals
logic kmac_valid ;
logic [MsgWidth-1:0] kmac_data [Share];
logic [MsgStrbW-1:0] kmac_strb ;
// Control SHA3 core
// `kmac_process` is to forward the process signal to SHA3 core only after
// the KMAC core writes the key block in case of the message is empty.
// If the incoming message is empty, there's chance that the `process_i`
// signal can be asserted while KMAC core processing the key block.
logic kmac_process, process_latched;
// Indication of Secret key write stage. Only in this stage, the internal
// message interface is active.
logic en_key_write;
logic en_kmac_datapath;
// Encoded key has wider bits. `key_sliced` is the data to send to sha3
logic [MsgWidth-1:0] key_sliced [Share];
sha3_pkg::sha3_mode_e unused_mode;
assign unused_mode = mode_i;
/////////
// FSM //
/////////
kmac_st_e st, st_d;
// State register
`PRIM_FLOP_SPARSE_FSM(u_state_regs, st_d, st, kmac_st_e, StKmacIdle)
// Next state and output logic
// SEC_CM: FSM.SPARSE
always_comb begin
st_d = st;
en_kmac_datapath = 1'b 0;
en_key_write = 1'b 0;
clr_keyidx = 1'b 0;
kmac_valid = 1'b 0;
kmac_process = 1'b 0;
sparse_fsm_error_o = 1'b 0;
unique case (st)
StKmacIdle: begin
if (kmac_en_i && start_i) begin
st_d = StKey;
end else begin
st_d = StKmacIdle;
end
end
// If State enters here, regardless of the `process_i`, the state writes
// full block size of the key into SHA3 hashing engine.
StKey: begin
en_kmac_datapath = 1'b 1;
en_key_write = 1'b 1;
if (sent_blocksize) begin
st_d = StKmacMsg;
kmac_valid = 1'b 0;
clr_keyidx = 1'b 1;
end else begin
st_d = StKey;
kmac_valid = 1'b 1;
end
end
StKmacMsg: begin
// If process is previously latched, it is sent to SHA3 here.
if (process_i || process_latched) begin
st_d = StKmacFlush;
kmac_process = 1'b 1;
end else begin
st_d = StKmacMsg;
end
end
StKmacFlush: begin
if (prim_mubi_pkg::mubi4_test_true_strict(done_i)) begin
st_d = StKmacIdle;
end else begin
st_d = StKmacFlush;
end
end
StTerminalError: begin
// this state is terminal
st_d = st;
sparse_fsm_error_o = 1'b 1;
end
default: begin
// this state is terminal
st_d = StTerminalError;
sparse_fsm_error_o = 1'b 1;
end
endcase
// SEC_CM: FSM.GLOBAL_ESC, FSM.LOCAL_ESC
// Unconditionally jump into the terminal error state
// if the life cycle controller triggers an escalation.
if (lc_escalate_en_i != lc_ctrl_pkg::Off) begin
st_d = StTerminalError;
end
end
//////////////
// Datapath //
//////////////
// DATA Mux depending on kmac_en
// When Key write happens, hold the FIFO request. so fifo_ready_o is tied to 0
assign msg_valid_o = (en_kmac_datapath) ? kmac_valid : fifo_valid_i;
assign msg_data_o = (en_kmac_datapath) ? kmac_data : fifo_data_i ;
assign msg_strb_o = (en_kmac_datapath) ? kmac_strb : fifo_strb_i ;
assign fifo_ready_o = (en_kmac_datapath) ? 1'b 0 : msg_ready_i ;
// secret key write request to SHA3 hashing engine is always full width write.
// KeyMgr is fixed 256 bit output. So `right_encode(256)` is 0x020100 --> strb 3
assign kmac_strb = (en_key_write ) ? '1 : '0;
assign kmac_data = (en_key_write) ? key_sliced : '{default:'0};
// Process is controlled by the KMAC core always.
// This is mainly to prevent process_i asserted while KMAC core is writing
// the secret key to SHA3 hashing engine (the empty message case)
assign process_o = (kmac_en_i) ? kmac_process : process_i ;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
process_latched <= 1'b 0;
end else if (process_i && !process_o) begin
process_latched <= 1'b 1;
end else if (process_o ||
prim_mubi_pkg::mubi4_test_true_strict(done_i)) begin
process_latched <= 1'b 0;
end
end
// bytepad(encode_string(K), 168 or 136) =====================================
// 1. Prepare left_encode(w)
// 2. Prepare left_encode(len(secret_key))
// 3. Concatenate left_encode(len(secret_key)) || secret_key
// 4. Concaatenate left_encode(w) || encode_string(secret_key)
// 5. Based on the address, slice out the data into MsgWidth bits
// left_encode(w): Same as used in sha3pad logic.
logic [15:0] encode_bytepad;
assign encode_bytepad = sha3_pkg::encode_bytepad_len(strength_i);
// left_encode(len(secret_key))
// encoded length is always byte size. Use MaxEncodedKeyLenByte parameter
// from kmac_pkg and add one more byte to indicate how many bytes used to
// represent len(secret_key)
// Note that if the secret_key is 128 bit, only lower 16 bits of
// `encode_keylen` are valid. Refer `encoded_key` concatenation logic below.
// As the encoded string in the spec big-endian, The endian swap is a must.
logic [MaxEncodedKeyLenSize + 8 - 1:0] encode_keylen [Share];
always_comb begin
// the spec mentioned the key length is encoded in left_encode()
// The number is represented in big-endian. For example:
// 384 ==> 0x02 0x01 0x80
// The first byte is the number of bytes to represent 384
// The second byte represents 2**8 number, which is 256 here.
// The third byte represents 2**0 number, which is 128.
// The data put into MsgFIFO is little-endian and SHA3(Keccak) processes in
// little-endian. So, below keylen swaps the byte order
unique case (key_len_i)
// endian-swapped key_length num_bytes
// Key128: encode_keylen[0] = {{<<8{MaxEncodedKeyLenSize'(128)}}, 8'h 01};
// Key192: encode_keylen[0] = {{<<8{MaxEncodedKeyLenSize'(192)}}, 8'h 01};
// Key256: encode_keylen[0] = {{<<8{MaxEncodedKeyLenSize'(256)}}, 8'h 02};
// Key384: encode_keylen[0] = {{<<8{MaxEncodedKeyLenSize'(384)}}, 8'h 02};
// Key512: encode_keylen[0] = {{<<8{MaxEncodedKeyLenSize'(512)}}, 8'h 02};
// Vivado does not support stream swap for non context value. So assign
// the value directly.
Key128: encode_keylen[0] = (MaxEncodedKeyLenSize+8)'('h 0080_01);
Key192: encode_keylen[0] = (MaxEncodedKeyLenSize+8)'('h 00C0_01);
Key256: encode_keylen[0] = (MaxEncodedKeyLenSize+8)'('h 0001_02);
Key384: encode_keylen[0] = (MaxEncodedKeyLenSize+8)'('h 8001_02);
Key512: encode_keylen[0] = (MaxEncodedKeyLenSize+8)'('h 0002_02);
default: encode_keylen[0] = '0;
endcase
end
if (EnMasking) begin: gen_encode_keylen_masked
assign encode_keylen[1] = '0;
end
// encode_string(secret_key): Concatenate key
// Based on the left_encode(len(secret_key)) size, the concatenation logic
// should be changed. If key length is 128 bit, only lower 16 bits of the
// encoded length are used so that the upper 8 bits are padded with 0 as
// defined in bytepad() function.
for (genvar i = 0 ; i < Share; i++) begin : gen_encoded_key
always_comb begin
unique case (key_len_i)
// In Key 128, 192 case, only lower parts of encode_keylen signal is
// used. So upper padding requires 8 more bits than MaxKeyLen - keylen
Key128: encoded_key[i] = {(8 + MaxKeyLen - 128)'(0),
key_data_i[i][0+:128],
encode_keylen[i][0+:MaxEncodedKeyLenSize]};
Key192: encoded_key[i] = {(8 + MaxKeyLen - 192)'(0),
key_data_i[i][0+:192],
encode_keylen[i][0+:MaxEncodedKeyLenSize]};
Key256: encoded_key[i] = {(MaxKeyLen - 256)'(0),
key_data_i[i][0+:256],
encode_keylen[i]};
Key384: encoded_key[i] = {(MaxKeyLen - 384)'(0),
key_data_i[i][0+:384],
encode_keylen[i]};
// Assume 512bit is the MaxKeyLen
Key512: encoded_key[i] = {key_data_i[i][0+:512],
encode_keylen[i]};
default: encoded_key[i] = '0;
endcase
end
end : gen_encoded_key
// Above logic assumes MaxKeyLen as 512 bits. Revise if it is not.
`ASSERT_INIT(MaxKeyLenMatchToKey512_A, kmac_pkg::MaxKeyLen == 512)
// Combine the bytepad `left_encode(w)` and the `encode_string(secret_key)`
logic [MaxEncodedKeyW + 16 -1 :0] encoded_key_block [Share];
assign encoded_key_block[0] = {encoded_key[0], encode_bytepad};
if (EnMasking) begin : gen_encoded_key_block_masked
assign encoded_key_block[1] = {encoded_key[1], 16'h 0};
end
// Slicer to slice out 64 bits
for (genvar i = 0 ; i < Share ; i++) begin : gen_key_slicer
prim_slicer #(
.InW (MaxEncodedKeyW+16),
.IndexW(KeccakMsgAddrW),
.OutW(MsgWidth)
) u_key_slicer (
.sel_i (key_index),
.data_i (encoded_key_block[i]),
.data_o (key_sliced[i])
);
end
// `key_index` logic
// key_index is used to select MsgWidth data from long `encoded_key_block`
// It behaves same as `keccak_addr` or `prefix_index` in sha3pad module.
assign inc_keyidx = kmac_valid & msg_ready_i ;
// This primitive is used to place a hardened counter
// SEC_CM: CTR.REDUN
prim_count #(
.Width(sha3_pkg::KeccakMsgAddrW)
) u_key_index_count (
.clk_i,
.rst_ni,
.clr_i(clr_keyidx),
.set_i(1'b0),
.set_cnt_i('0),
.incr_en_i(inc_keyidx),
.decr_en_i(1'b0),
.step_i(sha3_pkg::KeccakMsgAddrW'(1)),
.cnt_o(key_index),
.cnt_next_o(),
.err_o(key_index_error_o)
);
// Block size based on the address.
// This is used for bytepad() and also pad10*1()
// assign block_addr_limit = KeccakRate[strength_i];
// but below is easier to understand
always_comb begin
unique case (strength_i)
L128: block_addr_limit = KeccakCountW'(KeccakRate[L128]);
L224: block_addr_limit = KeccakCountW'(KeccakRate[L224]);
L256: block_addr_limit = KeccakCountW'(KeccakRate[L256]);
L384: block_addr_limit = KeccakCountW'(KeccakRate[L384]);
L512: block_addr_limit = KeccakCountW'(KeccakRate[L512]);
default: block_addr_limit = '0;
endcase
end
assign sent_blocksize = (key_index == block_addr_limit);
// Encoded Output Length =====================================================
//
// KMAC(K,X,L,S) := cSHAKE(newX,L,"KMAC",S)
// K : Secret Key
// X : Input Message
// L : Output Length
// S : Customization input string
// newX = bytepad(encode_string(key), 168or136) || X || right_encode(L)
//
// Software writes desired output length as encoded value into the message
// FIFO at the end of the message prior to set !!CMD.process.
////////////////
// Assertions //
////////////////
// If process_latched is set, then at Message state, it should be cleared
`ASSERT(ProcessLatchedCleared_A,
st == StKmacMsg && process_latched |=> !process_latched)
// Assume configuration is stable during the operation
`ASSUME(KmacEnStable_M, $changed(kmac_en_i) |-> st inside {StKmacIdle, StTerminalError})
`ASSUME(ModeStable_M, $changed(mode_i) |-> st inside {StKmacIdle, StTerminalError})
`ASSUME(StrengthStable_M,
$changed(strength_i) |->
(st inside {StKmacIdle, StTerminalError}) ||
($past(st) == StKmacIdle))
`ASSUME(KeyLengthStable_M,
$changed(key_len_i) |->
(st inside {StKmacIdle, StTerminalError}) ||
($past(st) == StKmacIdle))
`ASSUME(KeyDataStable_M,
$changed(key_data_i) |->
(st inside {StKmacIdle, StTerminalError}) ||
($past(st) == StKmacIdle))
// no acked to MsgFIFO in StKmacMsg
`ASSERT(AckOnlyInMessageState_A,
fifo_valid_i && fifo_ready_o && kmac_en_i |-> st == StKmacMsg)
endmodule : kmac_core