/*
 * SatoChip Bitcoin Hardware Wallet based on javacard
 * (c) 2015-2019 by Toporin - 16DMCk4WUaHofchAhpMaQS4UPm4urcy2dN
 * Sources available on https://github.com/Toporin					 
 * Changes include: -Bip32 support
 * 					-simple Bitcoin transaction signatures 
 * 					-Bitcoin message signatures
 * 					
 *  
 * Based on the M.US.C.L.E framework:
 * see http://pcsclite.alioth.debian.org/musclecard.com/musclecard/
 * see https://github.com/martinpaljak/MuscleApplet/blob/d005f36209bdd7020bac0d783b228243126fd2f8/src/com/musclecard/CardEdge/CardEdge.java
 * 
 *  MUSCLE SmartCard Development
 *      Authors: Tommaso Cucinotta <[email protected]>
 *	         	 David Corcoran    <[email protected]>
 *	    Description:      CardEdge implementation with JavaCard
 *      Protocol Authors: Tommaso Cucinotta <[email protected]>
 *		                  David Corcoran <[email protected]>
 *      
 * BEGIN LICENSE BLOCK
 * Copyright (C) 1999-2002 David Corcoran <[email protected]>
 * Copyright (C) 2015-2019 Toporin 
 * All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 * END LICENSE_BLOCK  
 */

package org.satochip.applet;

import javacard.framework.APDU;
//import javacard.framework.CardRuntimeException;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.SystemException;
import javacard.framework.Util;
import javacard.security.AESKey;
import javacard.security.ECPrivateKey;
import javacard.security.ECPublicKey;
import javacard.security.CryptoException;
import javacard.security.Key;
import javacard.security.KeyAgreement;
import javacard.security.KeyBuilder;
import javacard.security.Signature;
import javacard.security.MessageDigest;
import javacard.security.RandomData;
import javacardx.crypto.Cipher;

/**
 * Implements MUSCLE's Card Edge Specification.
 */
public class CardEdge extends javacard.framework.Applet { 

	/* constants declaration */
	
	/** 
	 * VERSION HISTORY
	 * PROTOCOL VERSION: changes that impact compatibility with the client side
	 * APPLET VERSION:   changes with no impact on compatibility of the client
	 */
	// 0.1-0.1: initial version
	// 0.2-0.1: support for hmac-sha1 authorisation + improved sha256 + bip32 full support  
	// 0.3-0.1: change parseTransaction response for coherence: add separate short flags for second factor authentication  
	// 0.3-0.2: fast Sha512 computation  
    // 0.4-0.1: getBIP32ExtendedKey also returns chaincode
	// 0.5-0.1: Support for Segwit transaction
	// 0.5-0.2: bip32 cached memory optimisation: fixed array instead of list 
	// 0.6-0.1: bip32 optimisation: speed up computation during derivation of non-hardened child 
	// 0.6-0.2: get_status returns number of pin/puk tries remaining
    // 0.6-0.3: Patch in function SignTransaction(): add optional 2FA flag in data
    // 0.7-0.1: add CryptTransaction2FA() to encrypt/decrypt tx messages sent to 2FA device for privacy
	// 0.7-0.2: 2FA patch to mitigate replay attack when importing a new seed
	// 0.8-0.1: add APDUs to reset the seed/eckey/2FA. 2FA required to sign tx/msg and reset seed/eckey/2FA. 2FA can only be disabled when all privkeys are cleared.
	// 0.9-0.1: Message signing for altcoin. 
	// 0.10-0.1: add method SignTransactionHash()
	// 0.10-0.2: support for native sha512
	// 0.10-0.3: ECkey recovery optimisation: support ALG_EC_SVDP_DH_PLAIN_XY
	// 0.10-0.4: Cleanup: optimised code only, removed legacy sha512 implementation and slow pubkey recovery
	// 0.11-0.1: support (mandatory) secure channel
	private final static byte PROTOCOL_MAJOR_VERSION = (byte) 0; 
	private final static byte PROTOCOL_MINOR_VERSION = (byte) 11;
	private final static byte APPLET_MAJOR_VERSION = (byte) 0;
	private final static byte APPLET_MINOR_VERSION = (byte) 1;
	
	// Maximum number of keys handled by the Cardlet
	private final static byte MAX_NUM_KEYS = (byte) 16;
	// Maximum number of PIN codes
	private final static byte MAX_NUM_PINS = (byte) 8; // TODO: set to 2?

	// Maximum size for the extended APDU buffer 
	private final static short EXT_APDU_BUFFER_SIZE = (short) 268;
	private final static short TMP_BUFFER_SIZE = (short) 256;

	// Minimum PIN size
	private final static byte PIN_MIN_SIZE = (byte) 4;
	// Maximum PIN size
	private final static byte PIN_MAX_SIZE = (byte) 16;// TODO: increase size?
	// PIN[0] initial value...
	private final static byte[] PIN_INIT_VALUE={(byte)'M',(byte)'u',(byte)'s',(byte)'c',(byte)'l',(byte)'e',(byte)'0',(byte)'0'};

	// code of CLA byte in the command APDU header
	private final static byte CardEdge_CLA = (byte) 0xB0;

	/****************************************
	 * Instruction codes *
	 ****************************************/

	// Applet initialization
	private final static byte INS_SETUP = (byte) 0x2A;

	// Keys' use and management
	private final static byte INS_IMPORT_KEY = (byte) 0x32;
	private final static byte INS_RESET_KEY = (byte) 0x33;
	private final static byte INS_GET_PUBLIC_FROM_PRIVATE= (byte)0x35;
	
	// External authentication
	private final static byte INS_CREATE_PIN = (byte) 0x40; //TODO: remove?
	private final static byte INS_VERIFY_PIN = (byte) 0x42;
	private final static byte INS_CHANGE_PIN = (byte) 0x44;
	private final static byte INS_UNBLOCK_PIN = (byte) 0x46;
	private final static byte INS_LOGOUT_ALL = (byte) 0x60;
	
	// Status information
	private final static byte INS_LIST_PINS = (byte) 0x48;
	private final static byte INS_GET_STATUS = (byte) 0x3C;
	
	// HD wallet
	private final static byte INS_BIP32_IMPORT_SEED= (byte) 0x6C;
	private final static byte INS_BIP32_RESET_SEED= (byte) 0x77;
	private final static byte INS_BIP32_GET_AUTHENTIKEY= (byte) 0x73;
	private final static byte INS_BIP32_SET_AUTHENTIKEY_PUBKEY= (byte)0x75;
	private final static byte INS_BIP32_GET_EXTENDED_KEY= (byte) 0x6D;
	private final static byte INS_BIP32_SET_EXTENDED_PUBKEY= (byte) 0x74;
	private final static byte INS_SIGN_MESSAGE= (byte) 0x6E;
	private final static byte INS_SIGN_SHORT_MESSAGE= (byte) 0x72;
	private final static byte INS_SIGN_TRANSACTION= (byte) 0x6F;
	private final static byte INS_PARSE_TRANSACTION = (byte) 0x71;
    private final static byte INS_CRYPT_TRANSACTION_2FA = (byte) 0x76;
    private final static byte INS_SET_2FA_KEY = (byte) 0x79;    
    private final static byte INS_RESET_2FA_KEY = (byte) 0x78;
    private final static byte INS_SIGN_TRANSACTION_HASH= (byte) 0x7A;
    
	// secure channel
	private final static byte INS_INIT_SECURE_CHANNEL = (byte) 0x81;
	private final static byte INS_PROCESS_SECURE_CHANNEL = (byte) 0x82;
	
	/****************************************
	 *          Error codes                 *
	 ****************************************/
	
	/** Entered PIN is not correct */
	private final static short SW_PIN_FAILED = (short)0x63C0;// includes number of tries remaining
	///** DEPRECATED - Entered PIN is not correct */
	//private final static short SW_AUTH_FAILED = (short) 0x9C02;
	/** Required operation is not allowed in actual circumstances */
	private final static short SW_OPERATION_NOT_ALLOWED = (short) 0x9C03;
	/** Required setup is not not done */
	private final static short SW_SETUP_NOT_DONE = (short) 0x9C04;
	/** Required setup is already done */
	private final static short SW_SETUP_ALREADY_DONE = (short) 0x9C07;
	/** Required feature is not (yet) supported */
	final static short SW_UNSUPPORTED_FEATURE = (short) 0x9C05;
	/** Required operation was not authorized because of a lack of privileges */
	private final static short SW_UNAUTHORIZED = (short) 0x9C06;
	/** Algorithm specified is not correct */
	private final static short SW_INCORRECT_ALG = (short) 0x9C09;
	
	/** There have been memory problems on the card */
	private final static short SW_NO_MEMORY_LEFT = Bip32ObjectManager.SW_NO_MEMORY_LEFT;
	///** DEPRECATED - Required object is missing */
	//private final static short SW_OBJECT_NOT_FOUND= (short) 0x9C07;

	/** Incorrect P1 parameter */
	private final static short SW_INCORRECT_P1 = (short) 0x9C10;
	/** Incorrect P2 parameter */
	private final static short SW_INCORRECT_P2 = (short) 0x9C11;
	/** Invalid input parameter to command */
	private final static short SW_INVALID_PARAMETER = (short) 0x9C0F;
	
	/** Eckeys initialized */
	private final static short SW_ECKEYS_INITIALIZED_KEY = (short) 0x9C1A;
	
	/** Verify operation detected an invalid signature */
	private final static short SW_SIGNATURE_INVALID = (short) 0x9C0B;
	/** Operation has been blocked for security reason */
	private final static short SW_IDENTITY_BLOCKED = (short) 0x9C0C;
	/** For debugging purposes */
	private final static short SW_INTERNAL_ERROR = (short) 0x9CFF;
	/** Very low probability error */
	private final static short SW_BIP32_DERIVATION_ERROR = (short) 0x9C0E;
	/** Incorrect initialization of method */
	private final static short SW_INCORRECT_INITIALIZATION = (short) 0x9C13;
	/** Bip32 seed is not initialized*/
	private final static short SW_BIP32_UNINITIALIZED_SEED = (short) 0x9C14;
	/** Bip32 seed is already initialized (must be reset before change)*/
	private final static short SW_BIP32_INITIALIZED_SEED = (short) 0x9C17;
	//** DEPRECATED - Bip32 authentikey pubkey is not initialized*/
	//private final static short SW_BIP32_UNINITIALIZED_AUTHENTIKEY_PUBKEY= (short) 0x9C16;
	/** Incorrect transaction hash */
	private final static short SW_INCORRECT_TXHASH = (short) 0x9C15;
	
	/** 2FA already initialized*/
	private final static short SW_2FA_INITIALIZED_KEY = (short) 0x9C18;
	/** 2FA uninitialized*/
	private final static short SW_2FA_UNINITIALIZED_KEY = (short) 0x9C19;
		
	/** HMAC errors */
	static final short SW_HMAC_UNSUPPORTED_KEYSIZE = (short) 0x9c1E;
	static final short SW_HMAC_UNSUPPORTED_MSGSIZE = (short) 0x9c1F;
	
	/** Secure channel */
	private final static short SW_SECURE_CHANNEL_REQUIRED = (short) 0x9C20;
	private final static short SW_SECURE_CHANNEL_UNINITIALIZED = (short) 0x9C21;
	private final static short SW_SECURE_CHANNEL_WRONG_IV= (short) 0x9C22;
	private final static short SW_SECURE_CHANNEL_WRONG_MAC= (short) 0x9C23;
	
	/** For instructions that have been deprecated*/
	private final static short SW_INS_DEPRECATED = (short) 0x9C26;
	
	/** For debugging purposes 2 */
	private final static short SW_DEBUG_FLAG = (short) 0x9FFF;
	
	// KeyBlob Encoding in Key Blobs
	private final static byte BLOB_ENC_PLAIN = (byte) 0x00;

	// Cipher Operations admitted in ComputeCrypt()
	private final static byte OP_INIT = (byte) 0x01;
	private final static byte OP_PROCESS = (byte) 0x02;
	private final static byte OP_FINALIZE = (byte) 0x03;

	// JC API 2.2.2 does not define these constants:
	private final static byte ALG_ECDSA_SHA_256= (byte) 33;
	private final static byte ALG_EC_SVDP_DH_PLAIN= (byte) 3; //https://javacard.kenai.com/javadocs/connected/javacard/security/KeyAgreement.html#ALG_EC_SVDP_DH_PLAIN
	private final static byte ALG_EC_SVDP_DH_PLAIN_XY= (byte) 6; //https://docs.oracle.com/javacard/3.0.5/api/javacard/security/KeyAgreement.html#ALG_EC_SVDP_DH_PLAIN_XY
	private final static short LENGTH_EC_FP_256= (short) 256;
		
	/****************************************
	 * Instance variables declaration *
	 ****************************************/
	
	// Key objects (allocated on demand)
	private Key[] eckeys;
	private ECPrivateKey tmpkey;
	short eckeys_flag=0x0000; //flag bit set to 1 when corresponding key is initialised 
	
	// PIN and PUK objects, allocated on demand
	private OwnerPIN[] pins, ublk_pins;

	// Buffer for storing extended APDUs
	private byte[] recvBuffer;
	private byte[] tmpBuffer;

	/*
	 * Logged identities: this is used for faster access control, so we don't
	 * have to ping each PIN object
	 */
	private short logged_ids;

	/* For the setup function - should only be called once */
	private boolean setupDone = false;
	
	// shared cryptographic objects
	private RandomData randomData;
	private KeyAgreement keyAgreement;
	private Signature sigECDSA;
	private Cipher aes128;
    
	/*********************************************
	 *  BIP32 Hierarchical Deterministic Wallet  *
	 *********************************************/
	// Secure Memory & Object Manager with no access from outside (used internally for storing BIP32 objects)
	private Bip32ObjectManager bip32_om;
	
	// seed derivation
	private static final byte[] BITCOIN_SEED = {'B','i','t','c','o','i','n',' ','s','e','e','d'};
	private static final byte[] BITCOIN_SEED2 = {'B','i','t','c','o','i','n',' ','s','e','e','d','2'};
	private static final byte MAX_BIP32_DEPTH = 10; // max depth in extended key from master (m/i is depth 1)
	
	// BIP32_object= [ hash(address) (4b) | extended_key (32b) | chain_code (32b) | compression_byte(1b)]
	// recvBuffer=[ parent_chain_code (32b) | 0x00 | parent_key (32b) | hash(address) (32b) | current_extended_key(32b) | current_chain_code(32b) ]
	// hash(address)= [ index(4b) | unused (28-4b) | ANTICOLLISIONHASH(4b)]
	private static final short BIP32_KEY_SIZE= 32; // size of extended key and chain code is 256 bits
	private static final short BIP32_ANTICOLLISION_LENGTH=4; // max 12 bytes so that index+crc + two hashes fit in 32 bits
	private static final short BIP32_OBJECT_SIZE= (short)(2*BIP32_KEY_SIZE+BIP32_ANTICOLLISION_LENGTH+1);  
	
	// offset in working buffer
	private static final short BIP32_OFFSET_PARENT_CHAINCODE=0;
	private static final short BIP32_OFFSET_PARENT_SEPARATOR=BIP32_KEY_SIZE;
	private static final short BIP32_OFFSET_PARENT_KEY=BIP32_KEY_SIZE+1;
	private static final short BIP32_OFFSET_INDEX= (short)(2*BIP32_KEY_SIZE+1);
	private static final short BIP32_OFFSET_COLLISIONHASH= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE-BIP32_ANTICOLLISION_LENGTH);
	private static final short BIP32_OFFSET_CHILD_KEY= (short)(BIP32_OFFSET_INDEX+BIP32_KEY_SIZE); 
	private static final short BIP32_OFFSET_CHILD_CHAINCODE= (short)(BIP32_OFFSET_CHILD_KEY+BIP32_KEY_SIZE);
	private static final short BIP32_OFFSET_PUB= (short)(BIP32_OFFSET_CHILD_CHAINCODE+BIP32_KEY_SIZE);
	private static final short BIP32_OFFSET_PUBX= (short)(BIP32_OFFSET_PUB+1);
	private static final short BIP32_OFFSET_PUBY= (short)(BIP32_OFFSET_PUBX+BIP32_KEY_SIZE);
	private static final short BIP32_OFFSET_PATH= (short)(BIP32_OFFSET_PUBY+BIP32_KEY_SIZE);
	private static final short BIP32_OFFSET_END= (short)(BIP32_OFFSET_PATH+4*MAX_BIP32_DEPTH);
	private static final short BIP32_OFFSET_SIG= (short)(ISO7816.OFFSET_CDATA+4*MAX_BIP32_DEPTH);//temporary location 
	
	//   bip32 keys
	private boolean bip32_seeded= false;
	private byte bip32_master_compbyte; // compression byte for master key
	private AESKey bip32_masterkey; 
	private AESKey bip32_masterchaincode; 
	private AESKey bip32_encryptkey; // used to encrypt sensitive data in object
	private ECPrivateKey bip32_extendedkey; // object storing last extended key used
	private ECPrivateKey bip32_authentikey; // key used to authenticate data
	private ECPublicKey bip32_pubkey;
	private byte[] authentikey_pubkey;// store authentikey coordx pubkey TODO: create ECPublicKey instead?
	
	/*********************************************
	 *        Other data instances               *
	 *********************************************/
	
	// Message signing
	private static final byte[] BITCOIN_SIGNED_MESSAGE_HEADER = {0x18,'B','i','t','c','o','i','n',' ','S','i','g','n','e','d',' ','M','e','s','s','a','g','e',':','\n'}; //"Bitcoin Signed Message:\n";
	private MessageDigest sha256;  
	private boolean sign_flag= false;
	
	// transaction signing
	private byte[] transactionData;
	private static final byte OFFSET_TRANSACTION_HASH=0;
	private static final byte OFFSET_TRANSACTION_AMOUNT=OFFSET_TRANSACTION_HASH+32;
	private static final byte OFFSET_TRANSACTION_TOTAL=OFFSET_TRANSACTION_AMOUNT+8;
	private static final byte OFFSET_TRANSACTION_SIZE=OFFSET_TRANSACTION_TOTAL+8;
	
	// tx parsing
	private static final byte PARSE_STD=0x00;
	private static final byte PARSE_SEGWIT=0x01;
	
	//2FA data
	private boolean needs_2FA= false;
	private boolean done_once_2FA= false;
	private byte[] data2FA;
	private static final byte OFFSET_2FA_HMACKEY=0;
	private static final byte OFFSET_2FA_ID=OFFSET_2FA_HMACKEY+20;
	private static final byte OFFSET_2FA_LIMIT=OFFSET_2FA_ID+20;
	private static final byte OFFSET_2FA_SIZE=OFFSET_2FA_LIMIT+8;
    private static final short HMAC_CHALRESP_2FA=(short)0x8000;
	/**
	 * 2FA codes:
	 * - Tx approval: [ 32b TX hash | 32-bit 0x00-padding ]
	 * - ECkey import:[ 32b coordx  | 32-bit (0x10^key_nb)-padding ]
	 * - ECkey reset: [ 32b coordx  | 32-bit (0x20^key_nb)-padding ] 
	 * - Seed reset:  [ 32b authentikey-coordx  | 32-bit 0xFF-padding ] 
	 * - 2FA reset:   [ 20b 2FA_ID  | 44-bit 0xAA-padding ]  
	 * - Msg signing: [ 32b SHA26(btcHeader+msg) | 32-bit 0xBB-padding ]
	 * - Hash signing:[ 32b Hash | 32-bit 0xCC-padding ]
	 *  */ 
    
    // 2FA msg encryption
    private static final byte[] CST_2FA = {'i','d','_','2','F','A',     
    									   'k','e','y','_','2','F','A'};
    private Cipher aes128_cbc;
    private AESKey key_2FA;
    
	// secure channel
	private static final byte[] CST_SC = {'s','c','_','k','e','y', 's','c','_','m','a','c'};
	private boolean needs_secure_channel= true;
	private boolean initialized_secure_channel= false;
	private ECPrivateKey sc_ephemeralkey; 
	private AESKey sc_sessionkey;
	private Cipher sc_aes128_cbc;
	private byte[] sc_buffer;
	private static final byte OFFSET_SC_IV=0;
	private static final byte OFFSET_SC_IV_RANDOM=OFFSET_SC_IV;
	private static final byte OFFSET_SC_IV_COUNTER=12;
	private static final byte OFFSET_SC_MACKEY=16;
	private static final byte SIZE_SC_MACKEY=20;
	private static final byte SIZE_SC_IV= 16;
	private static final byte SIZE_SC_IV_RANDOM=12;
	private static final byte SIZE_SC_IV_COUNTER=SIZE_SC_IV-SIZE_SC_IV_RANDOM;
	private static final byte SIZE_SC_BUFFER=SIZE_SC_MACKEY+SIZE_SC_IV;
	
	// additional options
	private short option_flags;
	
	/****************************************
	 * Methods *
	 ****************************************/

	private CardEdge(byte[] bArray, short bOffset, byte bLength) {
		// FIXED: something should be done already here, not only with setup APDU
		
		/* If init pin code does not satisfy policies, internal error */
		if (!CheckPINPolicy(PIN_INIT_VALUE, (short) 0, (byte) PIN_INIT_VALUE.length))
		    ISOException.throwIt(SW_INTERNAL_ERROR);

	    ublk_pins = new OwnerPIN[MAX_NUM_PINS];
		pins = new OwnerPIN[MAX_NUM_PINS];

		// DONE: pass in starting PIN setting with instantiation
		/* Setting initial PIN n.0 value */
		pins[0] = new OwnerPIN((byte) 3, (byte) PIN_INIT_VALUE.length);
		pins[0].update(PIN_INIT_VALUE, (short) 0, (byte) PIN_INIT_VALUE.length);
		
		// Temporary working arrays
		try {
			tmpBuffer = JCSystem.makeTransientByteArray((short) TMP_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT);
		} catch (SystemException e) {
			tmpBuffer = new byte[TMP_BUFFER_SIZE];
		}
		// Initialize the extended APDU buffer
		try {
			// Try to allocate the extended APDU buffer on RAM memory
			recvBuffer = JCSystem.makeTransientByteArray((short) EXT_APDU_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT);
		} catch (SystemException e) {
			// Allocate the extended APDU buffer on EEPROM memory
			// This is the fallback method, but its usage is really not
			// recommended as after ~ 100000 writes it will kill the EEPROM cells...
			recvBuffer = new byte[EXT_APDU_BUFFER_SIZE];
		}
		
		// common cryptographic objects
		randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
		sigECDSA= Signature.getInstance(ALG_ECDSA_SHA_256, false); 
		HmacSha160.init(tmpBuffer);
		try {
			keyAgreement = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN_XY, false); 
		} catch (CryptoException e) {
			ISOException.throwIt(SW_UNSUPPORTED_FEATURE);// unsupported feature => use a more recent card!
		}
		
		//secure channel objects
		try {
			sc_buffer = JCSystem.makeTransientByteArray((short) SIZE_SC_BUFFER, JCSystem.CLEAR_ON_DESELECT);
		} catch (SystemException e) {
			sc_buffer = new byte[SIZE_SC_BUFFER];
		}
		//sc_IV= new byte[(short)16];//todo make transient and combine?
		//sc_mackey= new byte[(short)20];
		sc_sessionkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); // todo: make transient?
		sc_ephemeralkey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false);
		sc_aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); 
		
		// debug
		register();
	} // end of constructor

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		CardEdge wal = new CardEdge(bArray, bOffset, bLength);
	}

	public boolean select() {
		/*
		 * Application has been selected: Do session cleanup operation
		 */
		LogOutAll();
		
		//todo: clear secure channel values?
		initialized_secure_channel=false;
		
		return true;
	}

	public void deselect() {
		LogOutAll();
	}

	public void process(APDU apdu) {
		// APDU object carries a byte array (buffer) to
		// transfer incoming and outgoing APDU header
		// and data bytes between card and CAD

		// At this point, only the first header bytes
		// [CLA, INS, P1, P2, P3] are available in
		// the APDU buffer.
		// The interface javacard.framework.ISO7816
		// declares constants to denote the offset of
		// these bytes in the APDU buffer
		
		if (selectingApplet())
			ISOException.throwIt(ISO7816.SW_NO_ERROR);

		byte[] buffer = apdu.getBuffer();
		// check SELECT APDU command
		if ((buffer[ISO7816.OFFSET_CLA] == 0) && (buffer[ISO7816.OFFSET_INS] == (byte) 0xA4))
			return;
		// verify the rest of commands have the
		// correct CLA byte, which specifies the
		// command structure
		if (buffer[ISO7816.OFFSET_CLA] != CardEdge_CLA)
			ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

		byte ins = buffer[ISO7816.OFFSET_INS];
		
		// prepare APDU buffer
		if (ins != (byte) INS_GET_STATUS){
			short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
			if (bytesLeft != apdu.setIncomingAndReceive())
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		}
		
		// only 3 commands are allowed, the others must be wrapped in a secure channel command
		// the 3 commands are: get_status, initialize_secure_channel & process_secure_channel
		short sizeout=(short)0;
		if (ins == (byte) INS_GET_STATUS){
			sizeout= GetStatus(apdu, buffer);
			apdu.setOutgoingAndSend((short) 0, sizeout);
			return;
		}
		else if (ins == (byte) INS_INIT_SECURE_CHANNEL){
			sizeout= InitiateSecureChannel(apdu, buffer);
			apdu.setOutgoingAndSend((short) 0, sizeout);
			return;
		}
		else if (ins == (byte) INS_PROCESS_SECURE_CHANNEL){
			sizeout= ProcessSecureChannel(apdu, buffer);
			//todo: check if sizeout and buffer[ISO7816.OFFSET_LC] matches...
			//if sizeout>4, buffer[ISO7816.OFFSET_LC] should be equal to (sizeout-5)
			//todo: remove padding ? (it is actually not used)			
		}
		else if (needs_secure_channel){
			ISOException.throwIt(SW_SECURE_CHANNEL_REQUIRED);
		}
		
		// at this point, the encrypted content has been deciphered in the buffer
		ins = buffer[ISO7816.OFFSET_INS];
		// check setup status
		if (!setupDone && (ins != (byte) INS_SETUP))
			ISOException.throwIt(SW_SETUP_NOT_DONE);
		if (setupDone && (ins == (byte) INS_SETUP))
			ISOException.throwIt(SW_SETUP_ALREADY_DONE);
		
		switch (ins) {
		case INS_SETUP:
			sizeout= setup(apdu, buffer);
			break;
		case INS_IMPORT_KEY:
			sizeout= ImportKey(apdu, buffer);
			break;
		case INS_RESET_KEY:
			sizeout= ResetKey(apdu, buffer);
			break;
		case INS_GET_PUBLIC_FROM_PRIVATE:
			sizeout= getPublicKeyFromPrivate(apdu, buffer);
			break;
		case INS_VERIFY_PIN:
			sizeout= VerifyPIN(apdu, buffer);
			break;
		case INS_CREATE_PIN:
			sizeout= CreatePIN(apdu, buffer);
			break;
		case INS_CHANGE_PIN:
			sizeout= ChangePIN(apdu, buffer);
			break;
		case INS_UNBLOCK_PIN:
			sizeout= UnblockPIN(apdu, buffer);
			break;
		case INS_LOGOUT_ALL:
			sizeout= LogOutAll();
			break;
		case INS_LIST_PINS:
			sizeout= ListPINs(apdu, buffer);
			break;
		case INS_GET_STATUS:
			sizeout= GetStatus(apdu, buffer);
			break;
		case INS_BIP32_IMPORT_SEED:
			sizeout= importBIP32Seed(apdu, buffer);
			break;
		case INS_BIP32_RESET_SEED:
			sizeout= resetBIP32Seed(apdu, buffer);
			break;
		case INS_BIP32_GET_AUTHENTIKEY:
			sizeout= getBIP32AuthentiKey(apdu, buffer);
			break;
		case INS_BIP32_SET_AUTHENTIKEY_PUBKEY:
			sizeout= setBIP32AuthentikeyPubkey(apdu, buffer);
			break;
		case INS_BIP32_GET_EXTENDED_KEY:
			sizeout= getBIP32ExtendedKey(apdu, buffer);
			break;
		case INS_BIP32_SET_EXTENDED_PUBKEY:
			sizeout= setBIP32ExtendedPubkey(apdu, buffer);
			break;
		case INS_SIGN_MESSAGE:	
			sizeout= signMessage(apdu, buffer);
			break;
		case INS_SIGN_SHORT_MESSAGE:	
			//sizeout= signShortMessage(apdu, buffer);
			ISOException.throwIt(SW_INS_DEPRECATED);
			break;
		case INS_SIGN_TRANSACTION:
			sizeout= SignTransaction(apdu, buffer);
			break;
		case INS_SIGN_TRANSACTION_HASH:
			sizeout= SignTransactionHash(apdu, buffer);
			break;
		case INS_PARSE_TRANSACTION:
			sizeout= ParseTransaction(apdu, buffer);
			break;
		case INS_SET_2FA_KEY:
			sizeout= set2FAKey(apdu, buffer);
			break;
		case INS_RESET_2FA_KEY:	
			sizeout= reset2FAKey(apdu, buffer);
			break;
        case INS_CRYPT_TRANSACTION_2FA:
        	sizeout= CryptTransaction2FA(apdu, buffer);
            break;
		default:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}//end of switch
		
		// Prepare buffer for return
		if (sizeout==0){
			return;
		}
		else if ((ins == (byte) INS_GET_STATUS) || (ins == (byte) INS_INIT_SECURE_CHANNEL)) {
			apdu.setOutgoingAndSend((short) 0, sizeout);
		}
		else if (needs_secure_channel) { // encrypt response
			// buffer contains the data (sizeout)
			// for encryption, data is padded with PKCS#7
			short blocksize=(short)16;
			short padsize= (short) (blocksize - (sizeout%blocksize));
			
			Util.arrayCopy(buffer, (short)0, tmpBuffer, (short)0, sizeout);
			Util.arrayFillNonAtomic(tmpBuffer, sizeout, padsize, (byte)padsize);//padding
			Util.arrayCopy(sc_buffer, OFFSET_SC_IV, buffer, (short)0, SIZE_SC_IV);
			sc_aes128_cbc.init(sc_sessionkey, Cipher.MODE_ENCRYPT, sc_buffer, OFFSET_SC_IV, SIZE_SC_IV);
			short sizeoutCrypt=sc_aes128_cbc.doFinal(tmpBuffer, (short)0, (short)(sizeout+padsize), buffer, (short) (18));
	        Util.setShort(buffer, (short)16, sizeoutCrypt);
	        sizeout= (short)(18+sizeoutCrypt);
	        //send back
	        apdu.setOutgoingAndSend((short) 0, sizeout);
		}
		else {
			apdu.setOutgoingAndSend((short) 0, sizeout);
		}
		
	} // end of process method

	/** 
	 * Setup APDU - initialize the applet and reserve memory
	 * This is done only once during the lifetime of the applet
	 * 
	 * ins: INS_SETUP (0x2A) 
	 * p1: 0x00
	 * p2: 0x00
	 * data: [default_pin_length(1b) | default_pin | 
     *        pin_tries0(1b) | ublk_tries0(1b) | pin0_length(1b) | pin0 | ublk0_length(1b) | ublk0 | 
     *        pin_tries1(1b) | ublk_tries1(1b) | pin1_length(1b) | pin1 | ublk1_length(1b) | ublk1 | 
     *        secmemsize(2b) | RFU(2b) | RFU(3b) |
     *        option_flags(2b) | 
     *        (option): hmacsha1_key(20b) | amount_limit(8b)
     *        ]
	 * where: 
	 * 		default_pin: {0x4D, 0x75, 0x73, 0x63, 0x6C, 0x65, 0x30, 0x30};
	 * 		pin_tries: max number of PIN try allowed before the corresponding PIN is blocked
	 * 		ublk_tries:  max number of UBLK(unblock) try allowed before the PUK is blocked
	 * 		secmemsize: number of bytes reserved for internal memory (storage of Bip32 objects)
	 * 		memsize: number of bytes reserved for memory with external access
	 * 		ACL: creation rights for objects - Key - PIN
	 * 		option_flags: flags to define up to 16 additional options:
	 * 		bit15 set: second factor authentication using hmac-sha1 challenge-response (v0.2-0.1)
	 * 			hmacsha1_key: 20-byte hmac key used for transaction authorization
	 * 			amount_limit: max amount (in satoshis) allowed without confirmation (this includes change value)
	 *  
	 * return: none
	 */
	private short setup(APDU apdu, byte[] buffer) {
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		short base = (short) (ISO7816.OFFSET_CDATA);

		byte numBytes = buffer[base++];
		bytesLeft--;

		OwnerPIN pin = pins[0];

		if (!CheckPINPolicy(buffer, base, numBytes))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		
		byte triesRemaining	= pin.getTriesRemaining();
		if (triesRemaining == (byte) 0x00)
			ISOException.throwIt(SW_IDENTITY_BLOCKED);
		if (!pin.check(buffer, base, numBytes))
			ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1));
		
		base += numBytes;
		bytesLeft-=numBytes;

		byte pin_tries = buffer[base++];
		byte ublk_tries = buffer[base++];
		numBytes = buffer[base++];
		bytesLeft-=3;

		if (!CheckPINPolicy(buffer, base, numBytes))
			ISOException.throwIt(SW_INVALID_PARAMETER);

		pins[0] = new OwnerPIN(pin_tries, PIN_MAX_SIZE);//TODO: new pin or update pin?
		pins[0].update(buffer, base, numBytes);
		
		base += numBytes;
		bytesLeft-=numBytes;
		numBytes = buffer[base++];
		bytesLeft--;
		
		if (!CheckPINPolicy(buffer, base, numBytes))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		
		ublk_pins[0] = new OwnerPIN(ublk_tries, PIN_MAX_SIZE);
		ublk_pins[0].update(buffer, base, numBytes);
		
		base += numBytes;
		bytesLeft-=numBytes;
		
		pin_tries = buffer[base++];
		ublk_tries = buffer[base++];
		numBytes = buffer[base++];
		bytesLeft-=3;
		
		if (!CheckPINPolicy(buffer, base, numBytes))
			ISOException.throwIt(SW_INVALID_PARAMETER);

		pins[1] = new OwnerPIN(pin_tries, PIN_MAX_SIZE);
		pins[1].update(buffer, base, numBytes);

		base += numBytes;
		bytesLeft-=numBytes;
		numBytes = buffer[base++];
		bytesLeft--;
		
		if (!CheckPINPolicy(buffer, base, numBytes))
			ISOException.throwIt(SW_INVALID_PARAMETER);

		ublk_pins[1] = new OwnerPIN(ublk_tries, PIN_MAX_SIZE);
		ublk_pins[1].update(buffer, base, numBytes);
		base += numBytes;
		bytesLeft-=numBytes;
		
		short secmem_size= Util.getShort(buffer, base);
		base += (short) 2;
		short RFU = Util.getShort(buffer, base); //mem_size deprecated => RFU...
		base += (short) 2;
		bytesLeft-=4;
		
		RFU = buffer[base++]; //create_object_ACL deprecated => RFU
		RFU = buffer[base++]; //create_key_ACL deprecated => RFU
		RFU = buffer[base++]; //create_pin_ACL deprecated => RFU
		bytesLeft-=3;
		
		bip32_om= new Bip32ObjectManager(secmem_size,BIP32_OBJECT_SIZE, BIP32_ANTICOLLISION_LENGTH);
		
		eckeys = new Key[MAX_NUM_KEYS];
		logged_ids = 0x0000; // No identities logged in
		
		// shared cryptographic objects
		aes128= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false);
		
		// HD wallet
		HmacSha512.init(tmpBuffer);
			
		// bip32 material
		bip32_seeded= false;
		bip32_master_compbyte=0x04;
		bip32_masterkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
		bip32_masterchaincode= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
		bip32_encryptkey= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
		// object containing the current extended key
		bip32_extendedkey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false);
		Secp256k1.setCommonCurveParameters(bip32_extendedkey);
		// key used to authenticate sensitive data from applet
		bip32_authentikey= (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false);
		Secp256k1.setCommonCurveParameters(bip32_authentikey);
		// key used to recover public key from private
		bip32_pubkey= (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, LENGTH_EC_FP_256, false);
		Secp256k1.setCommonCurveParameters(bip32_pubkey);
		authentikey_pubkey= new byte[2*BIP32_KEY_SIZE+1];
		
		// message signing
		sha256= MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
		
		// Transaction signing
		Transaction.init();
		transactionData= new byte[OFFSET_TRANSACTION_SIZE];
		
		// parse options
		option_flags=0;
		if (bytesLeft>=2){
			option_flags = Util.getShort(buffer, base);
			base+=(short)2;
			bytesLeft-=(short)2;
			// 2FA: transaction confirmation based on hmacsha160
			if ((option_flags & HMAC_CHALRESP_2FA)==HMAC_CHALRESP_2FA){
				data2FA= new byte[OFFSET_2FA_SIZE];
				Util.arrayCopyNonAtomic(buffer, base, data2FA, OFFSET_2FA_HMACKEY, (short)20); 
				base+=(short)20;
				bytesLeft-=(short)20;
				Util.arrayCopyNonAtomic(buffer, base, data2FA, OFFSET_2FA_LIMIT, (short)8); 
				base+=(short)8;
				bytesLeft-=(short)8;
                // set 2FA variables
				aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
                key_2FA= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
                // hmac derivation for id_2FA & key_2FA
				HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, CST_2FA, (short)0, (short)6, data2FA, OFFSET_2FA_ID);
                HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, CST_2FA, (short)6, (short)7, recvBuffer, (short)0);
                key_2FA.setKey(recvBuffer,(short)0); // AES-128: 16-bytes key!!
                needs_2FA= true;
                done_once_2FA= true;// some initialization steps should be done only once in the applet lifetime 
			}
		}
		
		setupDone = true;
		return (short)0;//nothing to return
	}

	/********** UTILITY FUNCTIONS **********/

	/**
	 * Retrieves the Key object to be used w/ the specified key number, key type
	 * (KEY_XX) and size. If exists, check it has the proper key type If not,
	 * creates it.
	 * 
	 * @return Retrieved Key object or throws SW_UNATUTHORIZED,
	 *         SW_OPERATION_NOT_ALLOWED
	 */
	private Key getKey(byte key_nb, byte key_type, short key_size) {
		
		if (eckeys[key_nb] == null) {
			// We have to create the Key
			eckeys[key_nb] = KeyBuilder.buildKey(key_type, key_size, false);
		} else {
			// Key already exists: check size & type
			/*
			 * TODO: As an option, we could just discard and recreate if not of
			 * the correct type, but creates trash objects
			 */
			if ((eckeys[key_nb].getSize() != key_size) || (eckeys[key_nb].getType() != key_type))
				ISOException.throwIt(SW_OPERATION_NOT_ALLOWED);
		}
		return eckeys[key_nb];
	}

	/**
	 * Registers logout of an identity. This must be called anycase when a PIN
	 * verification or external authentication fail
	 */
	private void LogoutIdentity(byte id_nb) {
		logged_ids &= (short) ~(0x0001 << id_nb);
	}

	/** Checks if PIN policies are satisfied for a PIN code */
	private boolean CheckPINPolicy(byte[] pin_buffer, short pin_offset, byte pin_size) {
		if ((pin_size < PIN_MIN_SIZE) || (pin_size > PIN_MAX_SIZE))
			return false;
		return true;
	}

	/****************************************
	 * APDU handlers *
	 ****************************************/	
	
	/** 
	 * This function allows the import of a private ECkey into the card.
	 * The exact key blob contents depend on the key�s algorithm, type and actual
	 * import parameters. The key's number, algorithm type, and parameters are
	 * specified by arguments P1, P2 and DATA.
	 * If 2FA is enabled, a hmac code must be provided.
	 * 
	 * ins: 0x32
	 * p1: private key number (0x00-0x0F)
	 * p2: 0x00
	 * data: [key_encoding(1) | key_type(1) | key_size(2) | RFU(6) | key_blob | (option)HMAC-2FA(20b)] 
	 * return: none
	 */
	private short ImportKey(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS))
			ISOException.throwIt(SW_INCORRECT_P1);
		
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		
		/*** Start reading key blob header***/
		// blob header= [ key_encoding(1) | key_type(1) | key_size(2) | RFU(6)]
		// Check entire blob header
		if (bytesLeft < 4)
			ISOException.throwIt(SW_INVALID_PARAMETER);
		// Check Blob Encoding
		short dataOffset = apdu.getOffsetCdata();
		if (buffer[dataOffset] != BLOB_ENC_PLAIN)
			ISOException.throwIt(SW_UNSUPPORTED_FEATURE);
		dataOffset++; // Skip Blob Encoding
		bytesLeft--;
		// we only support elliptic curve private key
        byte key_type = buffer[dataOffset];
		if (key_type!= KeyBuilder.TYPE_EC_FP_PRIVATE)
			ISOException.throwIt(SW_INCORRECT_ALG);
		dataOffset++; // Skip Key Type
		bytesLeft--;
		short key_size = Util.getShort(buffer, dataOffset);
		if (key_size != LENGTH_EC_FP_256)
			ISOException.throwIt(SW_INVALID_PARAMETER );
		dataOffset += (short) 2; // Skip Key Size
		bytesLeft -= (short) 2;
		dataOffset += (short) 6; // Skip ACL (deprecated)
		bytesLeft -= (short) 6;
		// key_blob=[blob_size(2) | privkey_blob(32)]
		if (bytesLeft < 2)
                ISOException.throwIt(SW_INVALID_PARAMETER);
        short blob_size = Util.getShort(buffer, dataOffset);
        if (blob_size != 32) // only bitcoin
        	ISOException.throwIt(blob_size);
        dataOffset += (short) 2; 
        bytesLeft -= (short) 2;
        if (bytesLeft < (short) (blob_size))
            ISOException.throwIt(SW_INVALID_PARAMETER);
        
        // check type and size
		ECPrivateKey ec_prv_key = (ECPrivateKey) getKey(key_nb, key_type, key_size);
		if ((ec_prv_key != null) && ec_prv_key.isInitialized())
			ISOException.throwIt(SW_INCORRECT_P1);
		
        // curves parameters are take by default as SECP256k1
        // Satochip default is secp256k1 (over Fp)
        Secp256k1.setCommonCurveParameters(ec_prv_key);
        
        // check 2FA if required
		if(needs_2FA){
			if (bytesLeft<(short)(blob_size+20))
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
			// we may have to create the tmpkey
			if (tmpkey == null)
				tmpkey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, LENGTH_EC_FP_256, false);
			// set from secret value
			tmpkey.setS(buffer, dataOffset, blob_size);
			// compute the corresponding partial public key...
			keyAgreement.init(tmpkey);
			keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form
			Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32);
			// hmac of 64-bytes msg: (pubkey-x | 32bytes (0x10^key_nb)-padding)
			Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)(0x10^key_nb));
			HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
			if (Util.arrayCompare(buffer, (short)(dataOffset+blob_size), recvBuffer, (short)64, (short)20)!=0)
				ISOException.throwIt(SW_SIGNATURE_INVALID);
		}
        
        // set key from secret value & set flag
        ec_prv_key.setS(buffer, dataOffset, blob_size);
        eckeys_flag |= (short) (0x0001 << key_nb);// set corresponding bit flag;
        return (short)0;
	}
	
	/** 
	 * This function allows to reset a private ECkey stored in the card.
	 * If 2FA is enabled, a hmac code must be provided to reset the key.
	 * 
	 * ins: 0x33
	 * p1: private key number (0x00-0x0F)
	 * p2: 0x00
	 * data: [ (option)HMAC-2FA(20b)] 
	 * return: none
	 */
	private short ResetKey(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS))
			ISOException.throwIt(SW_INCORRECT_P1);
		
		Key key = eckeys[key_nb];
		// check type and size
		if ((key == null) || !key.isInitialized())
			ISOException.throwIt(SW_INCORRECT_P1);
		
		// check 2FA if required
		if (needs_2FA){
			short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
			
			if (bytesLeft < (short)20)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
			
			// compute the corresponding partial public key...
			keyAgreement.init((ECPrivateKey)key);
			keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, tmpBuffer, (short)0); //pubkey in uncompressed form
			Util.arrayCopy(tmpBuffer, (short)1, recvBuffer, (short)0, (short)32);
			// hmac of 64-bytes msg: (pubkey-x | 32bytes (0x20^key_nb)-padding)
			Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte) (0x20^key_nb));
			HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
			if (Util.arrayCompare(buffer, ISO7816.OFFSET_CDATA, recvBuffer, (short)64, (short)20)!=0)
				ISOException.throwIt(SW_SIGNATURE_INVALID);			
		}
		
		// clear key & reset flag
		key.clearKey();
		eckeys_flag &= (short) ~(0x0001 << key_nb);// reset corresponding bit flag;
		
		return (short)0;
	}
	
	/** 
	 * This function returns the public key associated with a particular private key stored 
	 * in the applet. The exact key blob contents depend on the key�s algorithm and type. 
	 * 
	 * ins: 0x35
	 * p1: private key number (0x00-0x0F)
	 * p2: 0x00
	 * data: none 
	 * return(SECP256K1): [coordx_size(2b) | pubkey_coordx | sig_size(2b) | sig]
	 */
	private short getPublicKeyFromPrivate(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		
		byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS))
			ISOException.throwIt(SW_INCORRECT_P1);
		
		Key key = eckeys[key_nb];
		// check type and size
		if ((key == null) || !key.isInitialized())
			ISOException.throwIt(SW_INCORRECT_P1);
		if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE)
			ISOException.throwIt(SW_INCORRECT_ALG);		
		if (key.getSize()!= LENGTH_EC_FP_256)
			ISOException.throwIt(SW_INCORRECT_ALG);
		// check the curve param
		if(!Secp256k1.checkCurveParameters((ECPrivateKey)key, recvBuffer, (short)0))
			ISOException.throwIt(SW_INCORRECT_ALG);
				
		// compute the corresponding partial public key...
        keyAgreement.init((ECPrivateKey)key);
        short coordx_size=(short)32;
    	keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form
	    Util.setShort(buffer, (short)0, coordx_size);
        
        // sign fixed message
        sigECDSA.init(key, Signature.MODE_SIGN);
        short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4));
        Util.setShort(buffer, (short)(coordx_size+2), sign_size);
        
        // return x-coordinate of public key+signature
        // the client can recover full public-key from the signature or
        // by guessing the compression value () and verifying the signature... 
        return (short)(2+coordx_size+2+sign_size);
	}		

	/** 
	 * This function creates a PIN with parameters specified by the P1, P2 and DATA
	 * values. P2 specifies the maximum number of consecutive unsuccessful
	 * verifications before the PIN blocks. PIN can be created only if one of the logged identities
	 * allows it. 
	 * 
	 * ins: 0x40
	 * p1: PIN number (0x00-0x07)
	 * p2: max attempt number
	 * data: [PIN_size(1b) | PIN | UBLK_size(1b) | UBLK] 
	 * return: none
	 */
	private short CreatePIN(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		byte pin_nb = buffer[ISO7816.OFFSET_P1];
		byte num_tries = buffer[ISO7816.OFFSET_P2];
		
		if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS) || (pins[pin_nb] != null))
			ISOException.throwIt(SW_INCORRECT_P1);
		/* Allow pin lengths > 127 (useful at all ?) */
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		// At least 1 character for PIN and 1 for unblock code (+ lengths)
		if (bytesLeft < 4)
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte pin_size = buffer[ISO7816.OFFSET_CDATA];
		if (bytesLeft < (short) (1 + pin_size + 1))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte ucode_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)];
		if (bytesLeft != (short) (1 + pin_size + 1 + ucode_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), ucode_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		pins[pin_nb] = new OwnerPIN(num_tries, PIN_MAX_SIZE);
		pins[pin_nb].update(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size);
		ublk_pins[pin_nb] = new OwnerPIN((byte) 3, PIN_MAX_SIZE);
		// Recycle variable pin_size
		pin_size = (byte) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1);
		ublk_pins[pin_nb].update(buffer, pin_size, ucode_size);
		
		return (short)0;
	}

	/** 
	 * This function verifies a PIN number sent by the DATA portion. The length of
	 * this PIN is specified by the value contained in P3.
	 * Multiple consecutive unsuccessful PIN verifications will block the PIN. If a PIN
	 * blocks, then an UnblockPIN command can be issued.
	 * 
	 * ins: 0x42
	 * p1: PIN number (0x00-0x07)
	 * p2: 0x00
	 * data: [PIN] 
	 * return: none (throws an exception in case of wrong PIN)
	 */
	private short VerifyPIN(APDU apdu, byte[] buffer) {
		byte pin_nb = buffer[ISO7816.OFFSET_P1];
		if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS))
			ISOException.throwIt(SW_INCORRECT_P1);
		OwnerPIN pin = pins[pin_nb];
		if (pin == null)
			ISOException.throwIt(SW_INCORRECT_P1);
		if (buffer[ISO7816.OFFSET_P2] != 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		/*
		 * Here I suppose the PIN code is small enough to enter in the buffer
		 * TODO: Verify the assumption and eventually adjust code to support
		 * reading PIN in multiple read()s
		 */
		if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte triesRemaining	= pin.getTriesRemaining();
		if (triesRemaining == (byte) 0x00)
			ISOException.throwIt(SW_IDENTITY_BLOCKED);
		if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) bytesLeft)) {
			LogoutIdentity(pin_nb);
			ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1));
		}
		
		// Actually register that PIN has been successfully verified.
		logged_ids |= (short) (0x0001 << pin_nb);
		
		return (short)0;
	}

	
	/** 
	 * This function changes a PIN code. The DATA portion contains both the old and
	 * the new PIN codes. 
	 * 
	 * ins: 0x44
	 * p1: PIN number (0x00-0x07)
	 * p2: 0x00
	 * data: [PIN_size(1b) | old_PIN | PIN_size(1b) | new_PIN ] 
	 * return: none (throws an exception in case of wrong PIN)
	 */
	private short ChangePIN(APDU apdu, byte[] buffer) {
		/*
		 * Here I suppose the PIN code is small enough that 2 of them enter in
		 * the buffer TODO: Verify the assumption and eventually adjust code to
		 * support reading PINs in multiple read()s
		 */
		byte pin_nb = buffer[ISO7816.OFFSET_P1];
		if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS))
			ISOException.throwIt(SW_INCORRECT_P1);
		OwnerPIN pin = pins[pin_nb];
		if (pin == null)
			ISOException.throwIt(SW_INCORRECT_P1);
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		// At least 1 character for each PIN code
		if (bytesLeft < 4)
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte pin_size = buffer[ISO7816.OFFSET_CDATA];
		if (bytesLeft < (short) (1 + pin_size + 1))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte new_pin_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)];
		if (bytesLeft < (short) (1 + pin_size + 1 + new_pin_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		
		byte triesRemaining	= pin.getTriesRemaining();
		if (triesRemaining == (byte) 0x00)
			ISOException.throwIt(SW_IDENTITY_BLOCKED);
		if (!pin.check(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) {
			LogoutIdentity(pin_nb);
			ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1));
		}
		
		pin.update(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size);
		// JC specifies this resets the validated flag. So we do.
		logged_ids &= (short) ((short) 0xFFFF ^ (0x01 << pin_nb));
		
		return (short)0;
	}

	/**
	 * This function unblocks a PIN number using the unblock code specified in the
	 * DATA portion. The P3 byte specifies the unblock code length. 
	 * 
	 * ins: 0x46
	 * p1: PIN number (0x00-0x07)
	 * p2: 0x00
	 * data: [PUK] 
	 * return: none (throws an exception in case of wrong PUK)
	 */
	private short UnblockPIN(APDU apdu, byte[] buffer) {
		byte pin_nb = buffer[ISO7816.OFFSET_P1];
		if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS))
			ISOException.throwIt(SW_INCORRECT_P1);
		OwnerPIN pin = pins[pin_nb];
		OwnerPIN ublk_pin = ublk_pins[pin_nb];
		if (pin == null)
			ISOException.throwIt(SW_INCORRECT_P1);
		if (ublk_pin == null)
			ISOException.throwIt(SW_INTERNAL_ERROR);
		// If the PIN is not blocked, the call is inconsistent
		if (pin.getTriesRemaining() != 0)
			ISOException.throwIt(SW_OPERATION_NOT_ALLOWED);
		if (buffer[ISO7816.OFFSET_P2] != 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		/*
		 * Here I suppose the PIN code is small enough to fit into the buffer
		 * TODO: Verify the assumption and eventually adjust code to support
		 * reading PIN in multiple read()s
		 */
		if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		byte triesRemaining	= ublk_pin.getTriesRemaining();
		if (triesRemaining == (byte) 0x00)
			ISOException.throwIt(SW_IDENTITY_BLOCKED);
		if (!ublk_pin.check(buffer, ISO7816.OFFSET_CDATA, (byte) bytesLeft))
			ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1));
		
		pin.resetAndUnblock();
		
		return (short)0;
	}

	private short LogOutAll() {
		logged_ids = (short) 0x0000; // Nobody is logged in
		byte i;
		for (i = (byte) 0; i < MAX_NUM_PINS; i++)
			if (pins[i] != null)
				pins[i].reset();
		
		return (short)0;
	}
	
	/**
	 * This function returns a 2 byte bit mask of the available PINs that are currently in
	 * use. Each set bit corresponds to an active PIN.
	 * 
	 *  ins: 0x48
	 *  p1: 0x00
	 *  p2: 0x00
	 *  data: none
	 *  return: [RFU(1b) | PIN_mask(1b)]
	 */
	private short ListPINs(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		// Checking P1 & P2
		if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P1);
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		byte expectedBytes = (byte) (buffer[ISO7816.OFFSET_LC]);
		if (expectedBytes != (short) 2)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		// Build the PIN bit mask
		short mask = (short) 0x00;
		short b;
		for (b = (short) 0; b < MAX_NUM_PINS; b++)
			if (pins[b] != null)
				mask |= (short) (((short) 0x01) << b);
		// Fill the buffer
		Util.setShort(buffer, (short) 0, mask);
		// Send response
		return (short)2;
	}
	
	/**
	 * This function retrieves general information about the Applet running on the smart
	 * card, and useful information about the status of current session such as:
	 * 		- applet version (4b)
	 *  
	 *  ins: 0x3C
	 *  p1: 0x00 
	 *  p2: 0x00 
	 *  data: none
	 *  return: [versions(4b) | PIN0-PUK0-PIN1-PUK1 tries (4b) | needs2FA (1b) | is_seeded(1b) | setupDone(1b) | needs_secure_channel(1b)]
	 */
	private short GetStatus(APDU apdu, byte[] buffer) {
		// check that PIN[0] has been entered previously
		//if (!pins[0].isValidated())
		//	ISOException.throwIt(SW_UNAUTHORIZED);
		
		if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P1);
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		
		short pos = (short) 0;
		buffer[pos++] = (byte) PROTOCOL_MAJOR_VERSION; // Major Card Edge Protocol version n.
		buffer[pos++] = (byte) PROTOCOL_MINOR_VERSION; // Minor Card Edge Protocol version n.
		buffer[pos++] = (byte) APPLET_MAJOR_VERSION; // Major Applet version n.
		buffer[pos++] = (byte) APPLET_MINOR_VERSION; // Minor Applet version n.
		// PIN/PUK remaining tries available
		if (setupDone){
			buffer[pos++] = pins[0].getTriesRemaining();
			buffer[pos++] = ublk_pins[0].getTriesRemaining();
			buffer[pos++] = pins[1].getTriesRemaining();
			buffer[pos++] = ublk_pins[1].getTriesRemaining();
		} else {
			buffer[pos++] = (byte) 0;
			buffer[pos++] = (byte) 0;
			buffer[pos++] = (byte) 0;
			buffer[pos++] = (byte) 0;
		}
		if (needs_2FA)
			buffer[pos++] = (byte)0x01;
		else
			buffer[pos++] = (byte)0x00;
		if (bip32_seeded)
			buffer[pos++] = (byte)0x01;
		else
			buffer[pos++] = (byte)0x00;
		if (setupDone)
			buffer[pos++] = (byte)0x01;
		else
			buffer[pos++] = (byte)0x00;
		if (needs_secure_channel)
			buffer[pos++] = (byte)0x01;
		else
			buffer[pos++] = (byte)0x00;
		
		return pos;
	}
	
	/**
	 * This function imports a Bip32 seed to the applet and derives the master key and chain code.
	 * It also derives a second ECC that uniquely authenticates the HDwallet: the authentikey.
	 * Lastly, it derives a 32-bit AES key that is used to encrypt/decrypt Bip32 object stored in secure memory 
	 * If the seed already exists, it is reset if the logged identities allow it.
	 * 
	 * The function returns the x-coordinate of the authentikey, self-signed.
	 * The authentikey full public key can be recovered from the signature.
	 *  
	 *  ins: 0x6C
	 *  p1: seed_size(1b) 
	 *  p2: 0x00 
	 *  data: [seed_data (seed_size)]
	 *  return: [coordx_size(2b) | coordx | sig_size(2b) | sig]
	 */
	private short importBIP32Seed(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
			ISOException.throwIt(SW_INCORRECT_P2);
		// if already seeded, must call resetBIP32Seed first!
		if (bip32_seeded)
			ISOException.throwIt(SW_BIP32_INITIALIZED_SEED);
		
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		
		// get seed bytesize (max 64 bytes)
		byte bip32_seedsize = buffer[ISO7816.OFFSET_P1]; 
		if (bip32_seedsize <0 || bip32_seedsize>64)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		short offset= (short)ISO7816.OFFSET_CDATA;
		
		// derive master key!
		HmacSha512.computeHmacSha512(BITCOIN_SEED, (short)0, (short)BITCOIN_SEED.length, buffer, offset, (short)bip32_seedsize, recvBuffer, (short)0);
		bip32_masterkey.setKey(recvBuffer, (short)0); // data must be exactly 32 bytes long
		bip32_masterchaincode.setKey(recvBuffer, (short)32); // data must be exactly 32 bytes long
		
		// derive 2 more keys from seed:
		// - AES encryption key for secure storage of extended keys in object
		// - ECC key for authentication of sensitive data returned by the applet (hash, pubkeys)
		HmacSha512.computeHmacSha512(BITCOIN_SEED2, (short)0, (short)BITCOIN_SEED2.length, buffer, offset, (short)bip32_seedsize, recvBuffer, (short)64);
		bip32_authentikey.setS(recvBuffer, (short)64, BIP32_KEY_SIZE);
		bip32_encryptkey.setKey(recvBuffer, (short)96); // AES-128: 16-bytes key!!
		
		// bip32 is now seeded
		bip32_seeded= true;
		
		// clear recvBuffer
		Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)128, (byte)0);
		
		// compute the partial authentikey public key...
        keyAgreement.init(bip32_authentikey);
        short coordx_size= (short)32;
    	keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, authentikey_pubkey, (short)0); //pubkey in uncompressed form
	    Util.setShort(buffer, (short)0, coordx_size);
        Util.arrayCopyNonAtomic(authentikey_pubkey, (short)1, buffer, (short)2, coordx_size);
        // self signed public key
        sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN);
        short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4));
        Util.setShort(buffer, (short)(2+coordx_size), sign_size);
        
        // return x-coordinate of public key+signature
        // the client can recover full public-key from the signature or
        // by guessing the compression value () and verifying the signature... 
        // buffer= [coordx_size(2) | coordx | sigsize(2) | sig]
        return (short)(2+coordx_size+2+sign_size);
	}
	
	/**
	 * This function resets the Bip32 seed and all derived keys: the master key, chain code, authentikey 
	 * and the 32-bit AES key that is used to encrypt/decrypt Bip32 object stored in secure memory.
	 * If 2FA is enabled, then a hmac code must be provided, based on the 4-byte counter-2FA.
	 *  
	 *  ins: 0x77
	 *  p1: PIN_size 
	 *  p2: 0x00
	 *  data: [PIN | optional-hmac(20b)]
	 *  return: (none)
	 */
	private short resetBIP32Seed(APDU apdu, byte[] buffer){
		
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		
		// check provided PIN
		byte pin_size= buffer[ISO7816.OFFSET_P1];
		OwnerPIN pin = pins[(byte)0x00];
		if (bytesLeft < pin_size)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, pin_size))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		
		byte triesRemaining	= pin.getTriesRemaining();
		if (triesRemaining == (byte) 0x00)
			ISOException.throwIt(SW_IDENTITY_BLOCKED);
		if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) pin_size)) {
			LogoutIdentity((byte)0x00);
			ISOException.throwIt((short)(SW_PIN_FAILED + triesRemaining - 1));
		}
		
		// check if seeded
		if (!bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
		
		// check 2FA if required
		if (needs_2FA){
			short offset= Util.makeShort((byte)0, ISO7816.OFFSET_CDATA);
			offset+=pin_size;
			bytesLeft-= pin_size;
			if (bytesLeft < (short)20)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
			
			// compute hmac(counter_2FA) and compare with value provided 
			// hmac of 64-bytes msg: ( authentikey-coordx(32b) | 32bytes 0xFF-padding)
			Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)64, (byte)0xFF);
			Util.arrayCopyNonAtomic(authentikey_pubkey, (short)0x01, recvBuffer, (short)0, BIP32_KEY_SIZE);
			HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
			if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0)
				ISOException.throwIt(SW_SIGNATURE_INVALID);
		}		
		// reset memory cache, bip32 flag and all data!
		bip32_om.reset();
		bip32_seeded= false;
		bip32_masterkey.clearKey(); 
		bip32_masterchaincode.clearKey();
		bip32_encryptkey.clearKey();
		bip32_authentikey.clearKey();
		Secp256k1.setCommonCurveParameters(bip32_authentikey);// keep public params!
		Util.arrayFillNonAtomic(authentikey_pubkey, (short)0, (short)(2*BIP32_KEY_SIZE+1), (byte)0x00);
		LogOutAll();
		return (short)0;
	}
	
	/**
	 * This function returns the authentikey public key (uniquely derived from the Bip32 seed).
	 * The function returns the x-coordinate of the authentikey, self-signed.
	 * The authentikey full public key can be recovered from the signature.
	 * 
	 *  ins: 0x73
	 *  p1: 0x00 
	 *  p2: 0x00 
	 *  data: none
	 *  return: [coordx_size(2b) | coordx | sig_size(2b) | sig]
	 */
	private short getBIP32AuthentiKey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		// check whether the seed is initialized
		if (!bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
		
		// compute the partial authentikey public key...
        keyAgreement.init(bip32_authentikey);        
        short coordx_size= (short)32;
    	keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form
		Util.setShort(buffer, (short)0, coordx_size);
        // self signed public key
        sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN);
        short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4));
        Util.setShort(buffer, (short)(coordx_size+2), sign_size);
        
        // return x-coordinate of public key+signature
        // the client can recover full public-key from the signature or
        // by guessing the compression value () and verifying the signature... 
        // buffer= [coordx_size(2) | coordx | sigsize(2) | sig]
        return (short)(coordx_size+sign_size+4);
	}	
	
	/**
	 * DEPRECATED - Not necessary anymore when recovering the pubkey with ALG_EC_SVDP_DH_PLAIN_XY
	 * A minimalist API is maintained for backward compatibility.
	 * 
	 * This function allows to compute the authentikey pubkey externally and 
	 * store it in the secure memory cache for future use. 
	 * This allows to speed up computation during derivation of non-hardened child.
	 * 
	 * ins: 0x75
	 * p1: 
	 * p2:
	 * data: [coordx_size(2b) | coordx | sig_size(2b) | sig][coordy_size(2b) | coordy]
     *
	 * returns: none
	 */
	private short setBIP32AuthentikeyPubkey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
        short pos=0;
		Util.setShort(buffer, pos, bip32_om.nb_elem_free); // number of slot available 
		pos += (short) 2;
		Util.setShort(buffer, pos, bip32_om.nb_elem_used); // number of slot used 
		pos += (short) 2;
		return pos;
	}// end of setBIP32AuthentikeyPubkey
	
	/**
	 * The function computes the Bip32 extended key derived from the master key and returns the 
	 * x-coordinate of the public key signed by the authentikey.
	 * Extended key is stored in the chip in a temporary EC key, along with corresponding ACL
	 * Extended key and chaincode is also cached as a Bip32 object in secure memory
	 * 
	 * ins: 0x6D
	 * p1: depth of the extended key (master is depth 0, m/i is depht 1). Max depth is 10
	 * p2: 0x00 (default) or 0xFF (erase all Bip32 objects from secure memory)
	 * p2: option flags:
     *		0x80: reset the bip32 cache memory
	 *	 	0x40: optimize non-hardened child derivation
	 *	 	0x20: TODO flag whether to store (save) key as object (currently by default)?
	 * data: index path from master to extended key (m/i/j/k/...). 4 bytes per index
	 * 
	 * returns: [chaincode(32b) | coordx_size(2b) | coordx | sig_size(2b) | sig | sig_size(2b) | sig2]
	 * 
	 * */
	private short getBIP32ExtendedKey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		// check whether the seed is seed is initialized
		if (!bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
		
		// input 
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		
		byte bip32_depth = buffer[ISO7816.OFFSET_P1];
		if ((bip32_depth < 0) || (bip32_depth > MAX_BIP32_DEPTH) )
        	ISOException.throwIt(SW_INCORRECT_P1);
		if (bytesLeft < (short)(4*bip32_depth))
			ISOException.throwIt(SW_INVALID_PARAMETER);
		
		// P2 option flags
		byte opts = buffer[ISO7816.OFFSET_P2]; 
		if ((opts & 0x80)==0x80)
			bip32_om.reset();
		
		// master key data (usefull as parent's data for key derivation)
		// The method uses a temporary buffer recvBuffer to store the parent and extended key object data:
		// recvBuffer=[ parent_chain_code (32b) | 0x00 | parent_key (32b) | hash(address)(32b) | current_extended_key(32b) | current_chain_code(32b) | parent_pubkey(65b) | bip32_path(40b)]
		// hash(address)= [ index(4b) | unused (16b)| crc (4b) | ANTICOLLISIONHASHTMP(4b)| ANTICOLLISIONHASH(4b)]
	    // parent_pubkey(65b)= [compression_byte(1b) | coord_x (32b) | coord_y(32b)]
		bip32_masterchaincode.getKey(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE);
		bip32_masterkey.getKey(recvBuffer,BIP32_OFFSET_PARENT_KEY); 		
		recvBuffer[BIP32_OFFSET_PARENT_SEPARATOR]=0x00; // separator, also facilitate HMAC derivation
		Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, recvBuffer, BIP32_OFFSET_PATH, (short)(4*bip32_depth));
		short parent_base=Bip32ObjectManager.NULL_OFFSET; 
		
		// iterate on indexes provided 
		for (byte i=1; i<=bip32_depth; i++){
						
			//compute SHA of the extended key address up to depth i (only the last bytes are actually used)
			sha256.reset(); 
			sha256.doFinal(recvBuffer, BIP32_OFFSET_PATH, (short)(i*4), recvBuffer, BIP32_OFFSET_INDEX);
			short base=bip32_om.getBaseAddress(recvBuffer,BIP32_OFFSET_COLLISIONHASH);
			// retrieve object at this address if it exists
			if (base!=Bip32ObjectManager.NULL_OFFSET){
				bip32_om.getBytes(recvBuffer, BIP32_OFFSET_COLLISIONHASH, base, (short)0, BIP32_OBJECT_SIZE);
			}
			// otherwise, create object if no object was found
			if (base==Bip32ObjectManager.NULL_OFFSET){
				
				// normal or hardened child?
				byte msb= recvBuffer[(short)(BIP32_OFFSET_PATH+4*(i-1))];
				if ((msb & 0x80)!=0x80){ // normal child
					// we must compute parent's compressed pubkey from privkey
					// check if parent's compression byte is available
					byte compbyte=0x04;//default
					if (parent_base==Bip32ObjectManager.NULL_OFFSET)
						compbyte=bip32_master_compbyte;
					else
						compbyte=bip32_om.getByte(parent_base, (short)(BIP32_OBJECT_SIZE-1));
					
					// compute coord x from privkey 
					bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE);
					keyAgreement.init(bip32_extendedkey);
			        
			        // keyAgreement.generateSecret() recovers X and Y coordinates
					keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, recvBuffer, BIP32_OFFSET_PUB); //pubkey in uncompressed form
					boolean parity= ((recvBuffer[(short)(BIP32_OFFSET_PUBY+31)]&0x01)==0);
					compbyte= (parity)?(byte)0x02:(byte)0x03; 
					// save compbyte in parent's object for future use
					if (parent_base==Bip32ObjectManager.NULL_OFFSET)
						bip32_master_compbyte= compbyte;
					else
						bip32_om.setByte(parent_base, (short)(BIP32_OBJECT_SIZE-1), compbyte);
				
			        // compute HMAC of compressed pubkey + index
					recvBuffer[BIP32_OFFSET_PUB]= compbyte;
			        Util.arrayCopyNonAtomic(recvBuffer, (short)(BIP32_OFFSET_PATH+4*(i-1)), recvBuffer, BIP32_OFFSET_PUBY, (short)4);
					HmacSha512.computeHmacSha512(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE, recvBuffer, BIP32_OFFSET_PUB, (short)(1+BIP32_KEY_SIZE+4), recvBuffer, BIP32_OFFSET_CHILD_KEY);
				}
				else { // hardened child
					recvBuffer[BIP32_KEY_SIZE]= 0x00;
					Util.arrayCopyNonAtomic(recvBuffer, (short)(BIP32_OFFSET_PATH+4*(i-1)), recvBuffer, BIP32_OFFSET_INDEX, (short)4);
					HmacSha512.computeHmacSha512(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE, recvBuffer, BIP32_OFFSET_PARENT_SEPARATOR, (short)(1+BIP32_KEY_SIZE+4), recvBuffer, BIP32_OFFSET_CHILD_KEY);
				}
				
				// addition with parent_key...
				// First check that parse256(IL) < SECP256K1_R
				if(!Biginteger.lessThan(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE)){
					ISOException.throwIt(SW_BIP32_DERIVATION_ERROR);
				}
				// add parent_key (mod SECP256K1_R)
				if(Biginteger.add_carry(recvBuffer, BIP32_OFFSET_CHILD_KEY, recvBuffer, (short) (BIP32_KEY_SIZE+1), BIP32_KEY_SIZE)){
					// in case of final carry, we must substract SECP256K1_R
					// we have IL<SECP256K1_R and parent_key<SECP256K1_R, so IL+parent_key<2*SECP256K1_R
					Biginteger.subtract(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE);	
				}else{
				    // in the unlikely case where SECP256K1_R<=IL+parent_key<2^256
					if(!Biginteger.lessThan(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE)){
						Biginteger.subtract(recvBuffer, BIP32_OFFSET_CHILD_KEY, Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_R, BIP32_KEY_SIZE);
					}
					// check that value is not 0
					if(Biginteger.equalZero(recvBuffer, BIP32_OFFSET_CHILD_KEY, BIP32_KEY_SIZE)){
						ISOException.throwIt(SW_BIP32_DERIVATION_ERROR);
					}
				}
				
				// encrypt privkey & chaincode
				aes128.init(bip32_encryptkey, Cipher.MODE_ENCRYPT);
				aes128.doFinal(recvBuffer, BIP32_OFFSET_CHILD_KEY, (short)(2*BIP32_KEY_SIZE), recvBuffer, BIP32_OFFSET_CHILD_KEY);
				
				// Update object data
				recvBuffer[BIP32_OFFSET_PUB]=0x04;
				// create object 
				// todo: should we create object for tx keys in last index (since they are usually used only once)?
				base= bip32_om.createObject(recvBuffer,BIP32_OFFSET_COLLISIONHASH);
				
			}//end if (object creation)
			
			// at this point, recvBuffer contains a copy of the object related to extended key at depth i
			// decrypt privkey & chaincode as they are encrypted at this point
			aes128.init(bip32_encryptkey, Cipher.MODE_DECRYPT);
			aes128.doFinal(recvBuffer, BIP32_OFFSET_CHILD_KEY, (short)(2*BIP32_KEY_SIZE), recvBuffer, BIP32_OFFSET_CHILD_KEY);
			// copy privkey & chain code in parent's offset
			Util.arrayCopyNonAtomic(recvBuffer, BIP32_OFFSET_CHILD_CHAINCODE, recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_KEY_SIZE); // chaincode
			Util.arrayCopyNonAtomic(recvBuffer, BIP32_OFFSET_CHILD_KEY, recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE); // extended_key
			recvBuffer[BIP32_KEY_SIZE]=0x00;
			
			// update parent_base for next iteration
			parent_base=base;			
		} // end for
		
		// at this point, recvBuffer contains a copy of the last extended key 
		// instantiate elliptic curve with last extended key + copy ACL
		bip32_extendedkey.setS(recvBuffer, BIP32_OFFSET_PARENT_KEY, BIP32_KEY_SIZE);
		
		// save chaincode to buffer then clear recvBuffer
		Util.arrayCopyNonAtomic(recvBuffer, (short)BIP32_OFFSET_PARENT_CHAINCODE, buffer, (short)0, BIP32_KEY_SIZE); 
		Util.arrayFillNonAtomic(recvBuffer, BIP32_OFFSET_PARENT_CHAINCODE, BIP32_OFFSET_END, (byte)0);
				
		// compute the corresponding partial public key...
        keyAgreement.init(bip32_extendedkey);
        short coordx_size= (short)32;
    	keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)33); //pubkey in uncompressed form
		Util.setShort(buffer, BIP32_KEY_SIZE, (short)(coordx_size));
        
        // self-sign coordx
        sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN);
        short sign_size= sigECDSA.sign(buffer, (short)0, (short)(BIP32_KEY_SIZE+2+coordx_size), buffer, (short)(BIP32_KEY_SIZE+coordx_size+4));
        Util.setShort(buffer, (short)(BIP32_KEY_SIZE+coordx_size+2), sign_size);
        
        // coordx signed by authentikey
        sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN);
        short sign_size2= sigECDSA.sign(buffer, (short)0, (short)(BIP32_KEY_SIZE+coordx_size+sign_size+4), buffer, (short)(BIP32_KEY_SIZE+coordx_size+sign_size+6));
        Util.setShort(buffer, (short)(BIP32_KEY_SIZE+coordx_size+sign_size+4), sign_size2);
        
        // return x-coordinate of public key+signatures
        // the client can recover full public-key by guessing the compression value () and verifying the signature... 
        // buffer=[chaincode(32) | coordx_size(2) | coordx | sign_size(2) | self-sign | sign_size(2) | auth_sign]
        return (short)(BIP32_KEY_SIZE+coordx_size+sign_size+sign_size2+6);
        
	}// end of getBip32ExtendedKey()	
	
	/**
	 * DEPRECATED - Not necessary anymore when recovering the pubkey with ALG_EC_SVDP_DH_PLAIN_XY
	 * A minimalist API is maintained for backward compatibility.
	 * 
	 * This function allows to compute an extended pubkey externally and 
	 * store it in the secure BIP32 memory cache for future use. 
	 * This allows to speed up computation during derivation of non-hardened child.
	 * 
	 * ins: 0x74
	 * p1: 
	 * p2:
	 * data: [chaincode(32b) | coordx_size(2b) | coordx | sig_size(2b) | sig | sig_size(2b) | sig2 ]
	 * 			[ coordy_size(2b) | coordy]
	 *  returns: none
	 */
	private short setBIP32ExtendedPubkey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		short pos=0;
		Util.setShort(buffer, pos, bip32_om.nb_elem_free); // number of slot available 
		pos += (short) 2;
		Util.setShort(buffer, pos, bip32_om.nb_elem_used); // number of slot used 
		pos += (short) 2;
		return pos;
	}// end of setBIP32ExtendedPubkey
	
    /**
     * This function signs Bitcoin message using std or Bip32 extended key
	 *
     * ins: 0x6E
	 * p1: key number or 0xFF for the last derived Bip32 extended key 
	 * p2: Init-Update-Finalize
	 * data(init): [ full_msg_size(4b) | (option)altcoinSize(1b)-altcoin]
	 * data(update): [chunk_size(2b) | chunk_data]
	 * data(finalize): [chunk_size(2b) | chunk_data | (option)HMAC-2FA(20b)]
	 *  
	 * returns(init/update): none
	 * return(finalize): [sig]
	 *
     */
    private short signMessage(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) )
			ISOException.throwIt(SW_INCORRECT_P1);
		
		byte p2= buffer[ISO7816.OFFSET_P2];
    	if (p2 <= (byte) 0x00 || p2 > (byte) 0x03)
			ISOException.throwIt(SW_INCORRECT_P2);
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		
    	// check whether the seed is initialized
		if (key_nb==(byte)0xFF && !bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
		
		short chunk_size, offset, recvOffset;
		switch(p2){
			// initialization
			case OP_INIT: 
				recvOffset=0;
				if (bytesLeft<(short)4){
					ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);}
				else if (bytesLeft==(short)4){
					// copy Btc message header to tmp buffer
					Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length);
					recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length;
				}
				else {
					//Altcoin msg header from buffer
					offset= (short)ISO7816.OFFSET_CDATA;
					offset+=4;
					byte altcoinSize= buffer[offset];
					offset++;
					if (bytesLeft!=(short)(5+altcoinSize))
						ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
					recvBuffer[0]= (byte) (altcoinSize+17);
					Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, (short)1, (short)altcoinSize);
					Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)8, recvBuffer, (short)(1+altcoinSize), (short)17); //' Signed Message:\n'
					recvOffset= (short) (18+altcoinSize);
				}
				
				// buffer data = [4-byte msg_size]
				offset= (short)ISO7816.OFFSET_CDATA;
				recvOffset+= Biginteger.encodeVarInt(buffer, offset, recvBuffer, recvOffset);
				offset+=4;
				sha256.reset();
				sha256.update(recvBuffer, (short) 0, recvOffset);
				sign_flag= true; // set flag
				return (short)0;
			
			// update (optionnal)
			case OP_PROCESS: 
				if (!sign_flag)
					ISOException.throwIt(SW_INCORRECT_INITIALIZATION);
					
				// buffer data = [2-byte chunk_size | n-byte message to sign]
				offset= (short)ISO7816.OFFSET_CDATA;
				chunk_size=Util.getShort(buffer, offset);
				offset+=2;
				sha256.update(buffer, (short) offset, chunk_size);
				return (short)0;
			
			// final
			case OP_FINALIZE: 
				if (!sign_flag)
					ISOException.throwIt(SW_INCORRECT_INITIALIZATION);
				
				// buffer data = [2-byte chunk_size | n-byte message to sign]
				offset= (short)ISO7816.OFFSET_CDATA;
				chunk_size=Util.getShort(buffer, offset);
				offset+=2;
				bytesLeft-=2;
				sha256.doFinal(buffer, (short)offset, chunk_size, recvBuffer, (short) 0);
				sign_flag= false;// reset flag
				offset+=chunk_size;
				bytesLeft-=chunk_size;
				
				// check 2FA if required
				if(needs_2FA){
					if (bytesLeft<(short)20)
						ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
					// hmac of 64-bytes msg: (sha256(btcheader+msg) | 32bytes 0xBB-padding)
					Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0xBB);
					HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
					if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0)
						ISOException.throwIt(SW_SIGNATURE_INVALID);
				}
				
				// set key & sign
		    	if (key_nb==(byte)0xFF)
		    		sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN);
		    	else{
		    		Key key= eckeys[key_nb];
		    		// check type and size
		    		if ((key == null) || !key.isInitialized())
		    			ISOException.throwIt(SW_INCORRECT_P1);
		    		if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE)
		    			ISOException.throwIt(SW_INCORRECT_ALG);		
		    		if (key.getSize()!= LENGTH_EC_FP_256)
		    			ISOException.throwIt(SW_INCORRECT_ALG);
		    		sigECDSA.init(key, Signature.MODE_SIGN);
		    	}
		        short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0);
		        return sign_size;
		    	
		} //end switch  
		
		return (short)0;
	}
    
    /**
     * DEPRECATED - the generic signMessage() should be used instead!
     * 
     * This function signs short Bitcoin message using std or Bip32 extended key in 1 APDU
	 * 
     * ins: 0x72
	 * p1: key number or 0xFF for the last derived Bip32 extended key 
	 * p2: 0x00
	 * data: [msg_size(2b) | msg_data | (option)HMAC(20b)]
	 * 
	 * return: [sig]
	 *
     */
//    private short signShortMessage(APDU apdu, byte[] buffer){
//		// check that PIN[0] has been entered previously
//		if (!pins[0].isValidated())
//			ISOException.throwIt(SW_UNAUTHORIZED);
//		
//		byte key_nb = buffer[ISO7816.OFFSET_P1];
//		if ( (key_nb!=(byte)0xFF) && ((key_nb < 0)||(key_nb >= MAX_NUM_KEYS)) ) 
//			ISOException.throwIt(SW_INCORRECT_P1);
//		if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00)
//			ISOException.throwIt(SW_INCORRECT_P2);
//		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
//		
//		// check whether the seed is initialized
//		if (key_nb==(byte)0xFF && !bip32_seeded)
//			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
//				
//		// copy message header to tmp buffer
//		Util.arrayCopyNonAtomic(BITCOIN_SIGNED_MESSAGE_HEADER, (short)0, recvBuffer, (short)0, (short)BITCOIN_SIGNED_MESSAGE_HEADER.length);
//		short recvOffset= (short)BITCOIN_SIGNED_MESSAGE_HEADER.length;
//		
//		// buffer data = [2-byte size | n-byte message to sign]
//		short offset= (short)ISO7816.OFFSET_CDATA;
//		short msgSize= Util.getShort(buffer, offset);
//		recvOffset+= Biginteger.encodeShortToVarInt(msgSize, recvBuffer, recvOffset);
//		offset+=2;
//		bytesLeft-=2;
//		Util.arrayCopyNonAtomic(buffer, offset, recvBuffer, recvOffset, msgSize);
//		offset+= msgSize;
//		recvOffset+= msgSize;
//		bytesLeft-= msgSize;
//		
//		// hash SHA-256
//		sha256.reset();
//		sha256.doFinal(recvBuffer, (short) 0, recvOffset, recvBuffer, (short) 0);
//		
//		// check 2FA if required
//		if(needs_2FA){
//			if (bytesLeft<(short)20)
//				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
//			// hmac of 64-bytes msg: (sha256(btcheader+msg) | 32bytes 0xBB-padding)
//			Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0xBB);
//			HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
//			if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0)
//				ISOException.throwIt(SW_SIGNATURE_INVALID);
//		}
//		
//        // set key & sign
//    	if (key_nb==(byte)0xFF)
//    		sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN);
//    	else{
//    		Key key= eckeys[key_nb];
//    		// check type and size
//    		if ((key == null) || !key.isInitialized())
//    			ISOException.throwIt(SW_INCORRECT_P1);
//    		if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE)
//    			ISOException.throwIt(SW_INCORRECT_ALG);		
//    		if (key.getSize()!= LENGTH_EC_FP_256)
//    			ISOException.throwIt(SW_INCORRECT_ALG);
//    		sigECDSA.init(key, Signature.MODE_SIGN);
//    	}
//    	short sign_size= sigECDSA.sign(recvBuffer, (short)0, (short)32, buffer, (short)0);
//        return sign_size;
//    }    
    
    /**
     * This function parses a raw transaction and returns the corresponding double SHA-256
	 * If the Bip32 seed is initialized, the hash is signed with the authentikey.
	 * 
     * ins: 0x71
	 * p1: Init or Process 
	 * p2: PARSE_STD ou PARSE_SEGWIT
	 * data: [raw_tx]
	 * 
	 * return: [hash(32b) | needs_confirm(1b) | sig_size(2b) | sig ]
	 *
	 * where:
	 * 		needs_confirm is 0x01 if a hmac-sha1 of the hash must be provided for tx signing 
     */
    private short ParseTransaction(APDU apdu, byte[] buffer){
    	// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
    	byte p1 = buffer[ISO7816.OFFSET_P1];
        byte p2 = buffer[ISO7816.OFFSET_P2];
        short dataOffset = ISO7816.OFFSET_CDATA;
        short dataRemaining = (short)(buffer[ISO7816.OFFSET_LC] & 0xff);
    	
        if (p1== OP_INIT){
        	// initialize transaction object
        	Transaction.resetTransaction();
        }
        
        // parse the transaction
        byte result = Transaction.RESULT_ERROR;
        if (p2== PARSE_STD){
        	 result= Transaction.parseTransaction(buffer, dataOffset, dataRemaining);
        }else if (p2== PARSE_SEGWIT){
        	result = Transaction.parseSegwitTransaction(buffer, dataOffset, dataRemaining);
        }
        if (result == Transaction.RESULT_ERROR) {
        	Transaction.resetTransaction();
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        else if (result == Transaction.RESULT_MORE) {
            
        	short offset = 0;
        	// Transaction context
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_CURRENT_I, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_CURRENT_O, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_SCRIPT_COORD, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_AMOUNT, buffer, offset, Transaction.SIZEOF_AMOUNT);
            offset += Transaction.SIZEOF_AMOUNT;
            
            // not so relevant context info mainly for debugging (not sensitive)
//            if (DEBUG_MODE){
//	            Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_I, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_O, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_REMAINING, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_TMP_BUFFER, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += Transaction.SIZEOF_AMOUNT;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_ACTIVE, buffer, offset, Transaction.SIZEOF_U8);
//	        	offset += 1;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_B_TRANSACTION_STATE, buffer, offset, Transaction.SIZEOF_U8);
//	        	offset += 1;
//	        	Util.setShort(buffer, offset, dataOffset);
//	        	offset+=2;
//	        	Util.setShort(buffer, offset, dataRemaining);
//	        	offset+=2;
//            }
            
        	return offset;
        }
        else if (result == Transaction.RESULT_FINISHED) {
            
        	// check whether 2fa is required (hmac-sha1 of tx hash)
            short need2fa=(short)0x0000;
            Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_AMOUNT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8);
            Biginteger.add_carry(transactionData, OFFSET_TRANSACTION_AMOUNT, transactionData, OFFSET_TRANSACTION_TOTAL, (short)8);
            if (needs_2FA){
	            if (Biginteger.lessThan(data2FA, OFFSET_2FA_LIMIT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8)){
	            	need2fa^= HMAC_CHALRESP_2FA; // set msb 
	            }
            }
            
        	// store transaction hash (single hash!) in memory 
            Transaction.digestFull.doFinal(transactionData, (short)0, (short)0, transactionData, OFFSET_TRANSACTION_HASH);
            // return transaction hash (double hash!) 
            // the msb bit of hash_size is set to 1 if a Hmac confirmation is required for the tx signature
            sha256.reset();
            short hash_size=sha256.doFinal(transactionData, OFFSET_TRANSACTION_HASH, (short)32, buffer, (short)2);
            Util.setShort(buffer, (short)0, (short)(hash_size+2));
            Util.setShort(buffer, (short)(2+hash_size), need2fa);
            short offset = (short)(2+hash_size+2);
        	
            // hash signed by authentikey if seed is initialized
            if (bip32_seeded){
	            sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN);
	            short sign_size= sigECDSA.sign(buffer, (short)0, offset, buffer, (short)(offset+2));
	            Util.setShort(buffer, offset, sign_size);
	            offset+=(short)(2+sign_size); 
            }else{
            	Util.setShort(buffer, offset, (short)0);
            	offset+=(short)2;
            }
        	
        	// Transaction context 
            //todo: put this context in other method
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_CURRENT_I, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_CURRENT_O, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_SCRIPT_COORD, buffer, offset, Transaction.SIZEOF_U32);
        	offset += 4;
        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_AMOUNT, buffer, offset, Transaction.SIZEOF_AMOUNT);
            offset += Transaction.SIZEOF_AMOUNT;
            
            // not so relevant context info mainly for debugging (not sensitive)
//            if (DEBUG_MODE){
//	            Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_I, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_REMAINING_O, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_REMAINING, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += 4;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_TMP_BUFFER, buffer, offset, Transaction.SIZEOF_U32);
//	        	offset += Transaction.SIZEOF_AMOUNT;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_I_SCRIPT_ACTIVE, buffer, offset, Transaction.SIZEOF_U8);
//	        	offset += 1;
//	        	Util.arrayCopyNonAtomic(Transaction.ctx, Transaction.TX_B_TRANSACTION_STATE, buffer, offset, Transaction.SIZEOF_U8);
//	        	offset += 1;
//	        	Util.setShort(buffer, offset, dataOffset);
//	        	offset+=2;
//	        	Util.setShort(buffer, offset, dataRemaining);
//	        	offset+=2;
//            }
            
            // reset data and send result
            // buffer= [tx_hash(32) | sign_size(2) | signature | tx context(20 - 46)] //deprecated
            // buffer= [(hash_size+2)(2b) | tx_hash(32b) | need2fa(2b) | sig_size(2b) | sig(sig_size) | txcontext]
            Transaction.resetTransaction();
            return offset;
        }
        
        return 0; //should not happen!
    }
    
    /**
     * This function signs the current hash transaction with a std or the last extended key
     * The hash provided in the APDU is compared to the version stored inside the chip.
	 * Depending of the total amount in the transaction and the predefined limit, 
	 * a HMAC must be provided as an additional security layer. 
	 * 
     * ins: 0x6F
	 * p1: key number or 0xFF for the last derived Bip32 extended key  
	 * p2: 0x00
	 * data: [hash(32b) | option: 2FA-flag(2b)|hmac(20b)]
	 * 
	 * return: [sig ]
	 *
     */
    private short SignTransaction(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
    	byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ( (key_nb!=(byte)0xFF) && ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) )
			ISOException.throwIt(SW_INCORRECT_P1);
		
    	short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		if (bytesLeft<MessageDigest.LENGTH_SHA_256)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    	
    	// check whether the seed is initialized
		if (key_nb==(byte)0xFF && !bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
		
		// check doublehash value in buffer with cached singlehash value
		sha256.reset();
		sha256.doFinal(transactionData, OFFSET_TRANSACTION_HASH, MessageDigest.LENGTH_SHA_256, recvBuffer, (short)0);
		if ((byte)0 != Util.arrayCompare(buffer, ISO7816.OFFSET_CDATA, recvBuffer, (short)0, MessageDigest.LENGTH_SHA_256))
			ISOException.throwIt(SW_INCORRECT_TXHASH);
		
		// check challenge-response answer if necessary
		if(needs_2FA){
			if(	Biginteger.lessThan(data2FA, OFFSET_2FA_LIMIT, transactionData, OFFSET_TRANSACTION_AMOUNT, (short)8)){
				if (bytesLeft<MessageDigest.LENGTH_SHA_256+MessageDigest.LENGTH_SHA+(short)2)
					ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
				// check flag for 2fa_hmac_chalresp
				short hmac_flags= Util.getShort(buffer, (short)(ISO7816.OFFSET_CDATA+32));
				if (hmac_flags!=HMAC_CHALRESP_2FA)
					ISOException.throwIt(SW_INCORRECT_ALG);
				// hmac of 64-bytes msg: (doublesha256(raw_tx) | 32bytes zero-padding)
				Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0x00);
				HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
				if (Util.arrayCompare(buffer, (short)(ISO7816.OFFSET_CDATA+32+2), recvBuffer, (short)64, (short)20)!=0)
					ISOException.throwIt(SW_SIGNATURE_INVALID);
				// reset total amount
				Util.arrayFillNonAtomic(transactionData, OFFSET_TRANSACTION_TOTAL, (short)8, (byte)0x00);
			}
			else{					
				//update total amount
				Util.arrayCopyNonAtomic(transactionData, OFFSET_TRANSACTION_AMOUNT, transactionData, OFFSET_TRANSACTION_TOTAL, (short)8);
			}
		}
		
		// hash+sign singlehash
    	if (key_nb==(byte)0xFF)
    		sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN);
    	else{
    		Key key= eckeys[key_nb];
    		// check type and size
    		if ((key == null) || !key.isInitialized())
    			ISOException.throwIt(SW_INCORRECT_P1);
    		if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE)
    			ISOException.throwIt(SW_INCORRECT_ALG);		
    		if (key.getSize()!= LENGTH_EC_FP_256)
    			ISOException.throwIt(SW_INCORRECT_ALG);
    		sigECDSA.init(key, Signature.MODE_SIGN);
    	}
        short sign_size= sigECDSA.sign(transactionData, OFFSET_TRANSACTION_HASH, (short)32, buffer, (short)0);
        return sign_size;
    }
    
    
    /**
     * This function signs a given transaction hash with a std or the last extended key
     * If 2FA is enabled, a HMAC must be provided as an additional security layer. 
	 * 
     * ins: 0x7A
	 * p1: key number or 0xFF for the last derived Bip32 extended key  
	 * p2: 0x00
	 * data: [hash(32b) | option: 2FA-flag(2b)|hmac(20b)]
	 * 
	 * return: [sig ]
	 * 
     */
    private short SignTransactionHash(APDU apdu, byte[] buffer){
    	
    	// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
    	byte key_nb = buffer[ISO7816.OFFSET_P1];
		if ( (key_nb!=(byte)0xFF) && ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) )
			ISOException.throwIt(SW_INCORRECT_P1);
		
    	short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		if (bytesLeft<MessageDigest.LENGTH_SHA_256)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    	
    	// check whether the seed is initialized
		if (key_nb==(byte)0xFF && !bip32_seeded)
			ISOException.throwIt(SW_BIP32_UNINITIALIZED_SEED);
    	
		// check 2FA if required
		if(needs_2FA){
			// check data length
			if (bytesLeft<MessageDigest.LENGTH_SHA_256+MessageDigest.LENGTH_SHA+(short)2)
				ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
			// check flag for 2fa_hmac_chalresp
			short hmac_flags= Util.getShort(buffer, (short)(ISO7816.OFFSET_CDATA+32));
			if (hmac_flags!=HMAC_CHALRESP_2FA)
				ISOException.throwIt(SW_INCORRECT_ALG);
			// hmac of 64-bytes msg: ( 32bytes tx_hash | 32bytes 0xCC-padding)
			Util.arrayCopyNonAtomic(buffer, (short)ISO7816.OFFSET_CDATA, recvBuffer, (short)0, (short)32);
			Util.arrayFillNonAtomic(recvBuffer, (short)32, (short)32, (byte)0xCC);
			HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
			if (Util.arrayCompare(buffer, (short)(ISO7816.OFFSET_CDATA+32+2), recvBuffer, (short)64, (short)20)!=0)
				ISOException.throwIt(SW_SIGNATURE_INVALID);
		}
		
		// hash+sign singlehash
    	if (key_nb==(byte)0xFF)
    		sigECDSA.init(bip32_extendedkey, Signature.MODE_SIGN);
    	else{
    		Key key= eckeys[key_nb];
    		// check type and size
    		if ((key == null) || !key.isInitialized())
    			ISOException.throwIt(SW_INCORRECT_P1);
    		if (key.getType() != KeyBuilder.TYPE_EC_FP_PRIVATE)
    			ISOException.throwIt(SW_INCORRECT_ALG);		
    		if (key.getSize()!= LENGTH_EC_FP_256)
    			ISOException.throwIt(SW_INCORRECT_ALG);
    		sigECDSA.init(key, Signature.MODE_SIGN);
    	}
        short sign_size= sigECDSA.signPreComputedHash(buffer, ISO7816.OFFSET_CDATA, MessageDigest.LENGTH_SHA_256, buffer, (short)0);
        return sign_size;
    }
    
    
    /**
	 * This function allows to set the 2FA key and enable 2FA.
	 * Once activated, 2FA can only be deactivated when the seed is reset.
	 *  
	 *  ins: 0x79
	 *  p1: 0x00
	 *  p2: 0x00
	 *  data: [hmacsha1_key(20b) | amount_limit(8b)]
	 *  return: (none)
	 */
	private short set2FAKey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		// cannot modify an existing 2FA!
		if (needs_2FA)
			ISOException.throwIt(SW_2FA_INITIALIZED_KEY);
		
		//check input length
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		if (bytesLeft < (short)(20+8))
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		if (!done_once_2FA){
			data2FA= new byte[OFFSET_2FA_SIZE];
			randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
	        aes128_cbc= Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
	        key_2FA= (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
	        done_once_2FA= true;
		}
		
		short offset= ISO7816.OFFSET_CDATA;
		Util.arrayCopyNonAtomic(buffer, offset, data2FA, OFFSET_2FA_HMACKEY, (short)20); 
		offset+=(short)20;
		Util.arrayCopyNonAtomic(buffer, offset, data2FA, OFFSET_2FA_LIMIT, (short)8); 
		offset+=(short)8;
		// hmac derivation for id_2FA & key_2FA
		HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, CST_2FA, (short)0, (short)6, data2FA, OFFSET_2FA_ID);
        HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, CST_2FA, (short)6, (short)7, recvBuffer, (short)0);
        key_2FA.setKey(recvBuffer,(short)0); // AES-128: 16-bytes key!!
        needs_2FA= true;	
        
        return (short)0;
	}
    
    /**
	 * This function allows to reset the 2FA key and disable 2FA.
	 * Once activated, 2FA can only be deactivated when the seed is reset and all eckeys cleared.
	 *  
	 *  ins: 0x78
	 *  p1: 0x00
	 *  p2: 0x00
	 *  data: [hmacsha1_key(20b)]
	 *  return: (none)
	 */
	private short reset2FAKey(APDU apdu, byte[] buffer){
		// check that PIN[0] has been entered previously
		if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
		
		// check length
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		if (bytesLeft < (short)20)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		
		// reset 2FA can only be done if all private keys are cleared
		if (!needs_2FA)
			ISOException.throwIt(SW_2FA_UNINITIALIZED_KEY);
		if (bip32_seeded)
			ISOException.throwIt(SW_BIP32_INITIALIZED_SEED);
		if (eckeys_flag != 0x0000)
			ISOException.throwIt(SW_ECKEYS_INITIALIZED_KEY);
		
		// compute hmac(2FA_ID) and compare with value provided 
		// hmac of 64-bytes msg: (id_2FA(20b) | 44 bytes 0xAA-padding)
		short offset= ISO7816.OFFSET_CDATA;
		Util.arrayFillNonAtomic(recvBuffer, (short)0, (short)64, (byte)0xAA);
		Util.arrayCopyNonAtomic(data2FA, OFFSET_2FA_ID, recvBuffer, (short)0, (short)20);
		HmacSha160.computeHmacSha160(data2FA, OFFSET_2FA_HMACKEY, (short)20, recvBuffer, (short)0, (short)64, recvBuffer, (short)64);
		if (Util.arrayCompare(buffer, offset, recvBuffer, (short)64, (short)20)!=0)
			ISOException.throwIt(SW_SIGNATURE_INVALID);
		
		// reset flag and data
        needs_2FA= false;	
        key_2FA.clearKey();
        Util.arrayFillNonAtomic(data2FA, (short)0, OFFSET_2FA_SIZE, (byte)0x00);      
        
        return (short)0;
	}
    
    /**
     * This function encrypts/decrypt a given message with a 16bytes secret key derived from the 2FA key.
     * It also returns an id derived from the 2FA key.
     * This is used to privately exchange tx data between the hw wallet and the 2FA device.
	 * 
     * Algorithms: 
     *      id_2FA is hmac-sha1(secret_2FA, "id_2FA"), 
     *      key_2FA is hmac-sha1(secret_2FA, "key_2FA"), 
     *      message encrypted using AES
     *
     * ins: 0x76
	 * p1: 0x00 for encryption, 0x01 for decryption  
	 * p2: Init-Update-Finalize
	 * data(init): IF_ENCRYPT: none ELSE: [IV(16b)]
     * data(update/finalize): [chunk_size(2b) | chunk_data]
	 * 
	 * return(init): IF_ENCRYPT:[IV(16b) | id_2FA(20b)] ELSE: none
     * return(update/finalize): [chunk_size(2b) | chunk_data]
	 * 
	 *
     */
    private short CryptTransaction2FA(APDU apdu, byte[] buffer){
    	// check that PIN[0] has been entered previously
    	if (!pins[0].isValidated())
			ISOException.throwIt(SW_UNAUTHORIZED);
        
    	// check that 2FA is enabled
		if (!needs_2FA)
			ISOException.throwIt(SW_2FA_UNINITIALIZED_KEY);
    	
        byte ciph_dir = buffer[ISO7816.OFFSET_P1];
        byte ciph_op = buffer[ISO7816.OFFSET_P2];
        short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
        short dataOffset = ISO7816.OFFSET_CDATA;
        
        short IVlength=(short)16;
        switch(ciph_op){
            case OP_INIT:
                if (ciph_dir!=Cipher.MODE_ENCRYPT &&  ciph_dir!=Cipher.MODE_DECRYPT )
                    ISOException.throwIt(SW_INVALID_PARAMETER);
                
                if (ciph_dir==Cipher.MODE_ENCRYPT){
                    randomData.generateData(buffer,(short)0, IVlength);
                    aes128_cbc.init(key_2FA, Cipher.MODE_ENCRYPT, buffer, (short)0, IVlength);
                    Util.arrayCopyNonAtomic(data2FA, OFFSET_2FA_ID, buffer, (short)IVlength, (short)20);
                    return (short)(IVlength + 20);
                }
                if (ciph_dir==Cipher.MODE_DECRYPT){
                    aes128_cbc.init(key_2FA, Cipher.MODE_DECRYPT, buffer, dataOffset, IVlength);
                    return (short)0;
                }
                break;
            case OP_PROCESS:
            case OP_FINALIZE:
                if (bytesLeft < 2)
                	ISOException.throwIt(SW_INVALID_PARAMETER); 
                short size = Util.getShort(buffer, dataOffset);
                if (bytesLeft < (short) (2 + size))
                	ISOException.throwIt(SW_INVALID_PARAMETER); 
                
                short sizeout=0;
                if (ciph_op == OP_PROCESS){
                    sizeout=aes128_cbc.update(buffer, (short) (dataOffset + 2), size, buffer, (short) 2);
                }
                else {// ciph_op == OP_FINALIZE
                    sizeout=aes128_cbc.doFinal(buffer, (short) (dataOffset + 2), size, buffer, (short) 2);
            	}
                // Also copies the Short size information
                Util.setShort(buffer,(short)0,  sizeout);
                return (short) (sizeout + 2);
                
            default:
                ISOException.throwIt(SW_INCORRECT_P2);    
        } 
        return (short)0;
    }    
    
    /**
	 * This function allows to initiate a Secure Channel
	 *  
	 *  ins: 0x81
	 *  p1: 0x00
	 *  p2: 0x00
	 *  data: [client-pubkey(65b)]
	 *  return: [coordx_size(2b) | authentikey-coordx | sig_size(2b) | self-sig | sig2_size(optional) | authentikey-sig(optional)]
	 */
	private short InitiateSecureChannel(APDU apdu, byte[] buffer){
		
		// get client pubkey
		short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
		if (bytesLeft < (short)65)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
		if (buffer[ISO7816.OFFSET_CDATA] != (byte)0x04)
			ISOException.throwIt(SW_INVALID_PARAMETER);
			
		// generate a new ephemeral key
		sc_ephemeralkey.clearKey();
		Secp256k1.setCommonCurveParameters(sc_ephemeralkey);// keep public params!
		randomData.generateData(recvBuffer, (short)0, BIP32_KEY_SIZE);
		sc_ephemeralkey.setS(recvBuffer, (short)0, BIP32_KEY_SIZE); //random value first
		
		// compute the shared secret...
        keyAgreement.init(sc_ephemeralkey);        
        short coordx_size= (short)32;
    	keyAgreement.generateSecret(buffer, ISO7816.OFFSET_CDATA, (short) 65, recvBuffer, (short)0); //pubkey in uncompressed form
    	// derive sc_sessionkey & sc_mackey
    	HmacSha160.computeHmacSha160(recvBuffer, (short)1, (short)32, CST_SC, (short)6, (short)6, recvBuffer, (short)33);
        Util.arrayCopyNonAtomic(recvBuffer, (short)33, sc_buffer, OFFSET_SC_MACKEY, SIZE_SC_MACKEY);
        HmacSha160.computeHmacSha160(recvBuffer, (short)1, (short)32, CST_SC, (short)0, (short)6, recvBuffer, (short)33);
    	sc_sessionkey.setKey(recvBuffer,(short)33); // AES-128: 16-bytes key!!       
//    	//alternatively: derive session_key (sha256 of coordx)
//    	sha256.reset();
//    	sha256.doFinal(recvBuffer, (short)1, (short)32, recvBuffer, (short) 0);
//    	sc_sessionkey.setKey(recvBuffer,(short)0); // AES-128: 16-bytes key!!
//    	//derive mac_key
//    	sha256.reset();
//    	sha256.doFinal(recvBuffer, (short)0, (short)32, sc_mackey, (short) 0);
    	
    	//reset IV counter
    	Util.arrayFillNonAtomic(sc_buffer, OFFSET_SC_IV, SIZE_SC_IV, (byte) 0);
		
		// self signed ephemeral pubkey
		keyAgreement.generateSecret(Secp256k1.SECP256K1, Secp256k1.OFFSET_SECP256K1_G, (short) 65, buffer, (short)1); //pubkey in uncompressed form
		Util.setShort(buffer, (short)0, coordx_size);
		sigECDSA.init(sc_ephemeralkey, Signature.MODE_SIGN);
        short sign_size= sigECDSA.sign(buffer, (short)0, (short)(coordx_size+2), buffer, (short)(coordx_size+4));
        Util.setShort(buffer, (short)(coordx_size+2), sign_size);
        
		// hash signed by authentikey if seed is initialized
        short offset= (short)(2+coordx_size+2+sign_size);
        if (bip32_seeded){
            sigECDSA.init(bip32_authentikey, Signature.MODE_SIGN);
            short sign2_size= sigECDSA.sign(buffer, (short)0, offset, buffer, (short)(offset+2));
            Util.setShort(buffer, offset, sign2_size);
            offset+=(short)(2+sign2_size); 
        }else{
        	Util.setShort(buffer, offset, (short)0);
        	offset+=(short)2;
        }
		
        initialized_secure_channel= true;
        
        // return x-coordinate of public key+signature
        // the client can recover full public-key from the signature or
        // by guessing the compression value () and verifying the signature... 
        // buffer= [coordx_size(2) | coordx | sigsize(2) | sig | sig2_size(optional) | sig2(optional)]
        return offset;
	}
    
    /**
	 * This function allows to decrypt a secure channel message
	 *  
	 *  ins: 0x82
	 *  
	 *  p1: 0x00 (RFU)
	 *  p2: 0x00 (RFU)
	 *  data: [IV(16b) | data_size(2b) | encrypted_command | mac_size(2b) | mac]
     *  
     *  return: [decrypted command]
     *   
	 */
	private short ProcessSecureChannel(APDU apdu, byte[] buffer){
		
        short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]);
        short offset = ISO7816.OFFSET_CDATA;
        
        if (!initialized_secure_channel){
        	ISOException.throwIt(SW_SECURE_CHANNEL_UNINITIALIZED);
        }
        
        // check hmac
        if (bytesLeft<18)
        	ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        short sizein = Util.getShort(buffer, (short) (offset+SIZE_SC_IV));
        if (bytesLeft<(short)(SIZE_SC_IV+2+sizein+2))
        	ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        short sizemac= Util.getShort(buffer, (short) (offset+SIZE_SC_IV+2+sizein));
        if (sizemac != (short)20)
        	ISOException.throwIt(SW_SECURE_CHANNEL_WRONG_MAC);
        if (bytesLeft<(short)(SIZE_SC_IV+2+sizein+2+sizemac))
        	ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        HmacSha160.computeHmacSha160(sc_buffer, OFFSET_SC_MACKEY, SIZE_SC_MACKEY, buffer, offset, (short)(SIZE_SC_IV+2+sizein), recvBuffer, (short)0);
        if ( Util.arrayCompare(recvBuffer, (short)0, buffer, (short)(offset+SIZE_SC_IV+2+sizein+2), (short)20) != (byte)0 )
        	ISOException.throwIt(SW_SECURE_CHANNEL_WRONG_MAC);
        
        // process IV
        // IV received from client should be odd and strictly greater than locally saved IV
        // IV should be random (the 12 first bytes), never reused (the last 4 bytes counter) and different for send and receive
        if ((buffer[(short)(offset+SIZE_SC_IV-(short)1)] & (byte)0x01)==0x00)// should be odd
        	ISOException.throwIt(SW_SECURE_CHANNEL_WRONG_IV);
        if ( !Biginteger.lessThan(sc_buffer, OFFSET_SC_IV_COUNTER, buffer, (short)(offset+SIZE_SC_IV_RANDOM), SIZE_SC_IV_COUNTER ) ) //and greater than local IV
        	ISOException.throwIt(SW_SECURE_CHANNEL_WRONG_IV);
        // update local IV
        Util.arrayCopy(buffer, (short)(offset+SIZE_SC_IV_RANDOM), sc_buffer, OFFSET_SC_IV_COUNTER, SIZE_SC_IV_COUNTER);
        Biginteger.add1_carry(sc_buffer, OFFSET_SC_IV_COUNTER, SIZE_SC_IV_COUNTER);
     	randomData.generateData(sc_buffer, OFFSET_SC_IV_RANDOM, SIZE_SC_IV_RANDOM);
     	sc_aes128_cbc.init(sc_sessionkey, Cipher.MODE_DECRYPT, buffer, offset, SIZE_SC_IV);
        offset+=SIZE_SC_IV;
        bytesLeft-=SIZE_SC_IV;
        
        //decrypt command
        offset+=2;
        bytesLeft-=2;
        if (bytesLeft<sizein)
        	ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        short sizeout=sc_aes128_cbc.doFinal(buffer, offset, sizein, buffer, (short) (0));
        return sizeout;
	}
    
} // end of class JAVA_APPLET