コード例 #1
0
ファイル: picnic.py プロジェクト: ThorKn/Python-Picnic
    def serialize_signature(self):

        result = bytearray()

        # Append challenges as bytes
        challenges = BitVector(size=0)
        for i in self.__signature.challenges:
            if (i == 0):
                challenges = challenges + BitVector(bitlist=[0, 0])
            if (i == 1):
                challenges = challenges + BitVector(bitlist=[1, 0])
            if (i == 2):
                challenges = challenges + BitVector(bitlist=[0, 1])
        diff = 8 - (challenges.length() % 8)
        challenges = challenges + BitVector(intVal=0, size=diff)
        result.extend(bytes.fromhex(challenges.get_bitvector_in_hex()))

        # Append salt as bytes
        result.extend(
            bytes.fromhex(self.__signature.salt.get_bitvector_in_hex()))

        # Append all proofs
        for t in range(self.mpc_rounds):
            challenge_value = self.__signature.challenges[t]

            result.extend(self.__signature.proofs[t].view_3_commit)
            result.extend(
                bytes.fromhex(self.__signature.proofs[t].transcript.
                              get_bitvector_in_hex()))
            result.extend(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_1.get_bitvector_in_hex()))
            result.extend(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_2.get_bitvector_in_hex()))
            if (challenge_value == 1 or challenge_value == 2):
                result.extend(
                    bytes.fromhex(self.__signature.proofs[t].i_share.
                                  get_bitvector_in_hex()))

        self.__signature_ser = result
コード例 #2
0
ファイル: bloom_filter.py プロジェクト: michael-ruan/crawler
class BloomFilter(object):
    def __init__(self, array_size, hash_count):
        self.size = array_size                  #the size of bit array
        self.hash_count = hash_count            #the number of probes for each item
        self.bit_array = BitVector(size = array_size)

    def add(self, item):
        exist = True
        for seed in xrange(self.hash_count):
            result = mmh3.hash(item, seed) % self.size
            if self.bit_array[result] == 0:
                self.bit_array[result] = 1
                exist = False
        return exist

    def lookup(self, item):
        for seed in xrange(self.hash_count):
            result = mmh3.hash(item, seed) % self.size
            if self.bit_array[result] == 0:
                return False
        return True

    def __contains__(self, item):
        self.lookup(item)

    def __add__(self, bf):
        self.bit_array = self.bit_array | bf.bit_array
        return self

    def clear_all(self):
        self.bit_array.reset(0)

    def set_bit_array(self, ba):
        self.bit_array = ba

    def export_bloom(self, filename):
        with open(filename, 'wb') as f:
            f.write(str(self.size) + ":" + str(self.hash_count) + ":" + self.bit_array.get_bitvector_in_hex())
コード例 #3
0
ファイル: lowmc.py プロジェクト: ThorKn/Python-Picnic
class LowMC:
    def __init__(self, param):

        print("LowMC init for " + param)

        # Set security parameters according to param
        if (param == 'picnic-L1'):
            self.blocksize = 128
            self.keysize = 128
            self.number_sboxes = 10
            self.number_rounds = 20
            self.filename = 'picnic-L1.dat'
        elif (param == 'picnic-L3'):
            self.blocksize = 192
            self.keysize = 192
            self.number_sboxes = 10
            self.number_rounds = 30
            self.filename = 'picnic-L3.dat'
        elif (param == 'picnic-L5'):
            self.blocksize = 256
            self.keysize = 256
            self.number_sboxes = 10
            self.number_rounds = 38
            self.filename = 'picnic-L5.dat'

        # Public variables
        self.plaintext = None

        # Private variables
        self.__priv_key = None
        self.__state = None
        self.__lin_layer = []
        self.__lin_layer_inv = []
        self.__round_consts = []
        self.__round_key_mats = []
        self.__sbox = [0x00, 0x01, 0x03, 0x06, 0x07, 0x04, 0x05, 0x02]
        self.__sbox_inv = [0x00, 0x01, 0x07, 0x02, 0x05, 0x06, 0x03, 0x04]

        # Call init functions
        self.__read_constants()
        self.__invert_lin_matrix()

        print("LowMC init done")

    '''
  ////////////////////////////
  ///   Public functions   ///
  ////////////////////////////
  '''

    # Generate private key with lenght self.keysize from urandom
    def generate_priv_key(self):
        temp_key = os.urandom(int(self.keysize / 8))
        self.__priv_key = BitVector(rawbytes=temp_key)

    # Set private key
    # @param priv_key as raw bytes with length self.keysize
    def set_priv_key(self, priv_key):
        assert (len(priv_key) *
                8 == self.keysize), "Private key has length != keysize"
        self.__priv_key = BitVector(rawbytes=priv_key)

    # Encrypts a plaintext
    # @param plaintext must be raw bytes with length blocksize
    # @return ciphertext as raw bytes with length blocksize
    def encrypt(self, plaintext):
        assert (len(plaintext) *
                8) == self.blocksize, "Plaintext has length != blocksize"
        assert (self.__priv_key is not None), "Private key not set"

        self.__state = BitVector(rawbytes=plaintext)

        self.__key_addition(0)

        for i in range(self.number_rounds):
            self.__apply_sbox()
            self.__multiply_with_lin_mat(i)
            self.__state = self.__state ^ self.__round_consts[i]
            self.__key_addition(i + 1)

        result = bytes.fromhex(self.__state.get_bitvector_in_hex())
        self.__state = None
        return result

    # Decrypts a ciphertext
    # @param ciphertext must be raw bytes with length blocksize
    # @return plaintext as raw bytes with length blocksize
    def decrypt(self, ciphertext):
        assert (len(ciphertext) *
                8) == self.blocksize, "Ciphertext has length != blocksize"
        assert (self.__priv_key is not None), "Private key not set"

        self.__state = BitVector(rawbytes=ciphertext)

        for i in range(self.number_rounds, 0, -1):
            self.__key_addition(i)
            self.__state = self.__state ^ self.__round_consts[i - 1]
            self.__multiply_with_lin_mat_inv(i - 1)
            self.__apply_sbox_inv()

        self.__key_addition(0)

        result = bytes.fromhex(self.__state.get_bitvector_in_hex())
        self.__state = None
        return result

    '''
  /////////////////////////////
  ///   Picnic functions    ///
  /////////////////////////////
  '''

    def mpc_matrix_mul_keys(self, outputs, inputs, r, players):
        tmp_inputs = copy.deepcopy(inputs)
        for player in range(players):
            for i in range(self.blocksize):
                outputs[player][i] = (self.__round_key_mats[r][i]
                                      & tmp_inputs[player]).count_bits() % 2
        return outputs

    def mpc_matrix_mul_lin(self, outputs, inputs, r, players):
        tmp_inputs = copy.deepcopy(inputs)
        for player in range(players):
            for i in range(self.blocksize):
                outputs[player][i] = (self.__lin_layer[r][i]
                                      & tmp_inputs[player]).count_bits() % 2
        return outputs

    def mpc_xor_rconsts(self, states, r):
        states[0] = states[0] ^ self.__round_consts[r]
        return states

    def mpc_xor_rconsts_verify(self, states, r, chal_trit):
        if (chal_trit == 0):
            states[0] = states[0] ^ self.__round_consts[r]
        if (chal_trit == 2):
            states[1] = states[1] ^ self.__round_consts[r]
        return states

    '''
  /////////////////////////////
  ///   Private functions   ///
  /////////////////////////////
  '''

    def __apply_sbox(self):
        result = BitVector(size=self.blocksize)
        state_copy = self.__state.deep_copy()

        # Copy the identity part of the message
        result_ident = state_copy[(3 * self.number_sboxes):self.blocksize]

        # Substitute the rest of the message with the sboxes
        # ----------------------------------------------------
        # ATTENTION: The 3-bit chunks seem to be reversed
        # in the Picnic-Ref-Implementation, compared to the
        # LowMC-Ref-Implementation and the original LowMC-paper.
        # Example: state[0:3]='001' becomes '100' then gets sboxed
        # to '111' and reversed again for the state-update.
        # ----------------------------------------------------
        state_copy = self.__state[0:(3 * self.number_sboxes)]
        result_sbox = BitVector(size=0)
        for i in range(self.number_sboxes):
            state_index = (3 * i)
            state_3_bits = state_copy[state_index:state_index + 3].reverse()
            sbox_3_bits = BitVector(intVal=self.__sbox[int(state_3_bits)],
                                    size=3).reverse()
            result_sbox = result_sbox + sbox_3_bits

        result = result_sbox + result_ident
        self.__state = result

    def __apply_sbox_inv(self):
        result = BitVector(size=self.blocksize)
        state_copy = self.__state.deep_copy()

        # Copy the identity part of the message
        result_ident = state_copy[(3 * self.number_sboxes):self.blocksize]

        # Substitute the rest of the message with the inverse sboxes
        # ----------------------------------------------------
        # ATTENTION: The 3-bit chunks seem to be reversed
        # in the Picnic-Ref-Implementation, compared to the
        # LowMC-Ref-Implementation and the original LowMC-paper.
        # ----------------------------------------------------
        state_copy = self.__state[0:(3 * self.number_sboxes)]
        result_sbox = BitVector(size=0)
        for i in range(self.number_sboxes):
            state_index = (3 * i)
            state_3_bits = state_copy[state_index:state_index + 3].reverse()
            sbox_3_bits = BitVector(intVal=self.__sbox_inv[int(state_3_bits)],
                                    size=3).reverse()
            result_sbox = result_sbox + sbox_3_bits

        result = result_sbox + result_ident
        self.__state = result

    def __multiply_with_lin_mat(self, r):
        result = BitVector(size=self.blocksize)
        for i in range(self.blocksize):
            result[i] = (self.__lin_layer[r][i]
                         & self.__state).count_bits() % 2
        self.__state = result

    def __multiply_with_lin_mat_inv(self, r):
        result = BitVector(size=self.blocksize)
        for i in range(self.blocksize):
            result[i] = (self.__lin_layer_inv[r][i]
                         & self.__state).count_bits() % 2
        self.__state = result

    def __key_addition(self, r):
        round_key = BitVector(size=self.keysize)
        for i in range(self.blocksize):
            round_key[i] = (self.__round_key_mats[r][i]
                            & self.__priv_key).count_bits() % 2
        self.__state = self.__state ^ round_key

    def __read_constants(self):
        with open(self.filename, 'r') as matfile:
            const_data = matfile.read()

        const_data_split = const_data.split('\n')

        # Check for correct parameters and file length
        params = const_data_split[0:3]
        assert params[0] == str(
            self.blocksize), "Wrong blocksize in data file!"
        assert params[1] == str(self.keysize), "Wrong keysize in data file!"
        assert params[2] == str(
            self.number_rounds), "Wrong number of rounds in data file!"
        assert (len(const_data_split) - 1) == \
        3 + (((self.number_rounds * 2) + 1) * self.blocksize) + self.number_rounds,\
        "Wrong file size (number of lines)"

        # Linear layer matrices
        lines_offset = 3
        lines_count = self.number_rounds * self.blocksize
        lin_layer = const_data_split[lines_offset:(lines_offset + lines_count)]
        for r in range(self.number_rounds):
            mat = []
            for s in range(self.blocksize):
                bv = BitVector(bitstring=lin_layer[(r * self.blocksize) + s])
                mat.append(bv)
            self.__lin_layer.append(mat)

        # Round constants
        lines_offset += lines_count
        lines_count = self.number_rounds
        round_consts = const_data_split[lines_offset:(lines_offset +
                                                      lines_count)]
        for line in round_consts:
            self.__round_consts.append(BitVector(bitstring=line))

        # Round key matrices
        lines_offset += lines_count
        lines_count = (self.number_rounds + 1) * self.blocksize
        round_key_mats = const_data_split[lines_offset:(lines_offset +
                                                        lines_count)]
        for r in range(self.number_rounds + 1):
            mat = []
            for s in range(self.blocksize):
                mat.append(
                    BitVector(bitstring=round_key_mats[(r * self.blocksize) +
                                                       s]))
            self.__round_key_mats.append(mat)

    def __invert_lin_matrix(self):

        self.__lin_layer_inv = []
        for r in range(self.number_rounds):

            # Copy lin_layer
            mat = []
            for i in range(self.blocksize):
                mat.append(self.__lin_layer[r][i].deep_copy())

            # Create (initial identity) matrix, where the
            # inverted matrix will be stored in.
            inv_mat = []
            for i in range(self.blocksize):
                temp_bv = BitVector(intVal=0, size=self.blocksize)
                temp_bv[i] = 1
                inv_mat.append(temp_bv)

            # Transform to upper triangular matrix
            row = 0
            for col in range(self.keysize):
                if (not mat[row][col]):
                    r = row + 1
                    while ((r < self.blocksize) and (not mat[r][col])):
                        r += 1
                    if (r >= self.blocksize):
                        continue
                    else:
                        temp = mat[row]
                        mat[row] = mat[r]
                        mat[r] = temp
                        temp = inv_mat[row]
                        inv_mat[row] = inv_mat[r]
                        inv_mat[r] = temp
                for i in range(row + 1, self.blocksize):
                    if (mat[i][col]):
                        mat[i] = mat[i] ^ mat[row]
                        inv_mat[i] = inv_mat[i] ^ inv_mat[row]
                row += 1

            # Transform to inverse matrix
            for col in range(self.keysize, 0, -1):
                for r in range(col - 1):
                    if (mat[r][col - 1]):
                        mat[r] = mat[r] ^ mat[col - 1]
                        inv_mat[r] = inv_mat[r] ^ inv_mat[col - 1]

            self.__lin_layer_inv.append(inv_mat)
コード例 #4
0
ファイル: picnic.py プロジェクト: ThorKn/Python-Picnic
class Picnic:
    def __init__(self):

        # Public picnic parameters
        self.blocksize = 128
        self.blocksize_bytes = int(self.blocksize / 8)
        self.keysize = 128
        self.rounds = 20
        self.sboxes = 10
        self.mpc_rounds = 219
        self.hash_length = 256
        self.lowmc = LowMC('picnic-L1')

        # Private variables
        self.__priv_key = None
        self.__pub_key = None
        self.__views = []
        self.__commitments = []
        self.__seeds = []
        self.__salt = None
        self.__tapes_pos = 0
        self.__challenges = None
        self.__prove = None
        self.__signature = Signature()
        self.__signature_ser = None

    ##########################
    ###   Sign and verify  ###
    ##########################

    # Signing a message
    # @param message as bytes
    def sign(self, message):

        # Initialize empty views[mpc_rounds][players]
        for _ in range(self.mpc_rounds):
            three_views = []
            for _ in range(3):
                single_view = View(self.blocksize, self.rounds, self.sboxes)
                three_views.append(single_view)
            self.__views.append(three_views)

        # Initialize empty commitments[mpc_rounds][players]
        for _ in range(self.mpc_rounds):
            three_commits = []
            for _ in range(3):
                single_commit = Commitment(self.hash_length, 0)
                three_commits.append(single_commit)
            self.__commitments.append(three_commits)

        # Initialize seeds
        # Get one long shake_128 hash with length (3 * mpc_rounds + 1) * (blocksize / 8)
        # and split it afterwards into seeds and one salt
        shake128 = hashlib.shake_128()
        shake128.update(bytes.fromhex(self.__priv_key.get_bitvector_in_hex()))
        shake128.update(message)
        shake128.update(
            bytes.fromhex(self.__pub_key.public_key.get_bitvector_in_hex()))
        shake128.update(bytes.fromhex(self.__pub_key.p.get_bitvector_in_hex()))
        shake128.update(bytes([self.blocksize, 0]))
        long_hash = shake128.digest(
            ((3 * self.mpc_rounds) + 1) * self.blocksize_bytes)

        count = 0
        for _ in range(self.mpc_rounds):
            three_seeds = []
            for _ in range(3):
                single_seed = BitVector(
                    rawbytes=long_hash[count:count + self.blocksize_bytes])
                count += self.blocksize_bytes
                three_seeds.append(single_seed)
            self.__seeds.append(three_seeds)
        self.__salt = BitVector(rawbytes=long_hash[count:count +
                                                   self.blocksize_bytes])

        # A hack for the last 5 bytes of the tmp_view_raw is needed
        # and this seems as a bug (or unwanted behaviour) in the ref-sourcecode so far.
        new_end_of_tmp_view = bytearray([0, 0, 0, 0, 0])

        # MPC Rounds
        for t in range(self.mpc_rounds):

            print("MPC round " + str(t))
            tapes = []
            self.__tapes_pos = 0

            # Create tapes[0..2] and i_shares
            for j in range(2):
                length = int(
                    (self.blocksize + 3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw = self.mpc_create_random_tape(
                    self.__seeds[t][j], self.__salt, t, j,
                    length) + new_end_of_tmp_view
                self.__views[t][j].i_share = BitVector(
                    rawbytes=tmp_view_raw[0:self.blocksize_bytes])
                tapes.append(
                    BitVector(
                        rawbytes=tmp_view_raw[self.blocksize_bytes:length]))

            length_2 = int((3 * self.rounds * self.sboxes) / 8)
            tapes.append(
                BitVector(rawbytes=self.mpc_create_random_tape(
                    self.__seeds[t][2], self.__salt, t, 2, length_2)))
            self.__views[t][2].i_share = self.__priv_key ^ \
                                         self.__views[t][0].i_share ^ \
                                         self.__views[t][1].i_share

            # Run MPC
            new_end_of_tmp_view = self.run_mpc(t, tapes, tmp_view_raw)

            # Calculate the commitments
            self.mpc_commit(t)

        # Calculate challenges
        self.__challenges = self.h3(message)

        # Calculate proofs
        self.__proofs = self.prove()

        # Copy proofs, challenges and salt to self.__signature
        self.__signature.proofs = self.__proofs
        self.__signature.challenges = self.__challenges
        self.__signature.salt = self.__salt

    def verify(self, message):

        # Reset private variables
        self.__views = []
        self.__commitments = []
        self.__seeds = []
        self.__salt = None
        self.__tapes_pos = 0
        self.__challenges = None
        self.__prove = None

        # Initialize empty commitments[mpc_rounds][players]
        for _ in range(self.mpc_rounds):
            three_commits = []
            for _ in range(3):
                single_commit = Commitment(self.hash_length, 0)
                three_commits.append(single_commit)
            self.__commitments.append(three_commits)

        # Initialize empty outputs[mpc_rounds][players]
        outputs = []
        for _ in range(self.mpc_rounds):
            three_outputs = []
            for _ in range(3):
                single_output = BitVector(intVal=0, size=self.blocksize)
                three_outputs.append(single_output)
            outputs.append(three_outputs)

        # Initialize empty views[mpc_rounds][2]
        for _ in range(self.mpc_rounds):
            three_views = []
            for _ in range(2):
                single_view = View(self.blocksize, self.rounds, self.sboxes)
                three_views.append(single_view)
            self.__views.append(three_views)

        for t in range(self.mpc_rounds):

            print("MPC Round: " + str(t))
            tapes = []
            self.__tapes_pos = 0

            # Copy transcript to view[t][1]
            self.__views[t][1].transcript = self.__signature.proofs[
                t].transcript

            # Rebuild tapes and i_shares in regard to the challenges
            chal_trit = self.__signature.challenges[t]
            tmp_view_raw = None
            tmp_view_raw_short = None

            # Calculate both i_shares
            if (chal_trit == 0):
                length = int(
                    (self.blocksize + 3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw = self.mpc_create_random_tape(self.__signature.proofs[t].seed_1, \
                                                          self.__signature.salt, \
                                                          t, 0, length)
                self.__views[t][0].i_share = BitVector(
                    rawbytes=tmp_view_raw[0:self.blocksize_bytes])
                tapes.append(
                    BitVector(
                        rawbytes=tmp_view_raw[self.blocksize_bytes:length]))

                tmp_view_raw = self.mpc_create_random_tape(self.__signature.proofs[t].seed_2, \
                                                           self.__signature.salt, \
                                                           t, 1, length)
                self.__views[t][1].i_share = BitVector(
                    rawbytes=tmp_view_raw[0:self.blocksize_bytes])
                tapes.append(
                    BitVector(
                        rawbytes=tmp_view_raw[self.blocksize_bytes:length]))

            # Calculate i_share for player 0
            # i_share for player 1 was given in the proof
            if (chal_trit == 1):
                length = int(
                    (self.blocksize + 3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw = self.mpc_create_random_tape(self.__signature.proofs[t].seed_1, \
                                                           self.__signature.salt, \
                                                           t, 1, length)
                self.__views[t][0].i_share = BitVector(
                    rawbytes=tmp_view_raw[0:self.blocksize_bytes])
                tapes.append(
                    BitVector(
                        rawbytes=tmp_view_raw[self.blocksize_bytes:length]))

                length = int((3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw_short = self.mpc_create_random_tape(self.__signature.proofs[t].seed_2, \
                                                           self.__signature.salt, \
                                                           t, 2, length)
                tapes.append(BitVector(rawbytes=tmp_view_raw_short[0:length]))
                self.__views[t][1].i_share = self.__signature.proofs[t].i_share

            # i_share for player 0 was given in the proof
            # Calculate i_share for player 1
            if (chal_trit == 2):
                length = int((3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw_short = self.mpc_create_random_tape(self.__signature.proofs[t].seed_1, \
                                                           self.__signature.salt, \
                                                           t, 2, length)
                tapes.append(BitVector(rawbytes=tmp_view_raw_short[0:length]))
                self.__views[t][0].i_share = self.__signature.proofs[t].i_share

                length = int(
                    (self.blocksize + 3 * self.rounds * self.sboxes) / 8)
                tmp_view_raw = self.mpc_create_random_tape(self.__signature.proofs[t].seed_2, \
                                                           self.__signature.salt, \
                                                           t, 0, length)
                self.__views[t][1].i_share = BitVector(
                    rawbytes=tmp_view_raw[0:self.blocksize_bytes])
                tapes.append(
                    BitVector(
                        rawbytes=tmp_view_raw[self.blocksize_bytes:length]))

            # Run MPC
            self.run_mpc_verify(t, tapes, tmp_view_raw, chal_trit)

            # Calculate the commitments
            self.mpc_commit_verify(t, chal_trit)

            # Update outputs
            outputs[t][chal_trit] = self.__views[t][0].o_share
            outputs[t][(chal_trit + 1) % 3] = self.__views[t][1].o_share
            outputs[t][(chal_trit + 2) % 3] = self.__views[t][0].o_share ^ \
                                              self.__views[t][1].o_share ^ \
                                              self.__pub_key.public_key

        # Calculate challenges
        self.__challenges = self.h3_verify(message, outputs)

        if (self.__challenges == self.__signature.challenges):
            print("Signature verified")
        else:
            print("Signature verification failed!")

    ##############################
    ###   LowMC MPC functions  ###
    ##############################

    def run_mpc_verify(self, t, tapes, tmp_view_raw, chal_trit):

        key_shares = []
        states = []
        roundkeys = []

        # Create empty roundkeys and states
        # Fill key_shares with views
        for i in range(2):
            roundkeys.append(BitVector(intVal=0, size=self.blocksize))
            states.append(BitVector(intVal=0, size=self.blocksize))
            key_shares.append(self.__views[t][i].i_share)

        # Init states by xor'ing plaintext and roundkeys
        states = self.mpc_xor_constant_verify(states, self.__pub_key.p,
                                              chal_trit)
        roundkeys = self.lowmc.mpc_matrix_mul_keys(roundkeys, key_shares, 0, 2)
        states = self.mpc_xor(states, roundkeys, 2)

        for r in range(self.rounds):

            states = self.mpc_sbox_verify(states, tapes, r, t)
            states = self.lowmc.mpc_matrix_mul_lin(states, states, r, 2)
            states = self.lowmc.mpc_xor_rconsts_verify(states, r, chal_trit)
            roundkeys = self.lowmc.mpc_matrix_mul_keys(roundkeys, key_shares,
                                                       r + 1, 2)
            states = self.mpc_xor(states, roundkeys, 2)

        for i in range(2):
            self.__views[t][i].o_share = states[i]

    # Simulate LowMC for all three players
    def run_mpc(self, t, tapes, tmp_view_raw):

        key_shares = []
        states = []
        roundkeys = []

        # Create empty roundkeys and states
        # Fill key_shares with views
        for i in range(3):
            roundkeys.append(BitVector(intVal=0, size=self.blocksize))
            states.append(BitVector(intVal=0, size=self.blocksize))
            key_shares.append(self.__views[t][i].i_share)

        # Init states by xor'ing plaintext and roundkeys
        states = self.mpc_xor_constant(states, self.__pub_key.p)
        roundkeys = self.lowmc.mpc_matrix_mul_keys(roundkeys, key_shares, 0, 3)
        states = self.mpc_xor(states, roundkeys, 3)

        for r in range(self.rounds):

            states = self.mpc_sbox(states, tapes, r, t)
            states = self.lowmc.mpc_matrix_mul_lin(states, states, r, 3)
            states = self.lowmc.mpc_xor_rconsts(states, r)
            roundkeys = self.lowmc.mpc_matrix_mul_keys(roundkeys, key_shares,
                                                       r + 1, 3)
            states = self.mpc_xor(states, roundkeys, 3)

        for i in range(3):
            self.__views[t][i].o_share = states[i]

        # This is part of a hack for the end of tmp_view_raw
        new_end_of_tmp_view = bytes.fromhex(
            states[2][self.blocksize -
                      40:self.blocksize].get_bitvector_in_hex())

        return new_end_of_tmp_view

    # MPC LowMC sbox for verifying
    def mpc_sbox_verify(self, states, tapes, r, t):

        a = (BitVector(intVal=0, size=3))
        b = (BitVector(intVal=0, size=3))
        c = (BitVector(intVal=0, size=3))
        ab = (BitVector(intVal=0, size=3))
        bc = (BitVector(intVal=0, size=3))
        ca = (BitVector(intVal=0, size=3))

        # Sbox'ing the first 3*self.sboxes bits for
        # all three players
        for i in range(0, (3 * self.sboxes), 3):

            for j in range(2):
                a[j] = states[j][i + 2]
                b[j] = states[j][i + 1]
                c[j] = states[j][i]

            ab = self.mpc_and_verify(a, b, tapes, r, t)
            bc = self.mpc_and_verify(b, c, tapes, r, t)
            ca = self.mpc_and_verify(c, a, tapes, r, t)

            for j in range(2):
                states[j][i + 2] = a[j] ^ bc[j]
                states[j][i + 1] = a[j] ^ b[j] ^ ca[j]
                states[j][i] = a[j] ^ b[j] ^ c[j] ^ ab[j]

        return states

    # MPC LowMC sbox for signing
    def mpc_sbox(self, states, tapes, r, t):

        a = (BitVector(intVal=0, size=3))
        b = (BitVector(intVal=0, size=3))
        c = (BitVector(intVal=0, size=3))
        ab = (BitVector(intVal=0, size=3))
        bc = (BitVector(intVal=0, size=3))
        ca = (BitVector(intVal=0, size=3))

        # Sbox'ing the first 3*self.sboxes bits for
        # all three players
        for i in range(0, (3 * self.sboxes), 3):

            for j in range(3):
                a[j] = states[j][i + 2]
                b[j] = states[j][i + 1]
                c[j] = states[j][i]

            ab = self.mpc_and(a, b, tapes, r, t)
            bc = self.mpc_and(b, c, tapes, r, t)
            ca = self.mpc_and(c, a, tapes, r, t)

            for j in range(3):
                states[j][i + 2] = a[j] ^ bc[j]
                states[j][i + 1] = a[j] ^ b[j] ^ ca[j]
                states[j][i] = a[j] ^ b[j] ^ c[j] ^ ab[j]

        return states

    # MPC LowMC AND for verifying
    def mpc_and_verify(self, in1, in2, tapes, r, t):

        rand = BitVector(intVal=0, size=2)
        rand[0] = tapes[0][self.__tapes_pos]
        rand[1] = tapes[1][self.__tapes_pos]

        result = BitVector(intVal=0, size=2)

        result[0] = (in1[0] & in2[1]) ^ \
                    (in1[1] & in2[0]) ^ \
                    (in1[0] & in2[0]) ^ \
                    rand[0] ^ \
                    rand[1]
        self.__views[t][0].transcript[self.__tapes_pos] = result[0]
        result[1] = self.__views[t][1].transcript[self.__tapes_pos]
        self.__tapes_pos += 1

        return result

    # MPC LowMC AND for signing and updating the transcripts
    def mpc_and(self, in1, in2, tapes, r, t):

        rand = BitVector(intVal=0, size=3)
        rand[0] = tapes[0][self.__tapes_pos]
        rand[1] = tapes[1][self.__tapes_pos]
        rand[2] = tapes[2][self.__tapes_pos]

        result = BitVector(intVal=0, size=3)

        # Update the transcripts for all three players
        for i in range(3):
            result[i] = (in1[i] & in2[(i + 1) % 3]) ^ \
                        (in1[(i + 1) % 3] & in2[i]) ^ \
                        (in1[i] & in2[i]) ^ \
                        rand[i] ^ \
                        rand[(i + 1) % 3]
            self.__views[t][i].transcript[self.__tapes_pos] = result[i]

        self.__tapes_pos += 1

        return result

    # MPC LowMC XOR constant verify
    def mpc_xor_constant_verify(self, ins, constant, chal_trit):

        if (chal_trit == 0):
            ins[0] = ins[0] ^ constant
        if (chal_trit == 2):
            ins[1] = ins[1] ^ constant
        return ins

    # MPC LowMC XOR a constant
    def mpc_xor_constant(self, ins, constant):

        ins[0] = ins[0] ^ constant
        return ins

    # MPC LowMC XOR outs and ins for all players
    def mpc_xor(self, outs, ins, players):

        for i in range(players):
            outs[i] = outs[i] ^ ins[i]
        return outs

    # Calculate the commitments, the third one is given in the proof
    def mpc_commit_verify(self, t, chal_trit):

        self.__commitments[t][chal_trit].hash = self.mpc_h0(t, 0)
        self.__commitments[t][(chal_trit + 1) % 3].hash = self.mpc_h0(t, 1)
        self.__commitments[t][
            (chal_trit + 2) %
            3].hash = self.__signature.proofs[t].view_3_commit

    def mpc_h0(self, t, player):

        # H4(seed[mpc_round][player])
        shake128 = hashlib.shake_128()
        shake128.update(bytes([0x04]))
        if (player == 0):
            shake128.update(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_1.get_bitvector_in_hex()))
        if (player == 1):
            shake128.update(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_2.get_bitvector_in_hex()))
        h4 = shake128.digest(int(self.hash_length / 8))

        # Calculate h0(h4, views[t])
        shake128 = hashlib.shake_128()
        shake128.update(bytes([0x00]))
        shake128.update(h4)
        shake128.update(
            bytes.fromhex(
                self.__views[t][player].i_share.get_bitvector_in_hex()))
        shake128.update(
            bytes.fromhex(
                self.__views[t][player].transcript.get_bitvector_in_hex()))
        shake128.update(
            bytes.fromhex(
                self.__views[t][player].o_share.get_bitvector_in_hex()))

        return shake128.digest(int(self.hash_length / 8))

    # Calculate the commitments by hashing the
    # seeds and views for all three players
    def mpc_commit(self, t):

        for i in range(3):

            # H4(seed[mpc_round][player])
            shake128 = hashlib.shake_128()
            shake128.update(bytes([0x04]))
            shake128.update(
                bytes.fromhex(self.__seeds[t][i].get_bitvector_in_hex()))
            h4 = shake128.digest(int(self.hash_length / 8))

            # Calculate h0(h4, views[t])
            shake128 = hashlib.shake_128()
            shake128.update(bytes([0x00]))
            shake128.update(h4)
            shake128.update(
                bytes.fromhex(
                    self.__views[t][i].i_share.get_bitvector_in_hex()))
            shake128.update(
                bytes.fromhex(
                    self.__views[t][i].transcript.get_bitvector_in_hex()))
            shake128.update(
                bytes.fromhex(
                    self.__views[t][i].o_share.get_bitvector_in_hex()))

            self.__commitments[t][i].hash = shake128.digest(
                int(self.hash_length / 8))

    # Get one long hash for the random tapes
    def mpc_create_random_tape(self, seed, salt, mpc_round, player, length):

        # H2(seed[mpc_round][player])
        shake128 = hashlib.shake_128()
        shake128.update(bytes([0x02]))
        shake128.update(bytes.fromhex(seed.get_bitvector_in_hex()))
        h2 = shake128.digest(int(self.hash_length / 8))

        # Create random tape
        shake128 = hashlib.shake_128()
        shake128.update(h2)
        shake128.update(bytes.fromhex(salt.get_bitvector_in_hex()))
        shake128.update(bytes([mpc_round, 0]))
        shake128.update(bytes([player, 0]))
        length_le = length.to_bytes(2, byteorder='little')
        shake128.update(length.to_bytes(2, byteorder='little'))

        return shake128.digest(length)

    ################################
    ###   Challenges and proofs  ###
    ################################
    # Calculating the challenges in {0,1,2}*
    def h3_verify(self, message, outputs):

        shake128 = hashlib.shake_128()

        # Hash the output shares with prefix 0x01
        shake128.update(bytes([0x01]))
        for t in range(self.mpc_rounds):
            for player in range(3):
                shake128.update(
                    bytes.fromhex(outputs[t][player].get_bitvector_in_hex()))

        # Hash the commitments
        for t in range(self.mpc_rounds):
            for player in range(3):
                shake128.update(self.__commitments[t][player].hash)

        # Hash the circuit output
        circuit_output = outputs[0][0] ^ outputs[0][1] ^ outputs[0][2]
        shake128.update(bytes.fromhex(circuit_output.get_bitvector_in_hex()))

        # Hash p (plaintext), salt and message to get the challenge from it
        shake128.update(bytes.fromhex(self.__pub_key.p.get_bitvector_in_hex()))
        shake128.update(
            bytes.fromhex(self.__signature.salt.get_bitvector_in_hex()))
        shake128.update(message)

        tmp_hash = shake128.digest(int(self.hash_length / 8))

        tmp_bitvector = BitVector(rawbytes=tmp_hash)
        bit_pos = 0
        result = []

        # Build the challenge in {0,1,2}* as a list.
        # Each two bits in the hash become one element in the challenge-list
        # until the challenge-list has the length of self.mpc_rounds.
        # There can be re-calculating of the hash to reach that length.
        while (1):
            a = tmp_bitvector[bit_pos]
            b = tmp_bitvector[bit_pos + 1]
            if (a == 0 and b == 0):
                result.append(0)
            if (a == 0 and b == 1):
                result.append(1)
            if (a == 1 and b == 0):
                result.append(2)
            bit_pos += 2
            if (len(result) >= self.mpc_rounds):
                break
            if (bit_pos >= self.hash_length):
                shake128 = hashlib.shake_128()
                shake128.update(bytes([0x01]))
                shake128.update(tmp_hash)
                tmp_hash = shake128.digest(int(self.hash_length / 8))
                tmp_bitvector = BitVector(rawbytes=tmp_hash)
                bit_pos = 0

        return result

    # Calculating the challenges in {0,1,2}*
    def h3(self, message):

        shake128 = hashlib.shake_128()

        # Hash the output shares with prefix 0x01
        shake128.update(bytes([0x01]))
        for t in range(self.mpc_rounds):
            for player in range(3):
                shake128.update(
                    bytes.fromhex(self.__views[t]
                                  [player].o_share.get_bitvector_in_hex()))

        # Hash the commitments
        for t in range(self.mpc_rounds):
            for player in range(3):
                shake128.update(self.__commitments[t][player].hash)

        # Hash the circuit output
        circuit_output = self.__views[0][0].o_share ^ \
                         self.__views[0][1].o_share ^ \
                         self.__views[0][2].o_share
        shake128.update(bytes.fromhex(circuit_output.get_bitvector_in_hex()))

        # Hash p (plaintext), salt and message to get the challenge from it
        shake128.update(bytes.fromhex(self.__pub_key.p.get_bitvector_in_hex()))
        shake128.update(bytes.fromhex(self.__salt.get_bitvector_in_hex()))
        shake128.update(message)

        tmp_hash = shake128.digest(int(self.hash_length / 8))

        tmp_bitvector = BitVector(rawbytes=tmp_hash)
        bit_pos = 0
        result = []

        # Build the challenge in {0,1,2}* as a list.
        # Each two bits in the hash become one element in the challenge-list
        # until the challenge-list has the length of self.mpc_rounds.
        # There can be re-calculating of the hash to reach that length.
        while (1):
            a = tmp_bitvector[bit_pos]
            b = tmp_bitvector[bit_pos + 1]
            if (a == 0 and b == 0):
                result.append(0)
            if (a == 0 and b == 1):
                result.append(1)
            if (a == 1 and b == 0):
                result.append(2)
            bit_pos += 2
            if (len(result) >= self.mpc_rounds):
                break
            if (bit_pos >= self.hash_length):
                shake128 = hashlib.shake_128()
                shake128.update(bytes([0x01]))
                shake128.update(tmp_hash)
                tmp_hash = shake128.digest(int(self.hash_length / 8))
                tmp_bitvector = BitVector(rawbytes=tmp_hash)
                bit_pos = 0

        return result

    # Calculating the proofs from the challenges, seeds,
    # transcripts, i_shares and commitments
    def prove(self):

        proofs = []

        for t in range(self.mpc_rounds):
            tmp_proof = Proof()

            challenge = self.__challenges[t]
            if (challenge == 0):
                tmp_proof.seed_1 = self.__seeds[t][0]
                tmp_proof.seed_2 = self.__seeds[t][1]

            if (challenge == 1):
                tmp_proof.seed_1 = self.__seeds[t][1]
                tmp_proof.seed_2 = self.__seeds[t][2]
                tmp_proof.i_share = self.__views[t][2].i_share

            if (challenge == 2):
                tmp_proof.seed_1 = self.__seeds[t][2]
                tmp_proof.seed_2 = self.__seeds[t][0]
                tmp_proof.i_share = self.__views[t][2].i_share

            tmp_proof.transcript = self.__views[t][(challenge + 1) %
                                                   3].transcript
            tmp_proof.view_3_commit = self.__commitments[t][(challenge + 2) %
                                                            3].hash

            proofs.append(tmp_proof)

        return proofs

    ###########################
    ###   Helper functions  ###
    ###########################

    # Set or generate the priv and pub key
    def generate_keys(self, p=None, priv_key=None):

        # Generate random p (plaintext) with length self.keysize
        if (p is None):
            raw_p = os.urandom(int(self.keysize / 8))
        else:
            raw_p = p
        bitvector_p = BitVector(rawbytes=raw_p)

        # Generate private key with length self.keysize
        if (priv_key is None):
            raw_priv_key = os.urandom(int(self.keysize / 8))
        else:
            raw_priv_key = priv_key
        self.__priv_key = BitVector(rawbytes=raw_priv_key)

        # Generate public key [c,p]
        self.lowmc.set_priv_key(raw_priv_key)
        raw_c = self.lowmc.encrypt(raw_p)
        bitvector_c = BitVector(rawbytes=raw_c)
        self.__pub_key = Publickey(bitvector_c, bitvector_p)

    # Serialize a full signature from self.__signature
    def serialize_signature(self):

        result = bytearray()

        # Append challenges as bytes
        challenges = BitVector(size=0)
        for i in self.__signature.challenges:
            if (i == 0):
                challenges = challenges + BitVector(bitlist=[0, 0])
            if (i == 1):
                challenges = challenges + BitVector(bitlist=[1, 0])
            if (i == 2):
                challenges = challenges + BitVector(bitlist=[0, 1])
        diff = 8 - (challenges.length() % 8)
        challenges = challenges + BitVector(intVal=0, size=diff)
        result.extend(bytes.fromhex(challenges.get_bitvector_in_hex()))

        # Append salt as bytes
        result.extend(
            bytes.fromhex(self.__signature.salt.get_bitvector_in_hex()))

        # Append all proofs
        for t in range(self.mpc_rounds):
            challenge_value = self.__signature.challenges[t]

            result.extend(self.__signature.proofs[t].view_3_commit)
            result.extend(
                bytes.fromhex(self.__signature.proofs[t].transcript.
                              get_bitvector_in_hex()))
            result.extend(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_1.get_bitvector_in_hex()))
            result.extend(
                bytes.fromhex(
                    self.__signature.proofs[t].seed_2.get_bitvector_in_hex()))
            if (challenge_value == 1 or challenge_value == 2):
                result.extend(
                    bytes.fromhex(self.__signature.proofs[t].i_share.
                                  get_bitvector_in_hex()))

        self.__signature_ser = result

    def deserialize_signature(self):

        # Create an empty signature
        self.__signature = Signature()
        self.__signature.proofs = []
        bytes_pos = 0

        # Get challenges
        challenge_length = math.ceil(2 * self.mpc_rounds / 8)
        challenges_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                                challenge_length]
        challenges_bitvector = BitVector(rawbytes=challenges_bytes)
        challenges = []
        for i in range(0, (2 * self.mpc_rounds), 2):
            two_bits = str(challenges_bitvector[i:i + 2])
            if (two_bits == '00'):
                challenges.append(0)
            if (two_bits == '10'):
                challenges.append(1)
            if (two_bits == '01'):
                challenges.append(2)
        self.__signature.challenges = challenges
        bytes_pos += challenge_length

        # Get salt
        salt_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                          self.blocksize_bytes]
        salt = BitVector(rawbytes=salt_bytes)
        self.__signature.salt = salt
        bytes_pos += self.blocksize_bytes

        # Deserialize the proofs in all mpc_rounds
        for t in range(self.mpc_rounds):

            proof = Proof()
            chal_bit = challenges[t]

            # Get view_3_commitment
            view_3_commit_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                                       int(self.hash_length /
                                                           8)]
            proof.view_3_commit = view_3_commit_bytes
            bytes_pos += int(self.hash_length / 8)

            # Get transcript
            transcript_bytes = self.__signature_ser[bytes_pos:bytes_pos + int(
                (3 * self.rounds * self.sboxes) / 8)]
            proof.transcript = BitVector(rawbytes=transcript_bytes)
            bytes_pos += int((3 * self.rounds * self.sboxes) / 8)

            # Get seed_1
            seed_1_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                                self.blocksize_bytes]
            proof.seed_1 = BitVector(rawbytes=seed_1_bytes)
            bytes_pos += self.blocksize_bytes

            # Get seed_2
            seed_2_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                                self.blocksize_bytes]
            proof.seed_2 = BitVector(rawbytes=seed_2_bytes)
            bytes_pos += self.blocksize_bytes

            # If chal_bit is not 0, then get i_share
            if not (chal_bit == 0):
                i_share_bytes = self.__signature_ser[bytes_pos:bytes_pos +
                                                     self.blocksize_bytes]
                proof.i_share = BitVector(rawbytes=i_share_bytes)
                bytes_pos += self.blocksize_bytes

            self.__signature.proofs.append(proof)

        return

    # Write serialized signature to file
    def write_ser_sig_to_file(self, filename):

        with open(filename, "w") as text_file:
            text_file.write(self.__signature_ser.hex().upper())

    # Read serialized signature from file
    def read_ser_sig_from_file(self, filename):

        with open(filename, "r") as text_file:
            sig_ser_hex = text_file.read()
        self.__signature_ser = bytes.fromhex(sig_ser_hex)

    # Print out a (not serialized) signature from self.__signature
    def print_signature(self):

        print("Signature:")
        print("Salt: " + self.__salt.get_bitvector_in_hex())
        for t in range(self.mpc_rounds):
            print("Iteration t: " + str(t))
            print("e_" + str(t) + ": " + str(self.__signature.challenges[t]))
            print("b_" + str(t) + ": " +
                  self.__signature.proofs[t].view_3_commit.hex())
            print("transcript: " +
                  self.__signature.proofs[t].transcript.get_bitvector_in_hex())
            print("seed1: " +
                  self.__signature.proofs[t].seed_1.get_bitvector_in_hex())
            print("seed2: " +
                  self.__signature.proofs[t].seed_2.get_bitvector_in_hex())
            if (not (self.__signature.challenges[t] == 0)):
                print(
                    "inputShare: " +
                    self.__signature.proofs[t].i_share.get_bitvector_in_hex())

    # Print out a serialized signature from self.__signature_ser
    def print_signature_ser(self):
        print(self.__signature_ser.hex().upper())
コード例 #5
0
class LowMC(object):
    """LowMC blockcipher mainclass.

    For de- and encryption of message blocks with the LowMC blockipher.
    This class can handle the various Picnic security levels and is able to
    generate or set and store a single privat key.
    """

    __slots__ = [
        '__blocksize', '__keysize', '__number_sboxes', '__number_rounds',
        '__filename', '__blocksize_bytes', '__keysize_bytes', '__plaintext',
        '__priv_key', '__state', '__lin_layer', '__lin_layer_inv',
        '__round_consts', '__round_key_mats', '__sbox', '__sbox_inv'
    ]

    def __init__(self, param: str) -> None:
        """Instanciates a LowMC object.

        Args:
            param:  A string containing the Picnic security level
        """
        if (param == 'picnic-L1'):
            self.__blocksize = 128
            self.__keysize = 128
            self.__number_sboxes = 10
            self.__number_rounds = 20
            self.__filename = 'picnic-L1.dat'
        elif (param == 'picnic-L3'):
            self.__blocksize = 192
            self.__keysize = 192
            self.__number_sboxes = 10
            self.__number_rounds = 30
            self.__filename = 'picnic-L3.dat'
        elif (param == 'picnic-L5'):
            self.__blocksize = 256
            self.__keysize = 256
            self.__number_sboxes = 10
            self.__number_rounds = 38
            self.__filename = 'picnic-L5.dat'
        else:
            raise Exception(
                'Argument is not a valid Picnic security Level: {}'.format(
                    param))

        self.__blocksize_bytes = int(self.__blocksize / 8)
        self.__keysize_bytes = int(self.__keysize / 8)

        self.__plaintext = None

        self.__priv_key = None
        self.__state = None
        self.__lin_layer = []
        self.__lin_layer_inv = []
        self.__round_consts = []
        self.__round_key_mats = []
        self.__sbox = [0x00, 0x01, 0x03, 0x06, 0x07, 0x04, 0x05, 0x02]
        self.__sbox_inv = [0x00, 0x01, 0x07, 0x02, 0x05, 0x06, 0x03, 0x04]

        self.__read_constants()
        self.__invert_lin_matrix()

    @property
    def private_key(self) -> bytes:
        """Private key getter.

        Getter method for returning the private key

        Returns:
            bytearray of the private key

        """
        return self.__priv_key

    @private_key.setter
    def private_key(self, priv_key: Optional[bytes] = None) -> None:
        """Set or generate a private key.

        If no private key is provided as argument, one is generated from the
        CSPRNG of the underlying OS. This should be a plattform independend
        source for randomness. It will have the length self.__keysize_bytes.

        Args:
            priv_key:   If provided, must be a bytearray of
                        length self.__keysize_bytes
        """
        if (priv_key is None):
            temp_key = os.urandom(int(self.__keysize_bytes))
            self.__priv_key = BitVector(rawbytes=temp_key)
        else:
            assert (len(priv_key) == self.__keysize_bytes), \
                    "Private key has length != keysize"
            self.__priv_key = BitVector(rawbytes=priv_key)

    def encrypt(self, plaintext: bytes) -> bytes:
        """Encryption of a plaintext.

        Args:
            plaintext:  Must be a bytearray of length self.__blocksize_bytes

        Returns:
            A bytearray containing the ciphertext of
            length self.__blocksize_bytes

        """
        assert (len(plaintext) == self.__blocksize_bytes), \
            "Plaintext has length != blocksize"
        assert (self.__priv_key is not None), "Private key not set"

        self.__state = BitVector(rawbytes=plaintext)

        self.__key_addition(0)

        for i in range(self.__number_rounds):
            self.__apply_sbox()
            self.__multiply_with_lin_mat(i)
            self.__state = self.__state ^ self.__round_consts[i]
            self.__key_addition(i + 1)

        result = bytes.fromhex(self.__state.get_bitvector_in_hex())
        self.__state = None
        return result

    def decrypt(self, ciphertext: bytes) -> bytes:
        """Decryption of a ciphertext.

        Args:
            ciphertext:  Must be a bytearray of length self.__blocksize_bytes

        Returns:
            bytearray containing the plaintext of length self.__blocksize_bytes

        """
        assert (len(ciphertext) == self.__blocksize_bytes), \
            "Ciphertext has length != blocksize"
        assert (self.__priv_key is not None), "Private key not set"

        self.__state = BitVector(rawbytes=ciphertext)

        for i in range(self.__number_rounds, 0, -1):
            self.__key_addition(i)
            self.__state = self.__state ^ self.__round_consts[i - 1]
            self.__multiply_with_lin_mat_inv(i - 1)
            self.__apply_sbox_inv()

        self.__key_addition(0)

        result = bytes.fromhex(self.__state.get_bitvector_in_hex())
        self.__state = None
        return result

    def __apply_sbox(self) -> None:
        result = BitVector(size=self.__blocksize)
        state_copy = self.__state.deep_copy()

        # Copy the identity part of the message
        result_ident = state_copy[(3 * self.__number_sboxes):self.__blocksize]

        # Substitute the rest of the message with the sboxes
        # ----------------------------------------------------
        # ATTENTION: The 3-bit chunks seem to be reversed
        # in the Picnic-Ref-Implementation, compared to the
        # LowMC-Ref-Implementation and the original LowMC-paper.
        # Example: state[0:3]='001' becomes '100' then gets sboxed
        # to '111' and reversed again for the state-update.
        # ----------------------------------------------------
        state_copy = self.__state[0:(3 * self.__number_sboxes)]
        result_sbox = BitVector(size=0)
        for i in range(self.__number_sboxes):
            state_index = (3 * i)
            state_3_bits = state_copy[state_index:state_index + 3].reverse()
            sbox_3_bits = BitVector(intVal=self.__sbox[int(state_3_bits)],
                                    size=3).reverse()
            result_sbox = result_sbox + sbox_3_bits

        result = result_sbox + result_ident
        self.__state = result

    def __apply_sbox_inv(self) -> None:
        result = BitVector(size=self.__blocksize)
        state_copy = self.__state.deep_copy()

        # Copy the identity part of the message
        result_ident = state_copy[(3 * self.__number_sboxes):self.__blocksize]

        # Substitute the rest of the message with the inverse sboxes
        # ----------------------------------------------------
        # ATTENTION: The 3-bit chunks seem to be reversed
        # in the Picnic-Ref-Implementation, compared to the
        # LowMC-Ref-Implementation and the original LowMC-paper.
        # ----------------------------------------------------
        state_copy = self.__state[0:(3 * self.__number_sboxes)]
        result_sbox = BitVector(size=0)
        for i in range(self.__number_sboxes):
            state_index = (3 * i)
            state_3_bits = state_copy[state_index:state_index + 3].reverse()
            sbox_3_bits = BitVector(intVal=self.__sbox_inv[int(state_3_bits)],
                                    size=3).reverse()
            result_sbox = result_sbox + sbox_3_bits

        result = result_sbox + result_ident
        self.__state = result

    def __multiply_with_lin_mat(self, r: int) -> None:
        result = BitVector(size=self.__blocksize)
        for i in range(self.__blocksize):
            result[i] = (self.__lin_layer[r][i] & self.__state) \
                .count_bits() % 2
        self.__state = result

    def __multiply_with_lin_mat_inv(self, r: int) -> None:
        result = BitVector(size=self.__blocksize)
        for i in range(self.__blocksize):
            result[i] = (self.__lin_layer_inv[r][i] & self.__state) \
                        .count_bits() % 2
        self.__state = result

    def __key_addition(self, r: int) -> None:
        round_key = BitVector(size=self.__keysize)
        for i in range(self.__blocksize):
            round_key[i] = (self.__round_key_mats[r][i] & self.__priv_key) \
                            .count_bits() % 2
        self.__state = self.__state ^ round_key

    def __read_constants(self) -> None:
        with open(self.__filename, 'r') as matfile:
            const_data = matfile.read()

        const_data_split = const_data.split('\n')

        # Check for correct parameters and file length
        params = const_data_split[0:3]
        assert params[0] == str(self.__blocksize), \
            "Wrong blocksize in data file!"
        assert params[1] == str(self.__keysize), \
            "Wrong keysize in data file!"
        assert params[2] == str(self.__number_rounds), \
            "Wrong number of rounds in data file!"
        assert (len(const_data_split) - 1) == 3 \
            + (((self.__number_rounds * 2) + 1) * self.__blocksize) \
            + self.__number_rounds, \
            "Wrong file size (number of lines)"

        # Linear layer matrices
        lines_offset = 3
        lines_count = self.__number_rounds * self.__blocksize
        lin_layer = const_data_split[lines_offset:(lines_offset + lines_count)]
        for r in range(self.__number_rounds):
            mat = []
            for s in range(self.__blocksize):
                bv = BitVector(bitstring=lin_layer[(r * self.__blocksize) + s])
                mat.append(bv)
            self.__lin_layer.append(mat)

        # Round constants
        lines_offset += lines_count
        lines_count = self.__number_rounds
        round_consts = const_data_split[lines_offset:(lines_offset +
                                                      lines_count)]
        for line in round_consts:
            self.__round_consts.append(BitVector(bitstring=line))

        # Round key matrices
        lines_offset += lines_count
        lines_count = (self.__number_rounds + 1) * self.__blocksize
        round_key_mats = const_data_split[lines_offset:(lines_offset +
                                                        lines_count)]
        for r in range(self.__number_rounds + 1):
            mat = []
            for s in range(self.__blocksize):
                mat.append(
                    BitVector(bitstring=round_key_mats[(r * self.__blocksize) +
                                                       s]))
            self.__round_key_mats.append(mat)

    def __invert_lin_matrix(self) -> None:

        self.__lin_layer_inv = []
        for r in range(self.__number_rounds):

            # Copy lin_layer
            mat = []
            for i in range(self.__blocksize):
                mat.append(self.__lin_layer[r][i].deep_copy())

            # Create (initial identity) matrix, where the
            # inverted matrix will be stored in.
            inv_mat = []
            for i in range(self.__blocksize):
                temp_bv = BitVector(intVal=0, size=self.__blocksize)
                temp_bv[i] = 1
                inv_mat.append(temp_bv)

            # Transform to upper triangular matrix
            row = 0
            for col in range(self.__keysize):
                if (not mat[row][col]):
                    r = row + 1
                    while ((r < self.__blocksize) and (not mat[r][col])):
                        r += 1
                    if (r >= self.__blocksize):
                        continue
                    else:
                        temp = mat[row]
                        mat[row] = mat[r]
                        mat[r] = temp
                        temp = inv_mat[row]
                        inv_mat[row] = inv_mat[r]
                        inv_mat[r] = temp
                for i in range(row + 1, self.__blocksize):
                    if (mat[i][col]):
                        mat[i] = mat[i] ^ mat[row]
                        inv_mat[i] = inv_mat[i] ^ inv_mat[row]
                row += 1

            # Transform to inverse matrix
            for col in range(self.__keysize, 0, -1):
                for r in range(col - 1):
                    if (mat[r][col - 1]):
                        mat[r] = mat[r] ^ mat[col - 1]
                        inv_mat[r] = inv_mat[r] ^ inv_mat[col - 1]

            self.__lin_layer_inv.append(inv_mat)