/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package javacardapp;

import javacardapp.Secp192k1;
import javacardapp.Secp256k1;

import java.io.UnsupportedEncodingException;

import javacard.framework.*;
import javacardx.apdu.ExtendedLength;
import javacard.framework.*;
import javacard.security.*;
import static javacard.security.Signature.MODE_VERIFY;
import javacardx.crypto.*;

/**
 *
 * @author Damian Vertal
 */
public class GeocachingApp extends Applet implements ExtendedLength{
    
    // Eliptic curves length
    final static short secp256k1Length = (short) 32;
    final static short secp192k1Length = (short) 24;
    
    // Length of key
    final static short keyLength = (short) secp192k1Length;
    
    // Value types
    final static byte CACHE_ID = (byte)0x01;
    final static byte GAMER_ID = (byte)0x02;
    final static byte GAMER_TRAV_ITEM_ID = (byte)0x03;
    final static byte CACHE_TRAV_ITEM_ID = (byte)0x04;
    final static byte CERT = (byte)0x05;
    final static byte CACHE_PUB_KEY = (byte)0x06;
    final static byte GAMER_PUB_KEY = (byte)0x07;
    final static byte CACHE_SIGN = (byte)0x08;
    final static byte GAMER_SIGN = (byte)0x09;
    final static byte CACHE_RNDM_NUMB = (byte)0x10;
    final static byte GAMER_RNDM_NUMB = (byte)0x11;
    final static byte CACHE_CONF_MSG = (byte)0x12;
    final static byte GAMER_MSG = (byte)0x13;
    final static byte DISC_ORDER = (byte)0x14;
    final static byte CA_PUB_KEY = (byte)0x15;
    final static byte CA_SIGN = (byte)0x16;
    //final static byte CACHE_PRIV_KEY = (byte)0x17;
    
    // CLA byte
    final static byte Geocaching_CLA =(byte)0x90;
    
    // INS byte
    final static byte START = (byte) 0x01;
    final static byte STOP = (byte) 0x02;
    final static byte GAME = (byte) 0x03;
    //final static byte CRT_STORE = (byte) 0x04;
    
    
    final static byte[] S = new byte[]{(byte) 'G', (byte) 'C', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9'};
    final static short Slen = (short) 12;
    
    byte[] H;
    final static short Hlen = (short) 22;
    
    byte[] Us;
    final static short UsLen = (short) 22;
    
    byte[] Uh;
    final static short UhLen = (short) 22;
    
    private static byte[] emptyUs = {(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
    
    // Certificates
    private byte[] CRTs = new byte[]{(byte) 0x05, (byte) 0x01, (byte) 0xAA, 
        (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0xA3, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x59, (byte) 0x02, (byte) 0x06, (byte) 0x01, (byte) 0x79, (byte) 0x84, (byte) 0xC6, (byte) 0x57, (byte) 0xAB, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x07, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0xCE, (byte) 0x3D, (byte) 0x04, (byte) 0x01, (byte) 0x30, (byte) 0x15, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x13, (byte) 0x0A, (byte) 0x47, (byte) 0x65, (byte) 0x6F, (byte) 0x63, (byte) 0x61, (byte) 0x63, (byte) 0x68, (byte) 0x69, (byte) 0x6E, (byte) 0x67, (byte) 0x30, (byte) 0x1E, (byte) 0x17, (byte) 0x0D, (byte) 0x32, (byte) 0x31, (byte) 0x30, (byte) 0x35, (byte) 0x31, (byte) 0x39, (byte) 0x31, (byte) 0x33, (byte) 0x31, (byte) 0x37, (byte) 0x34, (byte) 0x31, (byte) 0x5A, (byte) 0x17, (byte) 0x0D, (byte) 0x32, (byte) 0x36, (byte) 0x30, (byte) 0x35, (byte) 0x31, (byte) 0x39, (byte) 0x31, (byte) 0x33, (byte) 0x31, (byte) 0x37, (byte) 0x34, (byte) 0x31, (byte) 0x5A, (byte) 0x30, (byte) 0x17, (byte) 0x31, (byte) 0x15, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x13, (byte) 0x0C, (byte) 0x47, (byte) 0x43, (byte) 0x30, (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x35, (byte) 0x36, (byte) 0x37, (byte) 0x38, (byte) 0x39, (byte) 0x30, (byte) 0x81, (byte) 0xF3, (byte) 0x30, (byte) 0x81, (byte) 0xBC, (byte) 0x06, (byte) 0x07, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0xCE, (byte) 0x3D, (byte) 0x02, (byte) 0x01, (byte) 0x30, (byte) 0x81, (byte) 0xB0, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x30, (byte) 0x24, (byte) 0x06, (byte) 0x07, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0xCE, (byte) 0x3D, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x19, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF, (byte) 0xFF, (byte) 0xEE, (byte) 0x37, (byte) 0x30, (byte) 0x34, (byte) 0x04, (byte) 0x18, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04, (byte) 0x18, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, (byte) 0x04, (byte) 0x31, (byte) 0x04, (byte) 0xDB, (byte) 0x4F, (byte) 0xF1, (byte) 0x0E, (byte) 0xC0, (byte) 0x57, (byte) 0xE9, (byte) 0xAE, (byte) 0x26, (byte) 0xB0, (byte) 0x7D, (byte) 0x02, (byte) 0x80, (byte) 0xB7, (byte) 0xF4, (byte) 0x34, (byte) 0x1D, (byte) 0xA5, (byte) 0xD1, (byte) 0xB1, (byte) 0xEA, (byte) 0xE0, (byte) 0x6C, (byte) 0x7D, (byte) 0x9B, (byte) 0x2F, (byte) 0x2F, (byte) 0x6D, (byte) 0x9C, (byte) 0x56, (byte) 0x28, (byte) 0xA7, (byte) 0x84, (byte) 0x41, (byte) 0x63, (byte) 0xD0, (byte) 0x15, (byte) 0xBE, (byte) 0x86, (byte) 0x34, (byte) 0x40, (byte) 0x82, (byte) 0xAA, (byte) 0x88, (byte) 0xD9, (byte) 0x5E, (byte) 0x2F, (byte) 0x9D, (byte) 0x02, (byte) 0x19, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, (byte) 0x26, (byte) 0xF2, (byte) 0xFC, (byte) 0x17, (byte) 0x0F, (byte) 0x69, (byte) 0x46, (byte) 0x6A, (byte) 0x74, (byte) 0xDE, (byte) 0xFD, (byte) 0x8D, (byte) 0x02, (byte) 0x01, (byte) 0x01, (byte) 0x03, (byte) 0x32, (byte) 0x00, (byte) 0x04, (byte) 0xC8, (byte) 0xE9, (byte) 0x3C, (byte) 0xFA, (byte) 0x55, (byte) 0x46, (byte) 0xD2, (byte) 0xE8, (byte) 0x94, (byte) 0xF2, (byte) 0x2E, (byte) 0xF5, (byte) 0x61, (byte) 0x3D, (byte) 0x83, (byte) 0xAF, (byte) 0x8A, (byte) 0x1B, (byte) 0x7F, (byte) 0x90, (byte) 0xEF, (byte) 0x2F, (byte) 0x08, (byte) 0xDD, (byte) 0x74, (byte) 0xC4, (byte) 0x3D, (byte) 0x08, (byte) 0x7D, (byte) 0x16, (byte) 0xEF, (byte) 0x72, (byte) 0xC9, (byte) 0x6A, (byte) 0xFF, (byte) 0xF9, (byte) 0x52, (byte) 0x3C, (byte) 0xF6, (byte) 0xF3, (byte) 0x78, (byte) 0x52, (byte) 0x86, (byte) 0xC5, (byte) 0x11, (byte) 0xE8, (byte) 0x23, (byte) 0x88, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x07, (byte) 0x2A, (byte) 0x86, (byte) 0x48, (byte) 0xCE, (byte) 0x3D, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0x39, (byte) 0x00, (byte) 0x30, (byte) 0x36, (byte) 0x02, (byte) 0x19, (byte) 0x00, (byte) 0xD0, (byte) 0x3E, (byte) 0x56, (byte) 0x6C, (byte) 0xF7, (byte) 0x2B, (byte) 0x45, (byte) 0x90, (byte) 0x24, (byte) 0x46, (byte) 0xAC, (byte) 0xC4, (byte) 0x60, (byte) 0x21, (byte) 0xF5, (byte) 0xC0, (byte) 0xCB, (byte) 0x11, (byte) 0x07, (byte) 0x73, (byte) 0x7C, (byte) 0x02, (byte) 0xE4, (byte) 0x62, (byte) 0x02, (byte) 0x19, (byte) 0x00, (byte) 0x90, (byte) 0xAC, (byte) 0x94, (byte) 0x85, (byte) 0x54, (byte) 0x4B, (byte) 0x36, (byte) 0x68, (byte) 0xA7, (byte) 0xDA, (byte) 0x5B, (byte) 0xBE, (byte) 0xF7, (byte) 0xB4, (byte) 0xDB, (byte) 0x3F, (byte) 0x32, (byte) 0x5D, (byte) 0x68, (byte) 0x8D, (byte) 0x7A, (byte) 0xD9, (byte) 0xA8, (byte) 0xF1};
    private byte[] CRTh;
    
    // Keys
    final static byte[] bSKs = new byte[]{(byte) 0x38, (byte) 0x47, (byte) 0x64, (byte) 0x78, (byte) 0xE1, (byte) 0x94, (byte) 0x5F, (byte) 0x4C, (byte) 0xBF, (byte) 0x7F, (byte) 0x82, (byte) 0xF8, (byte) 0x2A, (byte) 0x5B, (byte) 0x28, (byte) 0xC2, (byte) 0x36, (byte) 0x53, (byte) 0xE0, (byte) 0x3E, (byte) 0x6E, (byte) 0x72, (byte) 0x2D, (byte) 0x3A};
    final static byte[] bVKs = new byte[]{(byte) 0x04, (byte) 0xC8, (byte) 0xE9, (byte) 0x3C, (byte) 0xFA, (byte) 0x55, (byte) 0x46, (byte) 0xD2, (byte) 0xE8, (byte) 0x94, (byte) 0xF2, (byte) 0x2E, (byte) 0xF5, (byte) 0x61, (byte) 0x3D, (byte) 0x83, (byte) 0xAF, (byte) 0x8A, (byte) 0x1B, (byte) 0x7F, (byte) 0x90, (byte) 0xEF, (byte) 0x2F, (byte) 0x08, (byte) 0xDD, (byte) 0x74, (byte) 0xC4, (byte) 0x3D, (byte) 0x08, (byte) 0x7D, (byte) 0x16, (byte) 0xEF, (byte) 0x72, (byte) 0xC9, (byte) 0x6A, (byte) 0xFF, (byte) 0xF9, (byte) 0x52, (byte) 0x3C, (byte) 0xF6, (byte) 0xF3, (byte) 0x78, (byte) 0x52, (byte) 0x86, (byte) 0xC5, (byte) 0x11, (byte) 0xE8, (byte) 0x23, (byte) 0x88};
    private ECPrivateKey SKs;
    private ECPublicKey VKs;
    
    byte[] bVKh;
    private ECPublicKey VKh;
    
    final static byte[] bVKca = new byte[]{(byte) 0x04, (byte) 0x86, (byte) 0xEF, (byte) 0x1D, (byte) 0x16, (byte) 0x37, (byte) 0x01, (byte) 0xBC, (byte) 0xC0, (byte) 0xEC, (byte) 0x96, (byte) 0x28, (byte) 0xEB, (byte) 0x8F, (byte) 0x12, (byte) 0x94, (byte) 0x47, (byte) 0xA3, (byte) 0xF2, (byte) 0xEB, (byte) 0x5B, (byte) 0x73, (byte) 0xC4, (byte) 0x05, (byte) 0xFF, (byte) 0xC7, (byte) 0x87, (byte) 0x7B, (byte) 0xEE, (byte) 0x8A, (byte) 0xF1, (byte) 0xEC, (byte) 0x4C, (byte) 0x36, (byte) 0xA1, (byte) 0x2A, (byte) 0xF6, (byte) 0xFF, (byte) 0xD9, (byte) 0xCB, (byte) 0x52, (byte) 0xA4, (byte) 0x16, (byte) 0xC1, (byte) 0x83, (byte) 0x90, (byte) 0x4E, (byte) 0x05, (byte) 0x2B};
    private ECPublicKey VKca;
    
    private KeyPair keyPair;
    
    // Signaure
    private Signature ecdsa;
    
    
    byte[] Rs;
    byte[] Rh;
    
    private short rndNumbLength = (short)5;
    
    // Buffers
    byte[] data;
    private byte[] Zh;
    short ZhLen;
    private byte[] Zs;
    private byte[] Ph;
    
    // Counter
    private byte[] N;
    short NLen = (short) 2;
    
    // Other variables
    boolean stop;
    short offset;
    
    private GeocachingApp (byte[] bArray, short bOffset, byte bLength){
        register();
        
        Us = new byte[]{(byte) 'T', (byte) 'G', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4', (byte) '4'};
        
        H = JCSystem.makeTransientByteArray((short)Hlen, JCSystem.CLEAR_ON_DESELECT);
        
        Uh = JCSystem.makeTransientByteArray((short)UhLen, JCSystem.CLEAR_ON_DESELECT);
        
        Zh = JCSystem.makeTransientByteArray((short)(rndNumbLength*2 + Slen + UsLen + 8), JCSystem.CLEAR_ON_DESELECT);
        
        Zs = JCSystem.makeTransientByteArray((short)(rndNumbLength*2 + Slen + Hlen + NLen + UsLen + UhLen + 14), JCSystem.CLEAR_ON_DESELECT);
        
        Ph = JCSystem.makeTransientByteArray((short)256, JCSystem.CLEAR_ON_DESELECT);
        
        Rs = JCSystem.makeTransientByteArray((short)5, JCSystem.CLEAR_ON_DESELECT);
        
        Rh = JCSystem.makeTransientByteArray((short)5, JCSystem.CLEAR_ON_DESELECT);
        
        bVKh = JCSystem.makeTransientByteArray((short)(keyLength*2 + 1), JCSystem.CLEAR_ON_DESELECT);
        
        stop = false;
        
        keyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_192);
        keyPair.genKeyPair();
        
        ecdsa = Signature.getInstance(Signature.ALG_ECDSA_SHA, false);
        
        //CRTh = JCSystem.makeTransientByteArray((short)256, JCSystem.CLEAR_ON_DESELECT);
        
        data = new byte[(short)1280];
        offset = 0;
    }
    
    public static void install(byte[] bArray, short bOffset, byte bLength) {
        new GeocachingApp(bArray, bOffset, bLength);
    }
    
    public boolean select() {
        
        return true;
    }
    
    public void deselect() {
        
    }

    @Override
    public void process(APDU apdu) throws ISOException {
        if (stop == true) 
        	ISOException.throwIt (ISO7816.SW_COMMAND_NOT_ALLOWED);
        
       byte[] buffer = apdu.getBuffer();
        
        byte cla = buffer[(short)ISO7816.OFFSET_CLA];
        byte ins = buffer[(short)ISO7816.OFFSET_INS];
        
        if (selectingApplet())
            return;
        
        if (cla != Geocaching_CLA)
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        
        switch (ins) {
            case START: { 
                
                try {
                    startProtocol(apdu);
                }
                catch (APDUException exception) {
                    ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                }
                break;
            }
            
            case STOP: { 
                stop = true;
		ISOException.throwIt (ISO7816.SW_NO_ERROR);
                break;
            }
            
            case GAME: { 
                try {
                    offset = 0;
                    Util.arrayFillNonAtomic(data, offset, (short) data.length, (byte) 0);
                    receive(apdu);
                    
                    short ZhOffs = getCRThFromData();
                    if (verifyCRT()) {
                        short PhOffs = getZhFromData(ZhOffs);
                        getPhFromData(PhOffs);
                        if (verifyPh()) {
                            if (getAndCheckSubdataFromZh()) {
                                short sendBlockLen = prepareDataToSend();
                                
                                send(apdu, sendBlockLen);
                                
                            }
                            else {
                                ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                            }
                        }
                        else {
                            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                        }
                    }
                    else {
                        ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                    }
                    
                    
                } catch (APDUException exception) {
                    ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                }
                        
                break;
            }
            
            case 0x11: {
		
                apdu.setOutgoingAndSend((short) 0, (short)300);
                
                break;
            }
            
            default: {
                ISOException.throwIt (ISO7816.SW_INS_NOT_SUPPORTED);
                break;
            }
        }
    }
    
    private void receive(APDU apdu) throws APDUException {
        byte[] buffer = apdu.getBuffer();

        //short bufferOffset = apdu.getOffsetCdata();
		short bufferOffset = (short) 7;
		
        // set incoming and receive first data
        short recvLength = apdu.setIncomingAndReceive();
        Util.arrayCopyNonAtomic(buffer, bufferOffset, data, offset, recvLength);
        offset += recvLength;

        // receive more data if available
        short bytesLeft = apdu.getIncomingLength();
        bytesLeft -= recvLength;

        while (bytesLeft > 0) {
            recvLength = apdu.receiveBytes((short) 0); // read more APDU data
            Util.arrayCopyNonAtomic(buffer, (short) 0, data, offset, recvLength);
            offset += recvLength;

            bytesLeft -= recvLength;
        }
    }
    
    private void send(APDU apdu, short len) throws APDUException {
        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(data, (short)0, buffer, (short)0, (short)len);
        apdu.setOutgoingAndSend((short) 0, (short)len);
    }
    
    
    private void startProtocol(APDU apdu) {
        
        byte[] buffer = apdu.getBuffer();
        
        RandomData random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
        random.generateData(Rs,(short)0, (short)5);
        
        byte[] cacheRndmNumb = valueToAVPformat(CACHE_RNDM_NUMB, Rs);
        byte[] cacheId = valueToAVPformat(CACHE_ID, S);
        
        Util.arrayCopyNonAtomic(cacheRndmNumb, (short) 0, buffer, (short) 0, (short) cacheRndmNumb.length);
        Util.arrayCopyNonAtomic(cacheId, (short) 0, buffer, (short) cacheRndmNumb.length, (short) cacheId.length);
        
        apdu.setOutgoingAndSend((short) 0, (short) (cacheRndmNumb.length + cacheId.length));
    }
    
    
    private short getCRThFromData() {
        if (data[(short)0] == CERT) {
            //short CERTlen = (short) (data[(short)1]);
            short CERTlen = Util.getShort(data, (short)1);
            
            CRTh = JCSystem.makeTransientByteArray((short)CERTlen, JCSystem.CLEAR_ON_DESELECT);
            Util.arrayCopyNonAtomic(data, (short) 0, CRTh, (short) 0, (short) CERTlen);
            return CERTlen;
        }
        else
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        return (short)0;
    }
    
    private boolean verifyCRT() {
        short avpCertHeaderLen = (short) 3;
        short dataOffs = (short) (avpCertHeaderLen + 4);
        short dataLen = (short) (Util.getShort(CRTh, (short)(dataOffs + 2)) + 4);
        short signSpecOffs = (short) (dataOffs + dataLen);
        short signSpecLen = (short) (CRTh[(short)(signSpecOffs + 1)] + 2);
        short signBlockOffs = (short) (signSpecOffs + signSpecLen);
        short signBlockLen = (short) (CRTh[(short)(signBlockOffs + 1)] + 2);
        
        byte[] dataToVerify = new byte[(short)dataLen];
        Util.arrayCopyNonAtomic(CRTh, (short)(dataOffs), dataToVerify, (short) 0, (short) dataLen);
        
        short signLen = (short) (signBlockLen - 3);
        byte[] sign = new byte[(short)signLen];
        Util.arrayCopyNonAtomic(CRTh, (short) (signBlockOffs + 3), sign, (short) 0, (short) signLen);
        
        createCAPubKey();
        
        ecdsa.init(VKca, Signature.MODE_VERIFY);
        
        //Verify the signature of input data against the passed in ECC signature.
        boolean result = ecdsa.verify(dataToVerify, (short) 0, dataLen, sign, (short) 0, signLen);
        
        if (result == true){
            getIDFromCert(CRTh, H);
            getPubKFromCert(CRTh, bVKh);
            
            return true;
        }
                
        return false;
    }
    
    private void getIDFromCert(byte[] CRT, byte[] id) {
        short avpCertHeaderLen = (short) 3;
        short dataOffs = (short) (avpCertHeaderLen + 4);
        short dataLen = (short) (Util.getShort(CRT, (short)(dataOffs + 2)) + 4);
        short offs1 = (short) (dataOffs + 4);
        short len1 = (short) (CRT[(short)(offs1 + 1)] + 2);
        short offs2 = (short) (offs1 + len1);
        short len2 = (short) (CRT[(short)(offs2 + 1)] + 2);
        short offs3 = (short) (offs2 + len2);
        short len3 = (short) (CRT[(short)(offs3 + 1)] + 2);
        short offs4 = (short) (offs3 + len3);
        short len4 = (short) (CRT[(short)(offs4 + 1)] + 2);
        short offs5 = (short) (offs4 + len4);
        short offs6 = (short) (offs5 + 2);
        short offs7 = (short) (offs6 + 2);
        short offs8 = (short) (offs7 + 2);
        short len8 = (short) (CRT[(short)(offs8 + 1)] + 2);
        short offs9 = (short) (offs8 + len8);
        short len9 = (short) (CRT[(short)(offs9 + 1)] + 2);
        short offsH = (short) (offs9 + 2);
        short lenH = (short) (len9 - 2);
        
        Util.arrayCopyNonAtomic(CRT, (short)(offsH), id, (short)0, (short)lenH);
    }
    
    private void getPubKFromCert(byte[] CRT, byte[] key) {
        short avpCertHeaderLen = (short) 3;
        short dataOffs = (short) (avpCertHeaderLen + 4);
        short dataLen = (short) (Util.getShort(CRT, (short)(dataOffs + 2)) + 4);
        short offs1 = (short) (dataOffs + 4);
        short len1 = (short) (CRT[(short)(offs1 + 1)] + 2);
        short offs2 = (short) (offs1 + len1);
        short len2 = (short) (CRT[(short)(offs2 + 1)] + 2);
        short offs3 = (short) (offs2 + len2);
        short len3 = (short) (CRT[(short)(offs3 + 1)] + 2);
        short offs4 = (short) (offs3 + len3);
        short len4 = (short) (CRT[(short)(offs4 + 1)] + 2);
        short offs5 = (short) (offs4 + len4);
        short len5 = (short) (CRT[(short)(offs5 + 1)] + 2);
        short offs6 = (short) (offs5 + len5);
        short offs7 = (short) (offs6 + 3);
        short len7 = (short) (getShortFromByteArray(CRT, (short)(offs7 + 2)) + 3);
        short offs8 = (short) (offs7 + len7);
        short len8 = (short) (CRT[(short)(offs8 + 1)] + 2);
        short offsKey = (short) (offs8 + 3);
        short lenKey = (short) (len8 - 3);
        
        Util.arrayCopyNonAtomic(CRT, (short)(offsKey), key, (short)0, (short)lenKey);
    }
    
    private short getZhFromData(short offs) {
        short RhLen = (short) (data[(short)(offs + 1)]);
        short RsLen = (short) (data[(short)(offs + 1 + RhLen)]);
        short Slen = (short) (data[(short)(offs + 1 + RhLen + RsLen)]);
        short UhLen = (short) (data[(short)(offs + 1 + RhLen + RsLen + Slen)]);
        
        ZhLen = (short) (RhLen + RsLen + Slen + UhLen);
        
        Util.arrayCopyNonAtomic(data, (short) offs, Zh, (short) 0, (short) ZhLen);
        
        return (short)(offs + ZhLen);
    }
    
    private boolean getAndCheckSubdataFromZh() {
        short RhLen = (short) (Zh[(short)(1)]);
        short RsLen = (short) (Zh[(short)(1 + RhLen)]);
        short Srlen = (short) (Zh[(short)(1 + RhLen + RsLen)]);
        short UhLen = (short) (Zh[(short)(1 + RhLen + RsLen + Srlen)]);
        
        short RhOffs = (short) (2);
        short RsOffs = (short) (2 + RhLen);
        short SOffs = (short) (2 + RhLen + RsLen);
        short UhOffs = (short) (2 + RhLen + RsLen + Srlen);
        
        if((Zh[(short)(RhOffs - (short)2)] == GAMER_RNDM_NUMB) && (Zh[(short)(RsOffs - (short)2)] == CACHE_RNDM_NUMB) && (Zh[(short)(SOffs - (short)2)] == CACHE_ID) && (Zh[(short)(UhOffs - (short)2)] == GAMER_TRAV_ITEM_ID)) {
            Util.arrayCopyNonAtomic(Zh, (short) RhOffs, Rh, (short) 0, (short) Rh.length);
            
            byte[] Rsr = new byte[(short)5];
            Util.arrayCopyNonAtomic(Zh, (short) RsOffs, Rsr, (short) 0, (short) Rsr.length);
            if (compareTwoByteArrays(Rs, Rsr) == false)
                return false;
            
            byte[] Sr = new byte[(short)Slen];
            Util.arrayCopyNonAtomic(Zh, (short) SOffs, Sr, (short) 0, (short) Slen);
            if (compareTwoByteArrays(S, Sr) == false)
                return false;
            
            Util.arrayCopyNonAtomic(Zh, (short) UhOffs, Uh, (short) 0, (short) UsLen);
            
            return true;
        }
        
        else {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        
        return false;
    }
    
    private short getPhFromData(short offs) {
        if (data[(short)offs] == GAMER_SIGN) {
            short PhLen = (short) (data[(short)(offs + 1)]);
        
            Util.arrayCopyNonAtomic(data, (short) offs, Ph, (short) 0, (short) PhLen);
        
            return (short)(offs + PhLen);
        }
        else
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        return (short)0;
    }
    
    private boolean verifyPh() {
        short signLen = (short) (Ph[(short)1] - (short)2);
        byte[] sign = new byte[(short)signLen];
        Util.arrayCopyNonAtomic(Ph, (short) 2, sign, (short) 0, (short) signLen);
        
        createPlayerPubKey();
        
        ecdsa.init(VKh, Signature.MODE_VERIFY);
        
        //Verify the signature of input data against the passed in ECC signature.
        boolean result = ecdsa.verify(Zh, (short) 0, ZhLen, sign, (short) 0, signLen);
        
        return result;
    }
    
    
    
    private short prepareDataToSend() {
        short ZsOffs = (short) 0;
        short PsOffs = (short) 0;
        short dataEnd = (short) 0;
        
        ZsOffs = prepareCertToSend();
        PsOffs = prepareZsToSend(ZsOffs);
        dataEnd = preparePsToSend(ZsOffs, PsOffs);
        
        return (short)(dataEnd);
    }
    
    private short prepareCertToSend() {
        
        Util.arrayCopyNonAtomic(CRTs, (short) 0, data, (short) 0, (short)CRTs.length);
        
        return ((short) CRTs.length);
    }
    
    private short prepareZsToSend(short offs) {
        
        byte[] cacheRndmNumb = valueToAVPformat(CACHE_RNDM_NUMB, Rs);
        byte[] playerRndmNumb = valueToAVPformat(GAMER_RNDM_NUMB, Rh);
        byte[] cacheId = valueToAVPformat(CACHE_ID, S);
        byte[] playerId = valueToAVPformat(GAMER_ID, H);
        byte[] discOrder = valueToAVPformat(DISC_ORDER, incrementDiscOrder());
        byte[] cacheTravelItem = valueToAVPformat(CACHE_TRAV_ITEM_ID, Us);
        byte[] playerTravelItem = valueToAVPformat(GAMER_TRAV_ITEM_ID, Uh);
        
        Util.arrayCopyNonAtomic(cacheRndmNumb, (short) 0, data, (short) offs, (short)cacheRndmNumb.length);
        Util.arrayCopyNonAtomic(playerRndmNumb, (short) 0, data, (short)(offs + cacheRndmNumb.length), (short)playerRndmNumb.length);
        Util.arrayCopyNonAtomic(cacheId, (short) 0, data, (short)(offs + cacheRndmNumb.length + playerRndmNumb.length), (short)cacheId.length);
        Util.arrayCopyNonAtomic(playerId, (short) 0, data, (short)(offs + cacheRndmNumb.length + playerRndmNumb.length + cacheId.length), (short)playerId.length);
        Util.arrayCopyNonAtomic(discOrder, (short) 0, data, (short)(offs + cacheRndmNumb.length + playerRndmNumb.length + cacheId.length + playerId.length), (short)discOrder.length);
        Util.arrayCopyNonAtomic(cacheTravelItem, (short) 0, data, (short)(offs + cacheRndmNumb.length + playerRndmNumb.length + cacheId.length + playerId.length + discOrder.length), (short)cacheTravelItem.length);
        Util.arrayCopyNonAtomic(playerTravelItem, (short) 0, data, (short)(offs + cacheRndmNumb.length + playerRndmNumb.length + cacheId.length + playerId.length + discOrder.length + cacheTravelItem.length), (short)playerTravelItem.length);
        
        Util.arrayCopyNonAtomic(Uh, (short) 0, Us, (short) 0, (short)UsLen);
        Util.arrayFillNonAtomic(Uh, (short) 0, (short) Uh.length, (byte) 0);
        
        offs = (short) (offs + cacheRndmNumb.length + playerRndmNumb.length + cacheId.length + playerId.length + discOrder.length + cacheTravelItem.length + playerTravelItem.length);
        
        return ((short) offs);
    }
    
    private short preparePsToSend(short ZsOffs, short PsOffs) {
        short signBlockLen = (short) 0;
        
        signBlockLen = sign(ZsOffs, PsOffs);
        
        return (short) (signBlockLen + PsOffs);
    }
    
    private short sign(short dataOffs, short signOffs) {
        short dataLen = (short)(signOffs - dataOffs);
        
        createCacheKeyPair();
        
        ecdsa.init(SKs, Signature.MODE_SIGN);
        
        short signLen = ecdsa.sign(data, dataOffs, dataLen, data, (short)(signOffs + 2));
        
        data[(short)signOffs] = CACHE_SIGN;
        data[(short)(signOffs + 1)] = (byte)(signLen + 2);
        
        return (short)(signLen + 2);
    }
    
    
    private void assignCurveParametersPubK(ECPublicKey pubKey, byte[] W) {
        switch (keyLength) {
            case secp192k1Length: { 
                pubKey.setA(Secp192k1.SECP192K1_A, (short)0, (short)24);
                pubKey.setB(Secp192k1.SECP192K1_B, (short)0, (short)24);
                pubKey.setFieldFP(Secp192k1.SECP192K1_FP, (short)0, (short)24);
                pubKey.setG(Secp192k1.SECP192K1_G, (short)0, (short)49);
                pubKey.setK((short)1);
                pubKey.setR(Secp192k1.SECP192K1_R, (short)0, (short)24);
                pubKey.setW(W, (short)0, (short)49);
        
                break;
            }
            
            case secp256k1Length: { 
                pubKey.setA(Secp256k1.SECP256K1_A, (short)0, (short)24);
                pubKey.setB(Secp256k1.SECP256K1_B, (short)0, (short)24);
                pubKey.setFieldFP(Secp256k1.SECP256K1_FP, (short)0, (short)24);
                pubKey.setG(Secp256k1.SECP256K1_G, (short)0, (short)49);
                pubKey.setK((short)1);
                pubKey.setR(Secp256k1.SECP256K1_R, (short)0, (short)24);
                pubKey.setW(W, (short)130, (short)65);
        
                break;
            }
            
            default: {
                ISOException.throwIt (ISO7816.SW_INS_NOT_SUPPORTED);
                break;
            }
        }
    }
    
    private void assignCurveParametersPrivK(ECPrivateKey privKey, byte[] S) {
        switch (keyLength) {
            case secp192k1Length: { 
                privKey.setA(Secp192k1.SECP192K1_A, (short)0, (short)24);
                privKey.setB(Secp192k1.SECP192K1_B, (short)0, (short)24);
                privKey.setFieldFP(Secp192k1.SECP192K1_FP, (short)0, (short)24);
                privKey.setG(Secp192k1.SECP192K1_G, (short)0, (short)49);
                privKey.setK((short)1);
                privKey.setR(Secp192k1.SECP192K1_R, (short)0, (short)24);
                privKey.setS(S, (short)0, (short)24);
        
                break;
            }
            
            case secp256k1Length: { 
                privKey.setA(Secp192k1.SECP192K1_A, (short)0, (short)24);
                privKey.setB(Secp192k1.SECP192K1_B, (short)0, (short)24);
                privKey.setFieldFP(Secp192k1.SECP192K1_FP, (short)0, (short)24);
                privKey.setG(Secp192k1.SECP192K1_G, (short)0, (short)49);
                privKey.setK((short)1);
                privKey.setR(Secp192k1.SECP192K1_R, (short)0, (short)24);
                privKey.setS(S, (short)0, (short)24);
        
                break;
            }
            
            default: {
                ISOException.throwIt (ISO7816.SW_INS_NOT_SUPPORTED);
                break;
            }
        }
    }
    
    
    private void createCacheKeyPair() {
        //keyPair.genKeyPair();
        
        VKs = (ECPublicKey)keyPair.getPublic();
        SKs = (ECPrivateKey)keyPair.getPrivate();
	
        assignCurveParametersPubK(VKs, bVKs);
        assignCurveParametersPrivK(SKs, bSKs);
    }
    
    private void createPlayerPubKey() {
        //keyPair.genKeyPair();
        
        VKh = (ECPublicKey)keyPair.getPublic();
	
        assignCurveParametersPubK(VKh, bVKh);
    }
    
    private void createCAPubKey() {
        //keyPair.genKeyPair();
        
        VKca = (ECPublicKey)keyPair.getPublic();
	
        assignCurveParametersPubK(VKca, bVKca);
    }
    
    
    private byte[] incrementDiscOrder() throws APDUException {
        if (N == null) N = new byte[2];
        
        short order = Util.getShort(N, (short)0);
        order = (short)(order + 1);
        Util.setShort(N, (short)0, order);
        
        return N;
    }
    
    private byte[] valueToAVPformat(byte type, byte[] value) throws APDUException {
        byte[] avp = new byte[(short)(value.length + (short)2)];
        avp[(short)0] = type;
        avp[(short)1] = (byte)(avp.length);
        Util.arrayCopyNonAtomic(value, (short) 0, avp, (short) 2, (short) value.length);
        
        return avp;
    }
    
    private boolean compareTwoByteArrays(byte[] a1, byte[] a2) {
        if ((short)a1.length != (short)a2.length)
            return false;
        
        for (short i = 0; i < a1.length; i++){
            if (a1[(short)i] != a2[(short)i])
                return false;
        }
        
        return true;
    }
    
    private short getShortFromByteArray(byte[] array, short offs) {
        byte[] a = new byte[2];
        
        a[(short)0] = 0x00;
        a[(short)1] = array[(short)offs];
        
        return Util.getShort(a, (short)0);
    }
    
}
