/*## (c) SECURITY EXPLORATIONS    2018 poland                                #*/
/*##     http://www.security-explorations.com                                #*/

/* THIS SOFTWARE IS PROTECTED BY DOMESTIC AND INTERNATIONAL COPYRIGHT LAWS    */
/* UNAUTHORISED COPYING OF THIS SOFTWARE IN EITHER SOURCE OR BINARY FORM IS   */
/* EXPRESSLY FORBIDDEN. ANY USE, INCLUDING THE REPRODUCTION, MODIFICATION,    */
/* DISTRIBUTION, TRANSMISSION, RE-PUBLICATION, STORAGE OR DISPLAY OF ANY      */
/* PART OF THE SOFTWARE, FOR COMMERCIAL OR ANY OTHER PURPOSES REQUIRES A      */
/* VALID LICENSE FROM THE COPYRIGHT HOLDER.                                   */

/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS    */
/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,*/
/* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    */
/* SECURITY EXPLORATIONS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, */
/* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF  */
/* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE     */
/* SOFTWARE.                                                                  */

import java.lang.*;

public class TwofishMem extends Twofish {

 public static Key makeKey(byte direction,int key_len,int key_data) {
  key_len=(key_len+63)&~63;
  int num_rounds=num_rounds_array[(key_len-1)/64];

  int key32[]=new int[MAX_KEY_BITS/32];

  for(int i=0;i<MAX_KEY_BITS/32;i++) {
   key32[i]=0;
  }

  for(int i=0;i<key_len/32;i++) {
   key32[i]=Memory.read_dword(key_data+4*i);
  }

  Key key=new Key(direction,key_len,num_rounds,key32);

  reKey(key);

  return key;
 }

 public static Cipher cipherInit(byte mode,int IV) {
  int iv32[]=new int[BLOCK_SIZE/32];

  if ((mode!=MODE_ECB)&&(IV!=0)) {
   for(int i=0;i<BLOCK_SIZE/32;i++) {
    iv32[i]=Memory.read_dword(IV+4*i);   
   }
  }

  return new Cipher(mode,iv32);  
 }

 public static void blockEncrypt(Cipher cipher,Key key,int input,int input_len,int out_buffer) {
  int x[]=new int[BLOCK_SIZE/32];

  int rounds=key.num_rounds;

  for(int n=0;n<input_len;n+=BLOCK_SIZE,input+=BLOCK_SIZE/8,out_buffer+=BLOCK_SIZE/8) {
   for (int i=0;i<BLOCK_SIZE/32;i++) {
    int val=Memory.read_dword(input+4*i);
    x[i]=Bswap(val)^key.sub_keys[INPUT_WHITEN+i];

    if (cipher.mode==MODE_CBC) x[i]^=cipher.iv32[i];
   }

   for (int r=0;r<rounds;r++) {	
    int t0=F32(x[0],key.sbox_keys,key.key_len);
    int t1=F32(ROL(x[1],8),key.sbox_keys,key.key_len);

    x[3]=ROL(x[3],1);
    x[2]^=t0+t1+key.sub_keys[ROUND_SUBKEYS+2*r];
    x[3]^=t0+2*t1+key.sub_keys[ROUND_SUBKEYS+2*r+1];
    x[2]=ROR(x[2],1);

    if (r<rounds-1) {
     int tmp=x[0];
     x[0]=x[2];
     x[2]=tmp;

     tmp=x[1];
     x[1]=x[3];
     x[3]=tmp;
    }
   }

   for(int i=0;i<BLOCK_SIZE/32;i++) {
    int val=Bswap(x[i]^key.sub_keys[OUTPUT_WHITEN+i]);
    Memory.write_dword(out_buffer+4*i,val);

    if (cipher.mode==MODE_CBC) cipher.iv32[i]=Bswap(val);
   }
  }
 }

 public static void blockDecrypt(Cipher cipher,Key key,int input,int input_len,int out_buffer) {
  int block[]=new int[BLOCK_SIZE/32];
  int x[]=new int[BLOCK_SIZE/32];

  for(int n=0;n<input_len;n+=BLOCK_SIZE,input+=BLOCK_SIZE/8,out_buffer+=BLOCK_SIZE/8) {

   for(int i=0;i<BLOCK_SIZE/32;i++) {
    block[i]=Memory.read_dword(input+4*i);
   }

   blockDecrypt_internal(cipher,key,block,x);

   for(int i=0;i<BLOCK_SIZE/32;i++) {
    Memory.write_dword(out_buffer+4*i,block[i]);
   }
  }
 }

 public static void decrypt_ecb(int buf,int size,int key_data,int key_size) {
  Cipher c=cipherInit((byte)MODE_ECB,0);
  Key k=makeKey((byte)DIR_DECRYPT,key_size*8,key_data);

  rewrite_key(k);

  blockDecrypt(c,k,buf,size*8,buf);
 }

 public static void decrypt_ecb(int buf,int size,int key_data,int key_size,int outbuf) {
  Cipher c=cipherInit((byte)MODE_ECB,0);

  Key k=makeKey((byte)DIR_DECRYPT,key_size*8,key_data);

  rewrite_key(k);

  blockDecrypt(c,k,buf,size*8,outbuf);
 }

 public static void decrypt_cbc(int buf,int size,int key_data,int key_size,int iv) {
  Cipher c=cipherInit((byte)MODE_CBC,iv);
  Key k=makeKey((byte)DIR_DECRYPT,key_size*8,key_data);

  rewrite_key(k);

  blockDecrypt(c,k,buf,size*8,buf);
 }

 public static void decrypt_cbc(int buf,int size,int key_data,int key_size,int iv,int outbuf) {
  Cipher c=cipherInit((byte)MODE_CBC,iv);
  Key k=makeKey((byte)DIR_DECRYPT,key_size*8,key_data);

  rewrite_key(k);

  blockDecrypt(c,k,buf,size*8,outbuf);
 }

}
