| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // This module is an implementation of the 64bit PRINCE block cipher. It is a fully unrolled |
| // combinational implementation with configurable number of rounds. Optionally, registers for the |
| // data and key states can be enabled, if this is required. Due to the reflective construction of |
| // this cipher, the same circuit can be used for encryption and decryption, as described below. |
| // Further, the primitive supports a 32bit block cipher flavor which is not specified in the |
| // original paper. It should be noted, however, that the 32bit version is **not** secure and must |
| // not be used in a setting where cryptographic cipher strength is required. The 32bit variant is |
| // only intended to be used as a lightweight data scrambling device. |
| // |
| // See also: prim_present, prim_cipher_pkg |
| // |
| // References: - https://en.wikipedia.org/wiki/PRESENT |
| // - https://en.wikipedia.org/wiki/Prince_(cipher) |
| // - http://www.lightweightcrypto.org/present/present_ches2007.pdf |
| // - https://csrc.nist.gov/csrc/media/events/lightweight-cryptography-workshop-2015/ |
| // documents/papers/session7-maene-paper.pdf |
| // - https://eprint.iacr.org/2012/529.pdf |
| // - https://eprint.iacr.org/2015/372.pdf |
| // - https://eprint.iacr.org/2014/656.pdf |
| |
| `include "prim_assert.sv" |
| module prim_prince #( |
| parameter int DataWidth = 64, |
| parameter int KeyWidth = 128, |
| // The construction is reflective. Total number of rounds is 2*NumRoundsHalf + 2 |
| parameter int NumRoundsHalf = 5, |
| // This primitive uses the new key schedule proposed in https://eprint.iacr.org/2014/656.pdf |
| // Setting this parameter to 1 falls back to the original key schedule. |
| parameter bit UseOldKeySched = 1'b0, |
| // This instantiates a data register halfway in the primitive. |
| parameter bit HalfwayDataReg = 1'b0, |
| // This instantiates a key register halfway in the primitive. |
| parameter bit HalfwayKeyReg = 1'b0 |
| ) ( |
| input clk_i, |
| input rst_ni, |
| |
| input valid_i, |
| input [DataWidth-1:0] data_i, |
| input [KeyWidth-1:0] key_i, |
| input dec_i, // set to 1 for decryption |
| output logic valid_o, |
| output logic [DataWidth-1:0] data_o |
| ); |
| |
| /////////////////// |
| // key expansion // |
| /////////////////// |
| |
| logic [DataWidth-1:0] k0, k0_prime_d, k1_d, k0_new_d, k0_prime_q, k1_q, k0_new_q; |
| always_comb begin : p_key_expansion |
| k0 = key_i[2*DataWidth-1 : DataWidth]; |
| k0_prime_d = {k0[0], k0[DataWidth-1:2], k0[DataWidth-1] ^ k0[1]}; |
| k1_d = key_i[DataWidth-1:0]; |
| |
| // modify key for decryption |
| if (dec_i) begin |
| k0 = k0_prime_d; |
| k0_prime_d = key_i[2*DataWidth-1 : DataWidth]; |
| k1_d ^= prim_cipher_pkg::PRINCE_ALPHA_CONST[DataWidth-1:0]; |
| end |
| end |
| |
| if (UseOldKeySched) begin : gen_legacy_keyschedule |
| // In this case we constantly use k1. |
| assign k0_new_d = k1_d; |
| end else begin : gen_new_keyschedule |
| // Imroved keyschedule proposed by https://eprint.iacr.org/2014/656.pdf |
| // In this case we alternate between k1 and k0. |
| always_comb begin : p_new_keyschedule_k0_alpha |
| k0_new_d = key_i[2*DataWidth-1 : DataWidth]; |
| // We need to apply the alpha constant here as well, just as for k1 in decryption mode. |
| if (dec_i) begin |
| k0_new_d ^= prim_cipher_pkg::PRINCE_ALPHA_CONST[DataWidth-1:0]; |
| end |
| end |
| end |
| |
| if (HalfwayKeyReg) begin : gen_key_reg |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_key_reg |
| if (!rst_ni) begin |
| k1_q <= '0; |
| k0_prime_q <= '0; |
| k0_new_q <= '0; |
| end else begin |
| if (valid_i) begin |
| k1_q <= k1_d; |
| k0_prime_q <= k0_prime_d; |
| k0_new_q <= k0_new_d; |
| end |
| end |
| end |
| end else begin : gen_no_key_reg |
| // just pass the key through in this case |
| assign k1_q = k1_d; |
| assign k0_prime_q = k0_prime_d; |
| assign k0_new_q = k0_new_d; |
| end |
| |
| ////////////// |
| // datapath // |
| ////////////// |
| |
| // State variable for holding the rounds |
| // |
| // The "split_var" hint that we pass to verilator here tells it to schedule the different parts of |
| // data_state separately. This avoids an UNOPTFLAT error where it would otherwise see a dependency |
| // chain |
| // |
| // data_state -> data_state_round -> data_state_xor -> data_state |
| // |
| logic [NumRoundsHalf*2+1:0][DataWidth-1:0] data_state /* verilator split_var */; |
| |
| // pre-round XOR |
| always_comb begin : p_pre_round_xor |
| data_state[0] = data_i ^ k0; |
| data_state[0] ^= k1_d; |
| data_state[0] ^= prim_cipher_pkg::PRINCE_ROUND_CONST[0][DataWidth-1:0]; |
| end |
| |
| // forward pass |
| for (genvar k = 1; k <= NumRoundsHalf; k++) begin : gen_fwd_pass |
| logic [DataWidth-1:0] data_state_round; |
| if (DataWidth == 64) begin : gen_fwd_d64 |
| always_comb begin : p_fwd_d64 |
| data_state_round = prim_cipher_pkg::sbox4_64bit(data_state[k-1], |
| prim_cipher_pkg::PRINCE_SBOX4); |
| data_state_round = prim_cipher_pkg::prince_mult_prime_64bit(data_state_round); |
| data_state_round = prim_cipher_pkg::prince_shiftrows_64bit(data_state_round, |
| prim_cipher_pkg::PRINCE_SHIFT_ROWS64); |
| end |
| end else begin : gen_fwd_d32 |
| always_comb begin : p_fwd_d32 |
| data_state_round = prim_cipher_pkg::sbox4_32bit(data_state[k-1], |
| prim_cipher_pkg::PRINCE_SBOX4); |
| data_state_round = prim_cipher_pkg::prince_mult_prime_32bit(data_state_round); |
| data_state_round = prim_cipher_pkg::prince_shiftrows_32bit(data_state_round, |
| prim_cipher_pkg::PRINCE_SHIFT_ROWS64); |
| end |
| end |
| logic [DataWidth-1:0] data_state_xor; |
| assign data_state_xor = data_state_round ^ |
| prim_cipher_pkg::PRINCE_ROUND_CONST[k][DataWidth-1:0]; |
| // improved keyschedule proposed by https://eprint.iacr.org/2014/656.pdf |
| if (k % 2 == 1) begin : gen_fwd_key_odd |
| assign data_state[k] = data_state_xor ^ k0_new_d; |
| end else begin : gen_fwd_key_even |
| assign data_state[k] = data_state_xor ^ k1_d; |
| end |
| end |
| |
| // middle part |
| logic [DataWidth-1:0] data_state_middle_d, data_state_middle_q, data_state_middle; |
| if (DataWidth == 64) begin : gen_middle_d64 |
| always_comb begin : p_middle_d64 |
| data_state_middle_d = prim_cipher_pkg::sbox4_64bit(data_state[NumRoundsHalf], |
| prim_cipher_pkg::PRINCE_SBOX4); |
| data_state_middle = prim_cipher_pkg::prince_mult_prime_64bit(data_state_middle_q); |
| data_state_middle = prim_cipher_pkg::sbox4_64bit(data_state_middle, |
| prim_cipher_pkg::PRINCE_SBOX4_INV); |
| end |
| end else begin : gen_middle_d32 |
| always_comb begin : p_middle_d32 |
| data_state_middle_d = prim_cipher_pkg::sbox4_32bit(data_state_middle[NumRoundsHalf], |
| prim_cipher_pkg::PRINCE_SBOX4); |
| data_state_middle = prim_cipher_pkg::prince_mult_prime_32bit(data_state_middle_q); |
| data_state_middle = prim_cipher_pkg::sbox4_32bit(data_state_middle, |
| prim_cipher_pkg::PRINCE_SBOX4_INV); |
| end |
| end |
| |
| if (HalfwayDataReg) begin : gen_data_reg |
| logic valid_q; |
| always_ff @(posedge clk_i or negedge rst_ni) begin : p_data_reg |
| if (!rst_ni) begin |
| valid_q <= 1'b0; |
| data_state_middle_q <= '0; |
| end else begin |
| valid_q <= valid_i; |
| if (valid_i) begin |
| data_state_middle_q <= data_state_middle_d; |
| end |
| end |
| end |
| assign valid_o = valid_q; |
| end else begin : gen_no_data_reg |
| // just pass data through in this case |
| assign data_state_middle_q = data_state_middle_d; |
| assign valid_o = valid_i; |
| end |
| |
| assign data_state[NumRoundsHalf+1] = data_state_middle; |
| |
| // backward pass |
| for (genvar k = 1; k <= NumRoundsHalf; k++) begin : gen_bwd_pass |
| logic [DataWidth-1:0] data_state_xor0, data_state_xor1; |
| // improved keyschedule proposed by https://eprint.iacr.org/2014/656.pdf |
| if ((NumRoundsHalf + k + 1) % 2 == 1) begin : gen_bkwd_key_odd |
| assign data_state_xor0 = data_state[NumRoundsHalf+k] ^ k0_new_q; |
| end else begin : gen_bkwd_key_even |
| assign data_state_xor0 = data_state[NumRoundsHalf+k] ^ k1_q; |
| end |
| // the construction is reflective, hence the subtraction with NumRoundsHalf |
| assign data_state_xor1 = data_state_xor0 ^ |
| prim_cipher_pkg::PRINCE_ROUND_CONST[10-NumRoundsHalf+k][DataWidth-1:0]; |
| |
| logic [DataWidth-1:0] data_state_bwd; |
| if (DataWidth == 64) begin : gen_bwd_d64 |
| always_comb begin : p_bwd_d64 |
| data_state_bwd = prim_cipher_pkg::prince_shiftrows_64bit(data_state_xor1, |
| prim_cipher_pkg::PRINCE_SHIFT_ROWS64_INV); |
| data_state_bwd = prim_cipher_pkg::prince_mult_prime_64bit(data_state_bwd); |
| data_state[NumRoundsHalf+k+1] = prim_cipher_pkg::sbox4_64bit(data_state_bwd, |
| prim_cipher_pkg::PRINCE_SBOX4_INV); |
| end |
| end else begin : gen_bwd_d32 |
| always_comb begin : p_bwd_d32 |
| data_state_bwd = prim_cipher_pkg::prince_shiftrows_32bit(data_state_xor1, |
| prim_cipher_pkg::PRINCE_SHIFT_ROWS64_INV); |
| data_state_bwd = prim_cipher_pkg::prince_mult_prime_32bit(data_state_bwd); |
| data_state[NumRoundsHalf+k+1] = prim_cipher_pkg::sbox4_32bit(data_state_bwd, |
| prim_cipher_pkg::PRINCE_SBOX4_INV); |
| end |
| end |
| end |
| |
| // post-rounds |
| always_comb begin : p_post_round_xor |
| data_o = data_state[2*NumRoundsHalf+1] ^ |
| prim_cipher_pkg::PRINCE_ROUND_CONST[11][DataWidth-1:0]; |
| data_o ^= k1_q; |
| data_o ^= k0_prime_q; |
| end |
| |
| //////////////// |
| // assertions // |
| //////////////// |
| |
| `ASSERT_INIT(SupportedWidths_A, (DataWidth == 64 && KeyWidth == 128) || |
| (DataWidth == 32 && KeyWidth == 64)) |
| `ASSERT_INIT(SupportedNumRounds_A, NumRoundsHalf > 0 && NumRoundsHalf < 6) |
| |
| |
| endmodule : prim_prince |