def add_hash(self, scr_mem: MemFile) -> None: '''Calculate and insert a cSHAKE256 hash for scr_mem This reads all the scrambled data in logical order, except for the last 8 words. It then calculates the resulting cSHAKE hash and finally inserts that hash (unscrambled) in as the top 8 words. ''' # We only support flat memories of the correct length assert len(scr_mem.chunks) == 1 assert scr_mem.chunks[0].base_addr == 0 assert len(scr_mem.chunks[0].words) == self.rom_size_words assert scr_mem.width == 39 scr_chunk = scr_mem.chunks[0] bytes_per_word = 32 // 8 num_digest_words = 256 // 32 # Read out the scrambled data in logical address order to_hash = b'' for log_addr in range(self.rom_size_words - num_digest_words): phy_addr = self.addr_sp_enc(log_addr) scr_word = scr_chunk.words[phy_addr] to_hash += scr_word.to_bytes(64 // 8, byteorder='little') # Hash it hash_obj = cSHAKE256.new(data=to_hash, custom='ROM_CTRL'.encode('UTF-8')) digest_bytes = hash_obj.read(bytes_per_word * num_digest_words) digest256 = int.from_bytes(digest_bytes, byteorder='little') # Chop the 256-bit digest into 32-bit words. These words should never # be read "unscrambled": the rom_ctrl checker reads them raw. We can # guarantee this by fiddling around with the top 7 bits (which are # otherwise ignored) to ensure that they unscramble to words with # invalid ECC checksums. mask32 = (1 << 32) - 1 first_digest_idx = self.rom_size_words - num_digest_words for digest_idx in range(num_digest_words): log_addr = first_digest_idx + digest_idx w32 = (digest256 >> (32 * digest_idx)) & mask32 found_mismatch = False for chk_bits in range(128): w39 = w32 | (chk_bits << 32) clr39 = self.unscramble_word(39, log_addr, w39) clr32 = clr39 & mask32 exp39 = ecc_encode_some('inv_hsiao', 32, [clr32])[0][0] if clr39 != exp39: # The checksum doesn't match. Excellent! found_mismatch = True break # Surely at least one of the 128 possible choices of top bits # should have given us an invalid checksum. assert found_mismatch phy_addr = self.addr_sp_enc(log_addr) scr_chunk.words[phy_addr] = w32
def add_hash(self, scr_mem: MemFile) -> None: '''Calculate and insert a cSHAKE256 hash for scr_mem This reads all the scrambled data in logical order, except for the last 8 words. It then calculates the resulting cSHAKE hash and finally inserts that hash (unscrambled) in as the top 8 words. ''' # We only support flat memories of the correct length assert len(scr_mem.chunks) == 1 assert scr_mem.chunks[0].base_addr == 0 assert len(scr_mem.chunks[0].words) == self.rom_size_words scr_chunk = scr_mem.chunks[0] data_nonce_width = 64 - self._addr_width subst_perm_rounds = 2 addr_scr_nonce = self.nonce >> data_nonce_width bytes_per_word = 32 // 8 num_digest_words = 256 // 32 # Read out the scrambled data to_hash = b'' for log_addr in range(self.rom_size_words - num_digest_words): phy_addr = subst_perm_enc(log_addr, addr_scr_nonce, self._addr_width, subst_perm_rounds) scr_word = scr_chunk.words[phy_addr] to_hash += scr_word.to_bytes(64 // 8, byteorder='little') # Hash it hash_obj = cSHAKE256.new(data=to_hash, custom='ROM_CTRL'.encode('UTF-8')) digest_bytes = hash_obj.read(bytes_per_word * num_digest_words) digest256 = int.from_bytes(digest_bytes, byteorder='little') # Insert the hash back into scr_mem for digest_idx in range(num_digest_words): log_addr = self.rom_size_words - num_digest_words + digest_idx phy_addr = subst_perm_enc(log_addr, addr_scr_nonce, self._addr_width, subst_perm_rounds) digest_word = (digest256 >> (32 * digest_idx)) & ((1 << 32) - 1) scr_chunk.words[phy_addr] = digest_word