""" cryptopy.cipher.icedoll

    Modification of Rijndael to provide infinite error extension.
    The ith round of Rijndael is tapped and used to process the
    subsequent block.

    Changes to base Rijndael are marked with: '# --------------------------'

        For Rijndael with N rounds, normally ECB mode is C[i] = Ek(N,P[i])

        Modification is:
        Fi = Ek(t,P[i-1]) ; Fi, with i=0 is nonce or a fixed value
        C[i] = Fi^Ek(N,P[i]^Fi)

    Copyright (c) 2002 by Paul A. Lambert
    Read LICENSE.txt for license information.

    June 2002
    February 2003 -> discovered Ron Rivest's "Tweakable Block Ciphers"
                     http://theory.lcs.mit.edu/~rivest/publications.html
                     These are about the same concept ....
"""

from cryptopy.cipher.base import BlockCipherWithIntegrity, padWithPadLen, noPadding
from cryptopy.cipher.rijndael import  *
from binascii_plus import b2a_hex
from copy import deepcopy

class Icedoll(Rijndael):
    """ IceDoll encryption algorithm
        based on Rijndael, with added feedback for better integrity processing.
        Note - no integrity check is built into Icedoll directly
    """
    def __init__(self,key=None,padding=padWithPadLen(),keySize=16,blockSize=16,tapRound=6,extraRounds=6):
        """ key, keysize, blockSize same as Rijndael, tapROund is feedback tap, """
        self.tapRound    = tapRound     # <------- !!! change from Rijndael !!!
        self.extraRounds = extraRounds  # <------- !!! change from Rijndael !!!
        self.name        = 'ICEDOLL'
        self.keySize     = keySize
        self.strength    = keySize
        self.blockSize   = blockSize  # blockSize is in bytes
        self.padding     = padding    # change default to noPadding() to get normal ECB behavior

        assert( keySize%4==0 and NrTable[4].has_key(keySize/4)),'key size must be 16,20,24,29 or 32 bytes'
        assert( blockSize%4==0 and NrTable.has_key(blockSize/4)), 'block size must be 16,20,24,29 or 32 bytes'

        self.Nb = self.blockSize/4          # Nb is number of columns of 32 bit words
        self.Nk = keySize/4                 # Nk is the key length in 32-bit words
        self.Nr = NrTable[self.Nb][self.Nk]+extraRounds # <------- !!! change from Rijndael !!!

        if key != None:
            self.setKey(key)

    def setKey(self, key):
        """ Set a key and generate the expanded key """
        assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
        self.__expandedKey = keyExpansion(self, key)
        self.reset()                   # BlockCipher.reset()

    def encryptBlock(self, plainTextBlock):
        """ Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
        self.state = self._toBlock(plainTextBlock)
        if self.encryptBlockCount == 0:   # first call, set frdd back
            self.priorFeedBack = self._toBlock(chr(0)*(4*self.Nb)) # <------- !!! change from Rijndael !!!
        AddRoundKey(self, self.priorFeedBack)                      # <------- !!! change from Rijndael !!!
        AddRoundKey(self, self.__expandedKey[0:self.Nb])
        for round in range(1,self.Nr):          #for round = 1 step 1 to Nr1
            SubBytes(self)
            ShiftRows(self)
            MixColumns(self)
            if round == self.tapRound:
                        nextFeedBack = deepcopy(self.state)        # <------- !!! change from Rijndael !!!
            AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
        SubBytes(self)
        ShiftRows(self)
        AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
        AddRoundKey(self, self.priorFeedBack)                      # <------- !!! change from Rijndael !!!
        self.priorFeedBack = nextFeedBack                          # <------- !!! change from Rijndael !!!
        return self._toBString(self.state)

    def decryptBlock(self, encryptedBlock):
        """ decrypt a block (array of bytes) """
        self.state = self._toBlock(encryptedBlock)
        if self.decryptBlockCount == 0:   # first call, set frdd back
            self.priorFeedBack = self._toBlock( chr(0)*(4*self.Nb) ) # <------- !!! change from Rijndael !!!
        AddRoundKey(self, self.priorFeedBack)                        # <------- !!! change from Rijndael !!!
        AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
        for round in range(self.Nr-1,0,-1):
            InvShiftRows(self)
            InvSubBytes(self)
            AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
            if round == self.tapRound:
                        nextFeedBack = deepcopy(self.state)          # <------- !!! change from Rijndael !!!
            InvMixColumns(self)
        InvShiftRows(self)
        InvSubBytes(self)
        AddRoundKey(self, self.__expandedKey[0:self.Nb])
        AddRoundKey(self, self.priorFeedBack) # <------- !!! change from Rijndael !!!
        self.priorFeedBack = nextFeedBack     # <------- !!! change from Rijndael !!!
        return self._toBString(self.state)


