/*
*******************************************************************************
*   Java Card Bitcoin Hardware Wallet
*   (c) 2015 Ledger
*
*   This program is free software: you can redistribute it and/or modify
*   it under the terms of the GNU Affero General Public License as
*   published by the Free Software Foundation, either version 3 of the
*   License, or (at your option) any later version.
*
*   This program is distributed in the hope that it will be useful,
*   but WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*   GNU Affero General Public License for more details.
*
*   You should have received a copy of the GNU Affero General Public License
*   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************
*/

/* This file is automatically processed from the .javap version and only included for convenience. Please refer to the .javap file
   for more readable code */

package com.ledger.wallet;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.CardRuntimeException;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.OwnerPIN;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.AESKey;
import javacard.security.KeyBuilder;
import javacard.security.ECPrivateKey;
import javacard.security.ECPublicKey;
import javacard.security.Signature;
public class LedgerWalletApplet extends Applet {
    public LedgerWalletApplet(byte[] parameters, short parametersOffset, byte parametersLength) {
        BCDUtils.init();
        TC.init();
        Crypto.init();
        Transaction.init();
        Bip32Cache.init();
        Keycard.init();
        limits = new byte[LIMIT_LAST];
        scratch256 = JCSystem.makeTransientByteArray((short)256, JCSystem.CLEAR_ON_DESELECT);
        transactionPin = new OwnerPIN(TRANSACTION_PIN_ATTEMPTS, TRANSACTION_PIN_SIZE);
        walletPin = new OwnerPIN(WALLET_PIN_ATTEMPTS, WALLET_PIN_SIZE);
        secondaryPin = new OwnerPIN(SECONDARY_PIN_ATTEMPTS, SECONDARY_PIN_SIZE);
        masterDerived = new byte[64];
        chipKey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false);
        trustedInputKey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false);
        developerKey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false);
        try {
            pairingKey = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false);
        }
        catch(Exception e) {
        }
        reset();
        if (parametersLength != 0) {
            attestationPrivate = (ECPrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false);
            attestationPublic = new byte[65];
            Secp256k1.setCommonCurveParameters(attestationPrivate);
            attestationPrivate.setS(parameters, parametersOffset, (short)32);
            parametersOffset += (short)32;
            attestationSignature = new byte[parameters[(short)(parametersOffset + 1)] + 2];
            Util.arrayCopy(parameters, parametersOffset, attestationSignature, (short)0, (short)attestationSignature.length);
        }
    }
    private static void reset() {
        Crypto.random.generateData(scratch256, (short)0, (short)16);
        chipKey.setKey(scratch256, (short)0);
        Util.arrayFillNonAtomic(scratch256, (short)0, (short)16, (byte)0x00);
        setup = TC.FALSE;
        limitsSet = TC.FALSE;
    }
    protected static void writeIdleText() {
        short offset = Util.arrayCopyNonAtomic(TEXT_IDLE, (short)0, LWNFCForumApplet.FILE_DATA, LWNFCForumApplet.OFFSET_TEXT, (short)TEXT_IDLE.length);
        LWNFCForumApplet.writeHeader((short)(offset - LWNFCForumApplet.OFFSET_TEXT));
    }
    protected static boolean isContactless() {
        return ((APDU.getProtocol() & APDU.PROTOCOL_MEDIA_MASK) == APDU.PROTOCOL_MEDIA_CONTACTLESS_TYPE_A);
    }
    private static void checkAccess(boolean checkPinContactless) {
        if ((setup == TC.FALSE) || (setup != TC.TRUE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (!isContactless() && !walletPin.isValidated()) {
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
        if (checkPinContactless && !walletPin.isValidated()) {
         ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }
    private static void checkInterfaceConsistency() {
        if ((isContactless() && (TC.ctxP[TC.P_TX_Z_WIRED] != TC.FALSE)) ||
            (!isContactless() && (TC.ctxP[TC.P_TX_Z_WIRED] != TC.TRUE))) {
              ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
    }
    private static void verifyKeyChecksum(byte[] buffer, short offset, short length, byte[] scratch, short scratchOffset) {
        Crypto.digestScratch.doFinal(buffer, offset, (short)(length - 4), scratch, scratchOffset);
        Crypto.digestScratch.doFinal(scratch, scratchOffset, TC.SIZEOF_SHA256, scratch, scratchOffset);
        if (Util.arrayCompare(scratch, scratchOffset, buffer, (short)(offset + length - 4), (short)4) != (byte)0x00) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
    }
    private static short publicKeyToAddress(byte[] out, short outOffset) {
     Crypto.digestScratch.doFinal(scratch256, (short)0, (short)33, scratch256, (short)33);
     if (Crypto.digestRipemd != null) {
      Crypto.digestRipemd.doFinal(scratch256, (short)33, (short)32, scratch256, (short)1);
     }
     else {
      Ripemd160.hash32(scratch256, (short)33, scratch256, (short)1, scratch256, (short)100);
     }
     scratch256[0] = stdVersion;
     Crypto.digestScratch.doFinal(scratch256, (short)0, (short)21, scratch256, (short)21);
     Crypto.digestScratch.doFinal(scratch256, (short)21, (short)32, scratch256, (short)21);
     return Base58.encode(scratch256, (short)0, (short)25, out, outOffset, scratch256, (short)100);
    }
    private static void signTransientPrivate(byte[] keyBuffer, short keyOffset, byte[] dataBuffer, short dataOffset, byte[] targetBuffer, short targetOffset) {
        if ((proprietaryAPI == null) || (!proprietaryAPI.hasDeterministicECDSASHA256())) {
            Crypto.signTransientPrivate(keyBuffer, keyOffset, dataBuffer, dataOffset, targetBuffer, targetOffset);
        }
        else {
            Crypto.initTransientPrivate(keyBuffer, keyOffset);
            proprietaryAPI.signDeterministicECDSASHA256(Crypto.transientPrivate, dataBuffer, dataOffset, (short)32, targetBuffer, targetOffset);
            if (Crypto.transientPrivateTransient) {
                Crypto.transientPrivate.clearKey();
            }
        }
    }
    private static void checkAirgapPersonalizationAvailable() throws ISOException {
        if ((attestationPublic == null) || (Crypto.keyAgreement == null) || (Crypto.keyPair == null) || (Crypto.blobEncryptDecryptAES == null) || (pairingKey == null)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
    }
    private static void handleGetAttestation(APDU apdu) throws ISOException {
        short offset = (short)0;
        byte[] buffer = apdu.getBuffer();
        Util.arrayCopyNonAtomic(attestationPublic, (short)0, buffer, offset, (short)65);
        offset += (short)65;
        Util.arrayCopyNonAtomic(attestationSignature, (short)0, buffer, offset, (short)attestationSignature.length);
        offset += (short)(attestationSignature.length);
        apdu.setOutgoingAndSend((short)0, offset);
    }
    private static void handleAirgapKeyAgreement(APDU apdu) throws ISOException {
        short offset = (short)0;
        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        checkAirgapPersonalizationAvailable();
        if (buffer[ISO7816.OFFSET_P1] == P1_INITIATE_PAIRING) {
            if (buffer[ISO7816.OFFSET_LC] != (byte)65) {
                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            }
            pairingDone = false;
            Crypto.keyPair.genKeyPair();
            Crypto.keyAgreement.init((ECPrivateKey)Crypto.keyPair.getPrivate());
            Crypto.keyAgreement.generateSecret(buffer, ISO7816.OFFSET_CDATA, (short)65, scratch256, (short)0);
            pairingKey.setKey(scratch256, (short)0);
            ((ECPublicKey)Crypto.keyPair.getPublic()).getW(buffer, offset);
            offset += (short)65;
            Crypto.signature.init(attestationPrivate, Signature.MODE_SIGN);
            Crypto.signature.sign(buffer, (short)0, (short)65, buffer, offset);
            offset += (short)(buffer[(short)(offset + 1)] + 2);
            apdu.setOutgoingAndSend((short)0, offset);
        }
        else
        if (buffer[ISO7816.OFFSET_P1] == P1_CONFIRM_PAIRING) {
            if (buffer[ISO7816.OFFSET_LC] != (byte)32) {
                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            }
            Crypto.initCipherAES(pairingKey, false);
            Crypto.blobEncryptDecryptAES.doFinal(buffer, ISO7816.OFFSET_CDATA, (short)32, scratch256, (short)0);
            pairingKey.setKey(scratch256, (short)0);
            pairingDone = true;
        }
        else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
    }
    private static void handleSetAttestationPublic(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        checkAirgapPersonalizationAvailable();
        if (buffer[ISO7816.OFFSET_LC] != (byte)attestationPublic.length) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, attestationPublic, (short)0, (short)attestationPublic.length);
    }
    private static void handleHasCachedPublicKey(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     apdu.setIncomingAndReceive();
     short offset = ISO7816.OFFSET_CDATA;
     byte derivationSize = buffer[offset++];
     if (derivationSize > MAX_DERIVATION_PATH) {
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
     }
     boolean result = Bip32Cache.hasPublic(buffer, offset, derivationSize);
     buffer[0] = (result ? (byte)0x01 : (byte)0x00);
     apdu.setOutgoingAndSend((short)0, (short)1);
    }
    private static void handleStorePublicKey(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     apdu.setIncomingAndReceive();
     short offset = ISO7816.OFFSET_CDATA;
     byte derivationSize = buffer[offset++];
     byte i;
     if (Crypto.keyAgreement == null) {
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
     }
     if (derivationSize > MAX_DERIVATION_PATH) {
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
     }
        Crypto.initCipher(chipKey, false);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, scratch256, (short)0);
        i = Bip32Cache.copyPrivateBest(buffer, (short)(ISO7816.OFFSET_CDATA + 1), derivationSize, scratch256, (short)0);
        for (; i<derivationSize; i++) {
         Util.arrayCopyNonAtomic(buffer, (short)(offset + 4 * i), scratch256, Bip32.OFFSET_DERIVATION_INDEX, (short)4);
         if ((proprietaryAPI == null) && ((scratch256[Bip32.OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0)) {
          if (!Bip32Cache.setPublicIndex(buffer, (short)(ISO7816.OFFSET_CDATA + 1), i)) {
           ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
          }
         }
         if (!Bip32.derive(buffer)) {
          ISOException.throwIt(ISO7816.SW_WRONG_DATA);
         }
         Bip32Cache.storePrivate(buffer, (short)(ISO7816.OFFSET_CDATA + 1), (byte)(i + 1), scratch256);
        }
        offset += (short)(derivationSize * 4);
        Crypto.random.generateData(scratch256, (short)32, (short)32);
        signTransientPrivate(scratch256, (short)0, scratch256, (short)32, scratch256, (short)64);
        if (Crypto.verifyPublic(buffer, offset, scratch256, (short)32, scratch256, (short)64)) {
         Bip32Cache.storePublic(buffer, (short)(ISO7816.OFFSET_CDATA + 1), derivationSize, buffer, offset);
        }
        else {
         ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
    }
    private static void handleGetHalfPublicKey(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     apdu.setIncomingAndReceive();
     short offset = ISO7816.OFFSET_CDATA;
     byte derivationSize = buffer[offset++];
     byte i;
     if (Crypto.keyAgreement == null) {
      ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
     }
     if (derivationSize > MAX_DERIVATION_PATH) {
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
     }
        Crypto.initCipher(chipKey, false);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, scratch256, (short)0);
        i = Bip32Cache.copyPrivateBest(buffer, (short)(ISO7816.OFFSET_CDATA + 1), derivationSize, scratch256, (short)0);
        for (; i<derivationSize; i++) {
         Util.arrayCopyNonAtomic(buffer, (short)(offset + 4 * i), scratch256, Bip32.OFFSET_DERIVATION_INDEX, (short)4);
         if ((proprietaryAPI == null) && ((scratch256[Bip32.OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0)) {
          if (!Bip32Cache.setPublicIndex(buffer, (short)(ISO7816.OFFSET_CDATA + 1), i)) {
           ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
          }
         }
         if (!Bip32.derive(buffer)) {
          ISOException.throwIt(ISO7816.SW_WRONG_DATA);
         }
         Bip32Cache.storePrivate(buffer, (short)(ISO7816.OFFSET_CDATA + 1), (byte)(i + 1), scratch256);
        }
        Crypto.initTransientPrivate(scratch256, (short)0);
        Crypto.keyAgreement.init(Crypto.transientPrivate);
        Crypto.keyAgreement.generateSecret(Secp256k1.SECP256K1_G, (short)0, (short)Secp256k1.SECP256K1_G.length, scratch256, (short)32);
        offset = 0;
        Crypto.random.generateData(buffer, (short)offset, (short)32);
        offset += 32;
        Util.arrayCopyNonAtomic(scratch256, (short)32, buffer, offset, (short)32);
        offset += 32;
        signTransientPrivate(scratch256, (short)0, buffer, (short)0, buffer, offset);
        offset += buffer[(short)(offset + 1)] + 2;
        Crypto.digestScratch.doFinal(buffer, (short)0, (short)32, buffer, (short)0);
        apdu.setOutgoingAndSend((short)0, offset);
    }
    private static void handleGetFeatures(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     buffer[0] = (byte)0;
     if (proprietaryAPI != null) {
      buffer[0] |= JC_FEATURE_HAS_PROPRIETARY_API;
     }
     apdu.setOutgoingAndSend((short)0, (short)1);
    }
    private static void handleGetWalletPublicKey(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     short offset = ISO7816.OFFSET_CDATA;
     byte derivationSize = buffer[offset++];
     byte i;
     if (derivationSize > MAX_DERIVATION_PATH) {
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
     }
        Crypto.initCipher(chipKey, false);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, scratch256, (short)0);
        i = Bip32Cache.copyPrivateBest(buffer, (short)(ISO7816.OFFSET_CDATA + 1), derivationSize, scratch256, (short)0);
        for (; i<derivationSize; i++) {
         Util.arrayCopyNonAtomic(buffer, (short)(offset + 4 * i), scratch256, Bip32.OFFSET_DERIVATION_INDEX, (short)4);
         if ((proprietaryAPI == null) && ((scratch256[Bip32.OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0)) {
          if (!Bip32Cache.setPublicIndex(buffer, (short)(ISO7816.OFFSET_CDATA + 1), i)) {
           ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
          }
         }
         if (!Bip32.derive(buffer)) {
          ISOException.throwIt(ISO7816.SW_WRONG_DATA);
         }
         Bip32Cache.storePrivate(buffer, (short)(ISO7816.OFFSET_CDATA + 1), (byte)(i + 1), scratch256);
        }
        if (proprietaryAPI == null) {
      if (!Bip32Cache.setPublicIndex(buffer, offset, derivationSize)) {
       ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
      }
        }
        offset = 0;
        buffer[offset++] = (short)65;
        if (proprietaryAPI == null) {
         Bip32Cache.copyLastPublic(buffer, offset);
        }
        else {
         proprietaryAPI.getUncompressedPublicPoint(scratch256, (short)0, buffer, offset);
        }
        Util.arrayCopyNonAtomic(scratch256, (short)32, buffer, (short)200, (short)32);
        Util.arrayCopyNonAtomic(buffer, offset, scratch256, (short)0, (short)65);
        AddressUtils.compressPublicKey(scratch256, (short)0);
        offset += (short)65;
        buffer[offset] = (byte)(publicKeyToAddress(buffer, (short)(offset + 1)) - (short)(offset + 1));
        offset += (short)(buffer[offset] + 1);
        Util.arrayCopyNonAtomic(buffer, (short)200, buffer, offset, (short)32);
        offset += 32;
        apdu.setOutgoingAndSend((short)0, offset);
    }
    private static void handleTrustedInput(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        byte p1 = buffer[ISO7816.OFFSET_P1];
        byte dataOffset = (short)0;
        apdu.setIncomingAndReceive();
        if (p1 == P1_TRUSTED_INPUT_FIRST) {
            Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, TC.ctx, TC.TX_I_TRANSACTION_TARGET_INPUT, TC.SIZEOF_U32);
            TC.ctx[TC.TX_B_TRANSACTION_STATE] = Transaction.STATE_NONE;
            TC.ctx[TC.TX_B_TRUSTED_INPUT_PROCESSED] = (byte)0x00;
            TC.ctx[TC.TX_B_HASH_OPTION] = Transaction.HASH_FULL;
            dataOffset = (short)4;
        }
        else
        if (p1 != P1_TRUSTED_INPUT_NEXT) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        short remainingData = (short)((short)(buffer[ISO7816.OFFSET_LC] & 0xff) - dataOffset);
        byte result = Transaction.parseTransaction(Transaction.PARSE_TRUSTED_INPUT, buffer, (short)(ISO7816.OFFSET_CDATA + dataOffset), remainingData);
        if (result == Transaction.RESULT_ERROR) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        else
        if (result == Transaction.RESULT_MORE) {
            return;
        }
        else
        if (result == Transaction.RESULT_FINISHED) {
            short offset = 0;
            buffer[offset++] = BLOB_MAGIC_TRUSTED_INPUT;
            Crypto.random.generateData(buffer, offset, (short)3);
            offset += 3;
            Crypto.digestFull.doFinal(scratch256, (short)0, (short)0, scratch256, (short)0);
            Crypto.digestFull.doFinal(scratch256, (short)0, (short)32, buffer, offset);
            offset += 32;
            GenericBEHelper.swap(TC.SIZEOF_U32, buffer, offset, TC.ctx, TC.TX_I_TRANSACTION_TARGET_INPUT);
            offset += 4;
            Util.arrayCopyNonAtomic(TC.ctx, TC.TX_A_TRANSACTION_AMOUNT, buffer, offset, TC.SIZEOF_AMOUNT);
            offset += TC.SIZEOF_AMOUNT;
            Crypto.initCipher(trustedInputKey, true);
            Crypto.blobEncryptDecrypt.doFinal(buffer, (short)0, offset, scratch256, (short)0);
            Util.arrayCopyNonAtomic(scratch256, (short)(offset - 8), buffer, offset, (short)8);
            offset += 8;
            apdu.setOutgoingAndSend((short)0, offset);
        }
    }
    private static void handleHashTransaction(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        byte p1 = buffer[ISO7816.OFFSET_P1];
        byte p2 = buffer[ISO7816.OFFSET_P2];
        short dataOffset = (short)0;
        apdu.setIncomingAndReceive();
        if (p1 == P1_HASH_TRANSACTION_FIRST) {
            TC.clear();
            TC.ctx[TC.TX_B_TRANSACTION_STATE] = Transaction.STATE_NONE;
            TC.ctx[TC.TX_B_HASH_OPTION] = Transaction.HASH_BOTH;
            TC.ctx[TC.TX_Z_CHANGE_ACCEPTED] = (byte)0x01;
        }
        else
        if (p1 != P1_HASH_TRANSACTION_NEXT) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        if (p2 == P2_HASH_TRANSACTION_NEW_INPUT) {
            if (p1 == P1_HASH_TRANSACTION_FIRST) {
                checkAccess(true);
                TC.ctxP[TC.P_TX_Z_WIRED] = (isContactless() ? TC.FALSE : TC.TRUE);
                TC.ctxP[TC.P_TX_Z_FIRST_SIGNED] = TC.TRUE;
                TC.ctxP[TC.P_TX_Z_RELAXED] = TC.FALSE;
                TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] = TC.FALSE;
                TC.ctxP[TC.P_TX_Z_USE_KEYCARD] = TC.FALSE;
                Crypto.random.generateData(TC.ctxP, TC.P_TX_A_NONCE, TC.SIZEOF_NONCE);
            }
        }
        else
        if (p2 != P2_HASH_TRANSACTION_CONTINUE_INPUT) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        checkInterfaceConsistency();
        short remainingData = (short)((short)(buffer[ISO7816.OFFSET_LC] & 0xff) - dataOffset);
        byte result = Transaction.parseTransaction(Transaction.PARSE_SIGNATURE, buffer, (short)(ISO7816.OFFSET_CDATA + dataOffset), remainingData);
        if (result == Transaction.RESULT_ERROR) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        else
        if (result == Transaction.RESULT_MORE) {
            return;
        }
        else
        if (result == Transaction.RESULT_FINISHED) {
            return;
        }
    }
    private static short writeAmount(short textOffset, short amountOffset, byte[] addressBuffer, short addressOffset) {
        textOffset = BCDUtils.hexAmountToDisplayable(TC.ctx, amountOffset, LWNFCForumApplet.FILE_DATA, textOffset);
        LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
        textOffset = Util.arrayCopyNonAtomic(TEXT_BTC, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_BTC.length);
        LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
        textOffset = Util.arrayCopyNonAtomic(TEXT_TO, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_TO.length);
        Util.arrayCopyNonAtomic(addressBuffer, addressOffset, scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1));
        Crypto.digestScratch.doFinal(scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1), scratch256, (short)(TC.SIZEOF_RIPEMD + 1));
        Crypto.digestScratch.doFinal(scratch256, (short)(TC.SIZEOF_RIPEMD + 1), TC.SIZEOF_SHA256, scratch256, (short)(TC.SIZEOF_RIPEMD + 1));
        textOffset = Base58.encode(scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1 + 4), LWNFCForumApplet.FILE_DATA, textOffset, scratch256, (short)100);
        return textOffset;
    }
    private static void handleHashOutputFullChange(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = (short)(ISO7816.OFFSET_CDATA);
        apdu.setIncomingAndReceive();
        checkInterfaceConsistency();
        if (TC.ctx[TC.TX_B_TRANSACTION_STATE] != Transaction.STATE_PRESIGN_READY) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        byte i;
        byte addressLength = buffer[offset];
        if (TC.ctx[TC.TX_Z_CHANGE_ACCEPTED] != (byte)0x01) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (addressLength > MAX_DERIVATION_PATH) {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }
        offset++;
        Crypto.initCipher(chipKey, false);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, scratch256, (short)0);
        i = Bip32Cache.copyPrivateBest(buffer, offset, addressLength, scratch256, (short)0);
        for (; i<addressLength; i++) {
            Util.arrayCopyNonAtomic(buffer, (short)(offset + 4 * i), scratch256, Bip32.OFFSET_DERIVATION_INDEX, (short)4);
            if ((proprietaryAPI == null) && ((scratch256[Bip32.OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0)) {
                if (!Bip32Cache.setPublicIndex(buffer, offset, i)) {
                    ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
                }
            }
            if (!Bip32.derive(buffer)) {
                ISOException.throwIt(ISO7816.SW_WRONG_DATA);
            }
            Bip32Cache.storePrivate(buffer, offset, (byte)(i + 1), scratch256);
        }
        if (proprietaryAPI == null) {
            if (!Bip32Cache.setPublicIndex(buffer, offset, addressLength)) {
                ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
            }
            Bip32Cache.copyLastPublic(scratch256, (short)0);
        }
        else {
            proprietaryAPI.getUncompressedPublicPoint(scratch256, (short)0, scratch256, (short)0);
        }
        AddressUtils.compressPublicKey(scratch256, (short)0);
        Crypto.digestScratch.doFinal(scratch256, (short)0, (short)33, scratch256, (short)0);
        if (Crypto.digestRipemd != null) {
            Crypto.digestRipemd.doFinal(scratch256, (short)0, (short)32, TC.ctx, (short)(TC.TX_A_CHANGE_ADDRESS + 1));
        }
        else {
            Ripemd160.hash32(scratch256, (short)0, TC.ctx, (short)(TC.TX_A_CHANGE_ADDRESS + 1), scratch256, (short)33);
        }
        TC.ctx[TC.TX_Z_CHANGE_ACCEPTED] = (byte)0x00;
        TC.ctx[TC.TX_Z_CHANGE_INITIALIZED] = (byte)0x01;
    }
    private static void handleHashOutputFull(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = (short)(ISO7816.OFFSET_CDATA);
        apdu.setIncomingAndReceive();
        checkInterfaceConsistency();
        if (TC.ctx[TC.TX_B_TRANSACTION_STATE] != Transaction.STATE_PRESIGN_READY) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        Crypto.digestFull.update(buffer, offset, (short)(buffer[ISO7816.OFFSET_LC] & 0xff));
        Crypto.digestAuthorization.update(buffer, offset, (short)(buffer[ISO7816.OFFSET_LC] & 0xff));
        if (buffer[ISO7816.OFFSET_P1] == P1_FINALIZE_MORE) {
            if ((currentMode == MODE_WALLET) && (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] == TC.FALSE)) {
                ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
            }
            TC.ctx[TC.TX_Z_MULTIPLE_OUTPUT] = (byte)0x01;
            buffer[0] = (byte)0x00;
            apdu.setOutgoingAndSend((short)0, (short)1);
            return;
        }
        else
        if (buffer[ISO7816.OFFSET_P1] != P1_FINALIZE_LAST) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        if (TC.ctxP[TC.P_TX_Z_FIRST_SIGNED] == TC.TRUE) {
            TC.ctxP[TC.P_TX_Z_FIRST_SIGNED] = TC.FALSE;
            if ((currentMode == MODE_WALLET) && (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] == TC.FALSE)) {
                byte numOutputs;
                byte i;
                short addressOffset = (short)0;
                byte tmpVersion = (byte)0;
                boolean changeFilled = false;
                boolean regularFilled = false;
                numOutputs = buffer[offset++];
                if (numOutputs > 3) {
                    ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                }
                for (i=0; i<numOutputs; i++) {
                    offset += (short)8;
                    if ((Util.arrayCompare(buffer, offset, TRANSACTION_OUTPUT_SCRIPT_PRE, (short)0, (short)TRANSACTION_OUTPUT_SCRIPT_PRE.length) == (byte)0) &&
                        (Util.arrayCompare(buffer, (short)(offset + TC.SIZEOF_RIPEMD + (short)TRANSACTION_OUTPUT_SCRIPT_PRE.length), TRANSACTION_OUTPUT_SCRIPT_POST, (short)0, (short)TRANSACTION_OUTPUT_SCRIPT_POST.length) == (byte)0)) {
                        tmpVersion = stdVersion;
                        addressOffset = (short)(offset + (short)TRANSACTION_OUTPUT_SCRIPT_PRE.length);
                    }
                    else
                    if ((Util.arrayCompare(buffer, offset, TRANSACTION_OUTPUT_SCRIPT_P2SH_PRE, (short)0, (short)TRANSACTION_OUTPUT_SCRIPT_P2SH_PRE.length) == (byte)0) &&
                        (Util.arrayCompare(buffer, (short)(offset + TC.SIZEOF_RIPEMD + (short)TRANSACTION_OUTPUT_SCRIPT_P2SH_PRE.length), TRANSACTION_OUTPUT_SCRIPT_P2SH_POST, (short)0, (short)TRANSACTION_OUTPUT_SCRIPT_P2SH_POST.length) == (byte)0)) {
                        tmpVersion = p2shVersion;
                        addressOffset = (short)(offset + (short)TRANSACTION_OUTPUT_SCRIPT_P2SH_PRE.length);
                    }
                    else
                    if (buffer[(short)(offset + 1)] == OP_RETURN) {
                        if (!Uint64Helper.isEqualByte(buffer, (short)(offset - 8), (byte)0)) {
                            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                        }
                        offset += (short)((short)(buffer[offset] & 0xff) + (short)1);
                        continue;
                    }
                    else {
                        ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                    }
                    if ((TC.ctx[TC.TX_Z_CHANGE_INITIALIZED] == (byte)0x01) &&
                        (Util.arrayCompare(buffer, addressOffset, TC.ctx, (short)(TC.TX_A_CHANGE_ADDRESS + 1), TC.SIZEOF_RIPEMD) == (byte)0)) {
                        if (changeFilled) {
                            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                        }
                        TC.ctx[TC.TX_A_CHANGE_ADDRESS] = tmpVersion;
                        Util.arrayCopyNonAtomic(buffer, addressOffset, TC.ctx, (short)(TC.TX_A_CHANGE_ADDRESS + 1), TC.SIZEOF_RIPEMD);
                        Uint64Helper.swap(TC.ctx, TC.TX_A_CHANGE_AMOUNT, buffer, (short)(offset - 8));
                        changeFilled = true;
                        TC.ctx[TC.TX_Z_CHANGE_CHECKED] = (byte)0x01;
                    }
                    else {
                        if (regularFilled && changeFilled) {
                            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                        }
                        if (!regularFilled) {
                            TC.ctxP[TC.P_TX_A_OUTPUT_ADDRESS] = tmpVersion;
                            Util.arrayCopyNonAtomic(buffer, addressOffset, TC.ctxP, (short)(TC.P_TX_A_OUTPUT_ADDRESS + 1), TC.SIZEOF_RIPEMD);
                            Uint64Helper.swap(TC.ctx, TC.TX_A_OUTPUT_AMOUNT, buffer, (short)(offset - 8));
                            regularFilled = true;
                        }
                        else {
                            TC.ctx[TC.TX_A_CHANGE_ADDRESS] = tmpVersion;
                            Util.arrayCopyNonAtomic(buffer, addressOffset, TC.ctx, (short)(TC.TX_A_CHANGE_ADDRESS + 1), TC.SIZEOF_RIPEMD);
                            Uint64Helper.swap(TC.ctx, TC.TX_A_CHANGE_AMOUNT, buffer, (short)(offset - 8));
                            changeFilled = true;
                        }
                    }
                    offset += (short)((short)(buffer[offset] & 0xff) + (short)1);
                }
                if (changeFilled && (TC.ctx[TC.TX_Z_CHANGE_CHECKED] == (byte)0x00)) {
                    ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                }
                Uint64Helper.add(scratch256, (short)240, TC.ctx, TC.TX_A_OUTPUT_AMOUNT, TC.ctx, TC.TX_A_CHANGE_AMOUNT);
                Uint64Helper.sub(TC.ctx, TC.TX_A_FEE_AMOUNT, TC.ctx, TC.TX_A_TRANSACTION_AMOUNT, scratch256, (short)240);
                if (Keycard.isInitialized()) {
                    Util.arrayCopyNonAtomic(TC.ctxP, TC.P_TX_A_OUTPUT_ADDRESS, scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1));
                    Crypto.digestScratch.doFinal(scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1), scratch256, (short)(TC.SIZEOF_RIPEMD + 1));
                    Crypto.digestScratch.doFinal(scratch256, (short)(TC.SIZEOF_RIPEMD + 1), TC.SIZEOF_SHA256, scratch256, (short)(TC.SIZEOF_RIPEMD + 1));
                    addressOffset = Base58.encode(scratch256, (short)0, (short)(TC.SIZEOF_RIPEMD + 1 + 4), scratch256, (short)30, scratch256, (short)100);
                    Keycard.generateIndexes(TC.ctxP, TC.P_TX_A_KEYCARD_INDEXES, (byte)(addressOffset - (short)30));
                    TC.ctxP[TC.P_TX_Z_USE_KEYCARD] = TC.TRUE;
                }
                else {
                    if (TC.ctxP[TC.P_TX_Z_WIRED] == TC.FALSE) {
                        ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
                    }
                }
                if (TC.ctxP[TC.P_TX_Z_WIRED] == TC.TRUE) {
                    short textOffset = LWNFCForumApplet.OFFSET_TEXT;
                    textOffset = Util.arrayCopyNonAtomic(TEXT_CONFIRM, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_CONFIRM.length);
                    textOffset = writeAmount(textOffset, TC.TX_A_OUTPUT_AMOUNT, TC.ctxP, TC.P_TX_A_OUTPUT_ADDRESS);
                    LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
                    textOffset = Util.arrayCopyNonAtomic(TEXT_FEES, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_FEES.length);
                    textOffset = BCDUtils.hexAmountToDisplayable(TC.ctx, TC.TX_A_FEE_AMOUNT, LWNFCForumApplet.FILE_DATA, textOffset);
                    LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
                    textOffset = Util.arrayCopyNonAtomic(TEXT_BTC, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_BTC.length);
                    LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_COMMA;
                    if (changeFilled) {
                        textOffset = Util.arrayCopyNonAtomic(TEXT_NO_CHANGE, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_NO_CHANGE.length);
                    }
                    else {
                        textOffset = Util.arrayCopyNonAtomic(TEXT_CHANGE, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_CHANGE.length);
                        LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
                        textOffset = writeAmount(textOffset, TC.TX_A_CHANGE_AMOUNT, TC.ctx, TC.TX_A_CHANGE_ADDRESS);
                    }
                    LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_CLOSE_P;
                    LWNFCForumApplet.FILE_DATA[textOffset++] = TEXT_SPACE;
                    textOffset = Util.arrayCopyNonAtomic(TEXT_PIN, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_PIN.length);
                    for (i=0; i<TRANSACTION_PIN_SIZE; i++) {
                        scratch256[i] = (byte)(Crypto.getRandomByteModulo((byte)10));
                        scratch256[i] += (byte)'0';
                    }
                    transactionPin.resetAndUnblock();
                    transactionPin.update(scratch256, (short)0, TRANSACTION_PIN_SIZE);
                    textOffset = Util.arrayCopyNonAtomic(scratch256, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, TRANSACTION_PIN_SIZE);
                    LWNFCForumApplet.writeHeader((short)(textOffset - LWNFCForumApplet.OFFSET_TEXT));
                }
            }
            Crypto.digestAuthorization.doFinal(TC.ctxP, TC.P_TX_A_NONCE, TC.SIZEOF_NONCE, TC.ctxP, TC.P_TX_A_AUTHORIZATION_HASH);
        }
        else {
            Crypto.digestAuthorization.doFinal(TC.ctxP, TC.P_TX_A_NONCE, TC.SIZEOF_NONCE, scratch256, (short)0);
            if (Util.arrayCompare(scratch256, (short)0, TC.ctxP, TC.P_TX_A_AUTHORIZATION_HASH, TC.SIZEOF_SHA256) != 0) {
                ISOException.throwIt(ISO7816.SW_WRONG_DATA);
            }
        }
        short outOffset = (short)0;
        buffer[outOffset++] = (byte)0x00;
        if (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] == TC.TRUE) {
            buffer[outOffset++] = AUTHORIZATION_NONE;
        }
        else
        if (TC.ctxP[TC.P_TX_Z_WIRED] == TC.TRUE) {
            buffer[outOffset++] = AUTHORIZATION_NFC_KEYCARD;
        }
        else {
            buffer[outOffset++] = AUTHORIZATION_KEYCARD;
        }
        if (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] == TC.FALSE) {
            if (Keycard.isInitialized()) {
                buffer[outOffset++] = Keycard.issuerKeycardSize;
                Util.arrayCopyNonAtomic(TC.ctxP, TC.P_TX_A_KEYCARD_INDEXES, buffer, outOffset, Keycard.issuerKeycardSize);
                outOffset += Keycard.issuerKeycardSize;
            }
            else {
                buffer[outOffset++] = (byte)0x00;
            }
        }
        TC.ctx[TC.TX_B_TRANSACTION_STATE] = Transaction.STATE_SIGN_READY;
        apdu.setOutgoingAndSend((short)0, outOffset);
    }
    private static void handleHashSignDerive(APDU apdu, boolean checkStage) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        byte i;
        apdu.setIncomingAndReceive();
        if (checkStage) {
            checkInterfaceConsistency();
            if (TC.ctx[TC.TX_B_TRANSACTION_STATE] != Transaction.STATE_SIGN_READY) {
                ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
            }
        }
        byte derivationSize = buffer[offset++];
        if (derivationSize > MAX_DERIVATION_PATH) {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }
        Crypto.initCipher(chipKey, false);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, scratch256, (short)0);
        i = Bip32Cache.copyPrivateBest(buffer, (short)(ISO7816.OFFSET_CDATA + 1), derivationSize, scratch256, (short)0);
        offset += (short)(i * 4);
        for (; i<derivationSize; i++) {
            Util.arrayCopyNonAtomic(buffer, offset, scratch256, Bip32.OFFSET_DERIVATION_INDEX, (short)4);
            if ((proprietaryAPI == null) && ((scratch256[Bip32.OFFSET_DERIVATION_INDEX] & (byte)0x80) == 0)) {
                if (!Bip32Cache.setPublicIndex(buffer, (short)(ISO7816.OFFSET_CDATA + 1), i)) {
                    ISOException.throwIt(SW_PUBLIC_POINT_NOT_AVAILABLE);
                }
            }
            if (!Bip32.derive(buffer)) {
                ISOException.throwIt(ISO7816.SW_WRONG_DATA);
            }
            Bip32Cache.storePrivate(buffer, (short)(ISO7816.OFFSET_CDATA + 1), (byte)(i + 1), scratch256);
            offset += (short)4;
        }
    }
    private static void handleHashSign(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        byte i;
     byte derivationSize = buffer[offset++];
        offset += (short)(derivationSize * 4);
        short authorizationLength = (short)(buffer[offset++] & 0xff);
        if (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] == TC.FALSE) {
            boolean verified = false;
            writeIdleText();
            if (TC.ctxP[TC.P_TX_Z_USE_KEYCARD] == TC.TRUE) {
                Util.arrayCopyNonAtomic(TC.ctxP, TC.P_TX_A_OUTPUT_ADDRESS, scratch256, (short)32, (short)(TC.SIZEOF_RIPEMD + 1));
                Crypto.digestScratch.doFinal(scratch256, (short)32, (short)(TC.SIZEOF_RIPEMD + 1), scratch256, (short)(32 + TC.SIZEOF_RIPEMD + 1));
                Crypto.digestScratch.doFinal(scratch256, (short)(32 + TC.SIZEOF_RIPEMD + 1), TC.SIZEOF_SHA256, scratch256, (short)(32 + TC.SIZEOF_RIPEMD + 1));
                short addressOffset = Base58.encode(scratch256, (short)32, (short)(TC.SIZEOF_RIPEMD + 1 + 4), scratch256, (short)100, scratch256, (short)150);
                verified = Keycard.check(scratch256, (short)100, (byte)(addressOffset - 100),
                    buffer, offset, (byte)authorizationLength,
                    TC.ctxP, TC.P_TX_A_KEYCARD_INDEXES,
                    scratch256, (short)150);
            }
            if (!verified) {
                if (!transactionPin.check(buffer, offset, (byte)authorizationLength)) {
                    ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                }
            }
        }
        else
        if (TC.ctxP[TC.P_TX_Z_CONSUME_P2SH] != TC.TRUE) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        offset += authorizationLength;
        Uint32Helper.swap(scratch256, (short)100, buffer, offset);
        offset += 4;
        byte sigHashType = buffer[offset++];
        Uint32Helper.clear(scratch256, (short)104);
        scratch256[(short)104] = sigHashType;
        Crypto.digestFull.doFinal(scratch256, (short)100, (short)8, scratch256, (short)100);
        signTransientPrivate(scratch256, (short)0, scratch256, (short)100, buffer, (short)0);
        short signatureSize = (short)((short)(buffer[1] & 0xff) + 2);
        buffer[signatureSize] = sigHashType;
        TC.clear();
        apdu.setOutgoingAndSend((short)0, (short)(signatureSize + 1));
    }
    private static void handleSignMessage(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        if (buffer[ISO7816.OFFSET_P1] == P1_PREPARE_MESSAGE) {
            byte derivationSize = buffer[offset++];
            boolean addressVerified = false;
            if (Util.arrayCompare(buffer, offset, SLIP13_HEAD, (short)0, (short)SLIP13_HEAD.length) == (short)0) {
                addressVerified = true;
            }
            else {
                for (byte i=0; i<derivationSize; i++) {
                    if ((Util.arrayCompare(buffer, (short)(offset + 2), BITID_DERIVE, (short)0, (short)BITID_DERIVE.length) == (short)0) ||
                        (Util.arrayCompare(buffer, (short)(offset + 2), BITID_DERIVE_MULTIPLE, (short)0, (short)BITID_DERIVE_MULTIPLE.length) == (short)0)) {
                        addressVerified = true;
                        break;
                    }
                    offset += 4;
                }
            }
            if (!addressVerified) {
                ISOException.throwIt(ISO7816.SW_WRONG_DATA);
            }
            offset = (short)(ISO7816.OFFSET_CDATA + 1 + 4 * derivationSize);
            short messageLength = (short)(buffer[offset++] & 0xff);
            Crypto.digestFull.reset();
            Crypto.digestFull.update(SIGNMAGIC, (short)0, (short)SIGNMAGIC.length);
            scratch256[(short)100] = (byte)messageLength;
            Crypto.digestFull.update(scratch256, (short)100, (short)1);
            Crypto.digestFull.doFinal(buffer, offset, messageLength, scratch256, (short)32);
            signTransientPrivate(scratch256, (short)0, scratch256, (short)32, scratch256, (short)100);
            Util.arrayFillNonAtomic(scratch256, (short)0, (short)64, (byte)0x00);
            buffer[(short)0] = (byte)0x00;
            TC.ctx[TC.TX_B_MESSAGE_SIGN_READY] = (byte)0x01;
            apdu.setOutgoingAndSend((short)0, (short)1);
        }
        else
        if (buffer[ISO7816.OFFSET_P1] == P1_SIGN_MESSAGE) {
            if (TC.ctx[TC.TX_B_MESSAGE_SIGN_READY] != (byte)0x01) {
                ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
            }
            TC.ctx[TC.TX_B_MESSAGE_SIGN_READY] = (byte)0x00;
            short signatureSize = (short)((short)(scratch256[(short)101] & 0xff) + 2);
            Util.arrayCopyNonAtomic(scratch256, (short)100, buffer, (short)0, signatureSize);
            apdu.setOutgoingAndSend((short)0, signatureSize);
        }
        else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
    }
    private static void handleSetUserKeycard(APDU apdu, boolean airgap) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        apdu.setIncomingAndReceive();
        if ((setup == TC.FALSE) || (setup != TC.TRUE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (Keycard.issuerKeycardSize == (byte)0) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        if (buffer[ISO7816.OFFSET_P1] == P1_SET_KEYCARD) {
            if (buffer[ISO7816.OFFSET_LC] != (byte)(KEYCARD_KEY_LENGTH + 1)) {
                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            }
            Keycard.setPairingData(buffer, ISO7816.OFFSET_CDATA);
            Keycard.generateRandomIndexes(Keycard.challenge, (short)0, KEYCARD_CHALLENGE_LENGTH);
            buffer[0] = CONFIRM_PREVIOUS_KEYCARD;
            Util.arrayCopyNonAtomic(Keycard.challenge, (short)0, buffer, (short)1, KEYCARD_CHALLENGE_LENGTH);
            apdu.setOutgoingAndSend((short)0, (short)(KEYCARD_CHALLENGE_LENGTH + 1));
        }
        else
        if (buffer[ISO7816.OFFSET_P1] == P1_CONFIRM_KEYCARD) {
            if (buffer[ISO7816.OFFSET_LC] != KEYCARD_CHALLENGE_LENGTH) {
                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            }
            if (!Keycard.check(null, (short)0, (byte)0,
                   buffer, ISO7816.OFFSET_CDATA, (byte)4,
                   Keycard.challenge, (short)0,
                   scratch256, (short)150)) {
                Keycard.clearPairingData();
                ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
            }
            else {
                Keycard.getPairingData(scratch256, (short)0);
                if (!airgap) {
                    Keycard.setUser(scratch256[0], scratch256, (short)1);
                }
                else {
                    Crypto.initCipherAES(pairingKey, false);
                    Crypto.blobEncryptDecryptAES.doFinal(scratch256, (short)1, (short)16, scratch256, (short)100);
                    Keycard.setUser(scratch256[0], scratch256, (short)100);
                }
            }
        }
        else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
    }
    private static void handleSetup(APDU apdu, boolean airgap) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        byte keyLength;
        apdu.setIncomingAndReceive();
        if ((setup == TC.TRUE) || (setup != TC.FALSE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (buffer[ISO7816.OFFSET_P1] != P1_REGULAR_SETUP) {
         ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
        Bip32Cache.reset();
        supportedModes = buffer[offset++];
        for (byte i=0; i<(byte)AVAILABLE_MODES.length; i++) {
         if ((supportedModes & AVAILABLE_MODES[i]) != 0) {
          currentMode = AVAILABLE_MODES[i];
          break;
         }
        }
        features = buffer[offset++];
        if ((features & FEATURE_UNCOMPRESSED_KEYS) != 0) {
         ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }
        stdVersion = buffer[offset++];
        p2shVersion = buffer[offset++];
        walletPinSize = buffer[offset++];
        if (walletPinSize < 4) {
         ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        }
        Util.arrayFillNonAtomic(scratch256, (short)0, WALLET_PIN_SIZE, (byte)0xff);
        Util.arrayCopyNonAtomic(buffer, offset, scratch256, (short)0, walletPinSize);
        walletPin.update(scratch256, (short)0, WALLET_PIN_SIZE);
        walletPin.resetAndUnblock();
        offset += walletPinSize;
        secondaryPinSize = buffer[offset++];
        if (secondaryPinSize != 0) {
            Util.arrayFillNonAtomic(scratch256, (short)0, SECONDARY_PIN_SIZE, (byte)0xff);
            Util.arrayCopyNonAtomic(buffer, offset, scratch256, (short)0, secondaryPinSize);
            secondaryPin.update(scratch256, (short)0, SECONDARY_PIN_SIZE);
            secondaryPin.resetAndUnblock();
            offset += secondaryPinSize;
        }
        keyLength = buffer[offset++];
        if (keyLength == 0) {
         keyLength = DEFAULT_SEED_LENGTH;
         Crypto.random.generateData(scratch256, (short)0, keyLength);
            if (airgap) {
                Util.arrayCopyNonAtomic(scratch256, (short)0, scratch256, (short)(256 - DEFAULT_SEED_LENGTH), DEFAULT_SEED_LENGTH);
            }
         short textOffset = LWNFCForumApplet.OFFSET_TEXT;
         textOffset = Util.arrayCopyNonAtomic(TEXT_SEED, (short)0, LWNFCForumApplet.FILE_DATA, textOffset, (short)TEXT_SEED.length);
         for (byte i=0; i<DEFAULT_SEED_LENGTH; i++) {
          LWNFCForumApplet.FILE_DATA[textOffset++] = HEX[(scratch256[i] >> 4) & 0x0f];
          LWNFCForumApplet.FILE_DATA[textOffset++] = HEX[scratch256[i] & 0x0f];
         }
         LWNFCForumApplet.writeHeader((short)(textOffset - LWNFCForumApplet.OFFSET_TEXT));
         LWNFCForumApplet.erase = true;
        }
        else {
         if ((keyLength < 0) || (keyLength > DEFAULT_SEED_LENGTH)) {
          ISOException.throwIt(ISO7816.SW_DATA_INVALID);
         }
            if (airgap) {
                Crypto.initCipherAES(pairingKey, false);
                Crypto.blobEncryptDecryptAES.doFinal(buffer, offset, keyLength, scratch256, (short)0);
            }
            else {
            Util.arrayCopyNonAtomic(buffer, offset, scratch256, (short)0, keyLength);
            }
        }
        Bip32.deriveSeed(keyLength);
        Crypto.initCipher(chipKey, true);
        Crypto.blobEncryptDecrypt.doFinal(masterDerived, (short)0, (short)DEFAULT_SEED_LENGTH, masterDerived, (short)0);
        offset += keyLength;
        if ((supportedModes & MODE_DEVELOPER) != 0) {
         keyLength = buffer[offset++];
         if (keyLength == 0) {
                Crypto.random.generateData(scratch256, (short)0, (short)16);
                developerKey.setKey(scratch256, (short)0);
         }
         else {
             if (keyLength != 16) {
              ISOException.throwIt(ISO7816.SW_DATA_INVALID);
             }
             developerKey.setKey(buffer, offset);
         }
        }
        Crypto.random.generateData(scratch256, (short)0, (short)16);
        trustedInputKey.setKey(scratch256, (short)0);
        offset = 0;
        buffer[offset++] = (airgap ? SEED_ENCODED_AIRGAP : SEED_NOT_TYPED);
        if ((supportedModes & MODE_DEVELOPER) != 0) {
         trustedInputKey.getKey(buffer, offset);
         offset += (short)16;
         developerKey.getKey(buffer, offset);
         offset += (short)16;
        }
        if (airgap) {
            Crypto.initCipherAES(pairingKey, true);
            Crypto.blobEncryptDecryptAES.doFinal(scratch256, (short)(256 - DEFAULT_SEED_LENGTH), DEFAULT_SEED_LENGTH, buffer, offset);
            offset += DEFAULT_SEED_LENGTH;
        }
        apdu.setOutgoingAndSend((short)0, offset);
        setup = TC.TRUE;
    }
    private static void handleVerifyPin(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        if ((setup == TC.FALSE) || (setup != TC.TRUE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (buffer[ISO7816.OFFSET_P1] == P1_GET_REMAINING_ATTEMPTS) {
         buffer[0] = walletPin.getTriesRemaining();
         apdu.setOutgoingAndSend((short)0, (short)1);
         return;
        }
        apdu.setIncomingAndReceive();
        if (buffer[ISO7816.OFFSET_LC] != walletPinSize) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        Util.arrayFillNonAtomic(scratch256, (short)0, WALLET_PIN_SIZE, (byte)0xff);
        Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, scratch256, (short)0, walletPinSize);
        if (!walletPin.check(scratch256, (short)0, WALLET_PIN_SIZE)) {
            if (walletPin.getTriesRemaining() == 0) {
                reset();
            }
            ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
        }
    }
    private static void handleGetContactlessLimit(APDU apdu) throws ISOException {
        if ((setup == TC.FALSE) || (setup != TC.TRUE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        Util.arrayCopyNonAtomic(limits, (short)0, scratch256, (short)0, LIMIT_LAST);
        apdu.setOutgoingAndSend((short)0, LIMIT_LAST);
    }
    private static void handleSetContactlessLimit(APDU apdu) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        apdu.setIncomingAndReceive();
        if (isContactless()) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (buffer[ISO7816.OFFSET_LC] != LIMIT_LAST) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        Util.arrayCopy(buffer, ISO7816.OFFSET_CDATA, limits, (short)0, LIMIT_LAST);
        if (limitsSet != TC.TRUE) {
            limitsSet = TC.TRUE;
        }
    }
    private static void handleGetFirmwareVersion(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     Util.arrayCopyNonAtomic(FIRMWARE_VERSION, (short)0, buffer, (short)0, (short)FIRMWARE_VERSION.length);
     apdu.setOutgoingAndSend((short)0, (short)FIRMWARE_VERSION.length);
    }
    private static void handleGetOperationMode(APDU apdu) throws ISOException {
     byte[] buffer = apdu.getBuffer();
     if (buffer[ISO7816.OFFSET_P1] == P1_GET_OPERATION_MODE) {
      buffer[0] = currentMode;
     }
     else
     if (buffer[ISO7816.OFFSET_P1] == P1_GET_OPERATION_MODE_2FA) {
      buffer[0] = SFA_NFC;
     }
     apdu.setOutgoingAndSend((short)0, (short)1);
    }
    private static void handleAdmSetKeycardSeed(APDU apdu, boolean airgap) throws ISOException {
        byte[] buffer = apdu.getBuffer();
        short offset = ISO7816.OFFSET_CDATA;
        byte keyLength;
        apdu.setIncomingAndReceive();
        if ((setup == TC.TRUE) || (setup != TC.FALSE)) {
            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
        }
        if (buffer[ISO7816.OFFSET_LC] != (byte)(KEYCARD_KEY_LENGTH + 1)) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        if ((buffer[offset] == (byte)0) || (buffer[offset] > TC.MAX_KEYCARD_DIGIT_ADDRESS)) {
            ISOException.throwIt(ISO7816.SW_WRONG_DATA);
        }
        if (!airgap) {
            Keycard.setIssuer(buffer[offset], buffer, (short)(offset + 1));
        }
        else {
            Crypto.initCipherAES(pairingKey, false);
            Crypto.blobEncryptDecryptAES.doFinal(buffer, (short)(offset + 1), (short)16, scratch256, (short)0);
            Keycard.setIssuer(buffer[offset], scratch256, (short)0);
        }
    }
    public static void clearScratch() {
        if (TC.ctx[TC.TX_B_MESSAGE_SIGN_READY] != (byte)0x01) {
            Util.arrayFillNonAtomic(scratch256, (short)0, (short)scratch256.length, (byte)0x00);
        }
    }
    public void process(APDU apdu) throws ISOException {
        if (selectingApplet()) {
         if (LWNFCForumApplet.erase) {
          writeIdleText();
          LWNFCForumApplet.erase = false;
         }
            return;
        }
        byte[] buffer = apdu.getBuffer();
        if (buffer[ISO7816.OFFSET_CLA] == CLA_BTC) {
            clearScratch();
        if (isContactless()) {
        apdu.waitExtension();
        }
            try {
                switch(buffer[ISO7816.OFFSET_INS]) {
                    case INS_SETUP:
                        handleSetup(apdu, false);
                        break;
                    case INS_SET_USER_KEYCARD:
                        handleSetUserKeycard(apdu, false);
                        break;
                    case INS_VERIFY_PIN:
                     handleVerifyPin(apdu);
                     break;
                    case INS_GET_WALLET_PUBLIC_KEY:
                     checkAccess(true);
                     handleGetWalletPublicKey(apdu);
                     break;
                    case INS_GET_CONTACTLESS_LIMIT:
                        handleGetContactlessLimit(apdu);
                        break;
                    case INS_SET_CONTACTLESS_LIMIT:
                        checkAccess(true);
                        handleSetContactlessLimit(apdu);
                        break;
                    case INS_GET_TRUSTED_INPUT:
                        checkAccess(false);
                        handleTrustedInput(apdu);
                        break;
                    case INS_UNTRUSTED_HASH_START:
                        handleHashTransaction(apdu);
                        break;
                    case INS_UNTRUSTED_HASH_FINALIZE_FULL:
                        if (buffer[ISO7816.OFFSET_P1] == P1_FINALIZE_CHANGEINFO) {
                            handleHashOutputFullChange(apdu);
                        }
                        else {
                            handleHashOutputFull(apdu);
                        }
                        break;
                    case INS_UNTRUSTED_HASH_SIGN:
                        handleHashSignDerive(apdu, true);
                        handleHashSign(apdu);
                        break;
                    case INS_SIGN_MESSAGE:
                        if (buffer[ISO7816.OFFSET_P1] == P1_PREPARE_MESSAGE) {
                            handleHashSignDerive(apdu, false);
                        }
                        handleSignMessage(apdu);
                        break;
                    case INS_GET_FIRMWARE_VERSION:
                     handleGetFirmwareVersion(apdu);
                        break;
                    case INS_GET_OPERATION_MODE:
                     handleGetOperationMode(apdu);
                     break;
                    default:
                        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                }
            }
            catch(Exception e) {
                TC.clear();
                if (e instanceof CardRuntimeException) {
                    throw ((CardRuntimeException)e);
                }
                else {
                    ISOException.throwIt(ISO7816.SW_UNKNOWN);
                }
            }
            finally {
                clearScratch();
            }
            return;
        }
        else
        if (buffer[ISO7816.OFFSET_CLA] == CLA_BTC_ADMIN) {
            clearScratch();
            try {
                switch(buffer[ISO7816.OFFSET_INS]) {
                    case INS_ADM_SET_KEYCARD_SEED:
                        handleAdmSetKeycardSeed(apdu, false);
                        break;
                    default:
                        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                }
            }
            catch(Exception e) {
                TC.clear();
                if (e instanceof CardRuntimeException) {
                    throw ((CardRuntimeException)e);
                }
                else {
                    ISOException.throwIt(ISO7816.SW_UNKNOWN);
                }
            }
            finally {
                clearScratch();
            }
            return;
        }
        else
        if (buffer[ISO7816.OFFSET_CLA] == CLA_BTC_JC_EXTENSIONS) {
         clearScratch();
         try {
          switch(buffer[ISO7816.OFFSET_INS]) {
           case INS_EXT_GET_HALF_PUBLIC_KEY:
            if (TC.ctx[TC.TX_B_TRANSACTION_STATE] != Transaction.STATE_SIGN_READY) {
             checkAccess(true);
            }
            handleGetHalfPublicKey(apdu);
            break;
           case INS_EXT_PUT_PUBLIC_KEY_CACHE:
            handleStorePublicKey(apdu);
            break;
           case INS_EXT_HAS_PUBLIC_KEY_CACHE:
            handleHasCachedPublicKey(apdu);
            break;
           case INS_EXT_GET_FEATURES:
            handleGetFeatures(apdu);
            break;
                    case INS_EXT_AIRGAP_SET_ATTESTATION_PUBLIC:
                        handleSetAttestationPublic(apdu);
                        break;
                    case INS_EXT_AIRGAP_KEY_AGREEMENT:
                        handleAirgapKeyAgreement(apdu);
                        break;
                    case INS_EXT_AIRGAP_SETUP:
                        if (!pairingDone) {
                            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
                        }
                        handleSetup(apdu, true);
                        break;
                    case INS_EXT_AIRGAP_INITIALIZE_KEYCARD_SEED:
                        if (!pairingDone) {
                            ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
                        }
                        handleAdmSetKeycardSeed(apdu, true);
                        break;
                    case INS_EXT_AIRGAP_GET_ATTESTATION:
                        handleGetAttestation(apdu);
                        break;
                    case INS_EXT_AIRGAP_SET_USER_KEYCARD:
                        handleSetUserKeycard(apdu, true);
                        break;
           default:
            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
          }
         }
         catch(Exception e) {
          TC.clear();
          if (e instanceof CardRuntimeException) {
           throw ((CardRuntimeException)e);
          }
          else {
           ISOException.throwIt(ISO7816.SW_UNKNOWN);
          }
         }
         finally {
          clearScratch();
         }
         return;
        }
        else {
         ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }
    }
    public static void install (byte bArray[], short bOffset, byte bLength) throws ISOException {
        short offset = bOffset;
        offset += (short)(bArray[offset] + 1);
        offset += (short)(bArray[offset] + 1);
        new LedgerWalletApplet(bArray, (short)(offset + 1), bArray[offset]).register(bArray, (short)(bOffset + 1), bArray[bOffset]);
    }
    private static final byte FIRMWARE_VERSION[] = {
     (byte)0x01, (byte)0x60, (byte)0x01, (byte)0x01, (byte)0x00, (byte)0x00, (byte)0x00
    };
    protected static final short SW_PUBLIC_POINT_NOT_AVAILABLE = (short)0x6FF6;
    private static final byte TRANSACTION_PIN_ATTEMPTS = (byte)1;
    private static final byte TRANSACTION_PIN_SIZE = (byte)4;
    private static final byte WALLET_PIN_ATTEMPTS = (byte)3;
    private static final byte WALLET_PIN_SIZE = (byte)32;
    private static final byte SECONDARY_PIN_ATTEMPTS = (byte)3;
    private static final byte SECONDARY_PIN_SIZE = (byte)4;
    private static final byte TEXT_IDLE[] = { 'N', 'o', ' ', 'p', 'e', 'n', 'd', 'i', 'n', 'g', ' ', 't', 'r', 'a', 'n', 's', 'f', 'e', 'r' };
    private static final byte TEXT_CONFIRM[] = { 'C', 'o', 'n', 'f', 'i', 'r', 'm', ' ', 't', 'r', 'a', 'n', 's', 'f', 'e', 'r', ' ', 'o', 'f', ' ' };
    private static final byte TEXT_BTC[] = { 'B', 'T', 'C' };
    private static final byte TEXT_TO[] = { 't', 'o', ' ' };
    private static final byte TEXT_FEES[] = { '(', 'f', 'e', 'e', 's', ' ' };
    private static final byte TEXT_NO_CHANGE[] = { 'n', 'o', ' ', 'c', 'h', 'a', 'n', 'g', 'e' };
    private static final byte TEXT_CHANGE[] = { 'c', 'h', 'a', 'n', 'g', 'e' };
    private static final byte TEXT_PIN[] = { 'w', 'i', 't', 'h', ' ', 'P', 'I', 'N', ' ' };
    private static final byte TEXT_CLOSE_P = ')';
    private static final byte TEXT_SPACE = ' ';
    private static final byte TEXT_COMMA = ',';
    private static final byte TEXT_SEED[] = { 'W','a','l','l','e','t',' ', 'S','e','e','d', ':', ' ' };
    private static final byte HEX[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
    private static final byte AUTHORIZATION_NONE = (byte)0x00;
    private static final byte AUTHORIZATION_KEYCARD = (byte)0x04;
    private static final byte AUTHORIZATION_NFC_KEYCARD = (byte)0x05;
    private static final byte TRANSACTION_OUTPUT_SCRIPT_PRE[] = { (byte)0x19, (byte)0x76, (byte)0xA9, (byte)0x14 };
    private static final byte TRANSACTION_OUTPUT_SCRIPT_POST[] = { (byte)0x88, (byte)0xAC };
    private static final byte TRANSACTION_OUTPUT_SCRIPT_P2SH_PRE[] = { (byte)0x17, (byte)0xA9, (byte)0x14 };
    private static final byte TRANSACTION_OUTPUT_SCRIPT_P2SH_POST[] = { (byte)0x87 };
    private static final byte OP_RETURN = (byte)0x6A;
    private static final byte KEY_VERSION_P2SH = (byte)0x05;
    private static final byte KEY_VERSION_P2SH_TESTNET = (byte)0xC4;
    private static final byte KEY_VERSION_PRIVATE = (byte)0x80;
    private static final byte KEY_VERSION = (byte)0x00;
    private static final byte KEY_VERSION_TESTNET = (byte)0x6F;
    private static final byte PUBLIC_KEY_W_LENGTH = 65;
    private static final byte PRIVATE_KEY_S_LENGTH = 32;
    private static final byte CLA_BTC_ADMIN = (byte)0xD0;
    private static final byte CLA_BTC = (byte)0xE0;
    private static final byte CLA_BTC_JC_EXTENSIONS = (byte)0xF0;
    private static final byte INS_SETUP = (byte)0x20;
    private static final byte INS_SET_USER_KEYCARD = (byte)0x10;
    private static final byte INS_SETUP_SECURE_SCREEN = (byte)0x12;
    private static final byte INS_SET_ALTERNATE_COIN_VERSIONS = (byte)0x14;
    private static final byte INS_VERIFY_PIN = (byte)0x22;
    private static final byte INS_GET_OPERATION_MODE = (byte)0x24;
    private static final byte INS_SET_OPERATION_MODE = (byte)0x26;
    private static final byte INS_GET_WALLET_PUBLIC_KEY = (byte)0x40;
    private static final byte INS_GET_TRUSTED_INPUT = (byte)0x42;
    private static final byte INS_UNTRUSTED_HASH_START = (byte)0x44;
    private static final byte INS_UNTRUSTED_HASH_FINALIZE = (byte)0x46;
    private static final byte INS_UNTRUSTED_HASH_SIGN = (byte)0x48;
    private static final byte INS_UNTRUSTED_HASH_FINALIZE_FULL = (byte)0x4A;
    private static final byte INS_SIGN_MESSAGE = (byte)0x4E;
    private static final byte INS_IMPORT_PRIVATE_KEY = (byte)0xB0;
    private static final byte INS_GET_PUBLIC_KEY = (byte)0xB2;
    private static final byte INS_DERIVE_BIP32_KEY = (byte)0xB4;
    private static final byte INS_SIGN_VERIFY_IMMEDIATE = (byte)0xB6;
    private static final byte INS_GET_RANDOM = (byte)0xC0;
    private static final byte INS_GET_ATTESTATION = (byte)0xC2;
    private static final byte INS_GET_FIRMWARE_VERSION = (byte)0xC4;
    private static final byte INS_ADM_SET_KEYCARD_SEED = (byte)0x26;
    private static final byte INS_SET_CONTACTLESS_LIMIT = (byte)0xA4;
    private static final byte INS_GET_CONTACTLESS_LIMIT = (byte)0xA6;
    private static final byte INS_EXT_GET_HALF_PUBLIC_KEY = (byte)0x20;
    private static final byte INS_EXT_PUT_PUBLIC_KEY_CACHE = (byte)0x22;
    private static final byte INS_EXT_HAS_PUBLIC_KEY_CACHE = (byte)0x24;
    private static final byte INS_EXT_GET_FEATURES = (byte)0x26;
    private static final byte INS_EXT_AIRGAP_KEY_AGREEMENT = (byte)0x40;
    private static final byte INS_EXT_AIRGAP_SETUP = (byte)0x42;
    private static final byte INS_EXT_AIRGAP_INITIALIZE_KEYCARD_SEED = (byte)0x44;
    private static final byte INS_EXT_AIRGAP_SET_USER_KEYCARD = (byte)0x46;
    private static final byte INS_EXT_AIRGAP_SET_ATTESTATION_PUBLIC = (byte)0x48;
    private static final byte INS_EXT_AIRGAP_GET_ATTESTATION = (byte)0x4A;
    private static final byte P1_REGULAR_SETUP = (byte)0x00;
    private static final byte P1_TRUSTED_INPUT_FIRST = (byte)0x00;
    private static final byte P1_TRUSTED_INPUT_NEXT = (byte)0x80;
    private static final byte P1_HASH_TRANSACTION_FIRST = (byte)0x00;
    private static final byte P1_HASH_TRANSACTION_NEXT = (byte)0x80;
    private static final byte P2_HASH_TRANSACTION_NEW_INPUT = (byte)0x00;
    private static final byte P2_HASH_TRANSACTION_CONTINUE_INPUT = (byte)0x80;
    private static final byte P1_FINALIZE_MORE = (byte)0x00;
    private static final byte P1_FINALIZE_LAST = (byte)0x80;
    private static final byte P1_FINALIZE_CHANGEINFO = (byte)0xFF;
    private static final byte[] SLIP13_HEAD = { (byte)0x80, (byte)0x00, (byte)0x00, (byte)0x0D };
    private static final byte[] BITID_DERIVE = { (byte)0xB1, (byte)0x1D };
    private static final byte[] BITID_DERIVE_MULTIPLE = { (byte)0xB1, (byte)0x1E };
    private static final byte[] SIGNMAGIC = { (byte)0x18, (byte)'B', (byte)'i', (byte)'t', (byte)'c', (byte)'o', (byte)'i', (byte)'n', (byte)' ', (byte)'S', (byte)'i', (byte)'g', (byte)'n', (byte)'e', (byte)'d', (byte)' ', (byte)'M', (byte)'e', (byte)'s', (byte)'s', (byte)'a', (byte)'g', (byte)'e', (byte)':', (byte)'\n' };
    private static final byte P1_PREPARE_MESSAGE = (byte)0x00;
    private static final byte P1_SIGN_MESSAGE = (byte)0x80;
    private static final byte P1_GET_REMAINING_ATTEMPTS = (byte)0x80;
    private static final byte P1_GET_OPERATION_MODE = (byte)0x00;
    private static final byte P1_GET_OPERATION_MODE_2FA = (byte)0x01;
    private static final byte P1_INITIATE_PAIRING = (byte)0x01;
    private static final byte P1_CONFIRM_PAIRING = (byte)0x02;
    private static final byte P1_SET_KEYCARD = (byte)0x01;
    private static final byte P1_CONFIRM_KEYCARD = (byte)0x02;
    private static final byte CONFIRM_PREVIOUS_KEYCARD = (byte)0x02;
    public static final byte BLOB_MAGIC_TRUSTED_INPUT = (byte)0x32;
    private static final byte LIMIT_GLOBAL_AMOUNT = (byte)0;
    private static final byte LIMIT_MAX_FEES = (byte)(LIMIT_GLOBAL_AMOUNT + TC.SIZEOF_AMOUNT);
    private static final byte LIMIT_MAX_CHANGE = (byte)(LIMIT_MAX_FEES + TC.SIZEOF_AMOUNT);
    private static final byte LIMIT_LAST = (byte)(LIMIT_MAX_CHANGE + TC.SIZEOF_AMOUNT);
    protected static final byte MODE_WALLET = (byte)0x01;
    protected static final byte MODE_RELAXED_WALLET = (byte)0x02;
    protected static final byte MODE_SERVER = (byte)0x04;
    protected static final byte MODE_DEVELOPER = (byte)0x08;
    private static final byte SFA_NONE = (byte)0x00;
    private static final byte SFA_ORIGINAL = (byte)0x11;
    private static final byte SFA_SECURITY_CARD = (byte)0x12;
    private static final byte SFA_SECURE_SCREEN = (byte)0x13;
    private static final byte SFA_NFC = (byte)0x20;
    protected static final byte FEATURE_UNCOMPRESSED_KEYS = (byte)0x01;
    protected static final byte FEATURE_RFC_6979 = (byte)0x02;
    protected static final byte FEATURE_ALL_HASHTYPES = (byte)0x04;
    protected static final byte FEATURE_NO_2FA_P2SH = (byte)0x08;
    protected static final byte FEATURE_ARBITRARY_CHANGE = (byte)0x10;
    private static final byte DEFAULT_SEED_LENGTH = (byte)64;
    private static final byte KEYCARD_KEY_LENGTH = (byte)16;
    private static final byte KEYCARD_CHALLENGE_LENGTH = (byte)4;
    private static final byte MAX_DERIVATION_PATH = (byte)10;
    private static final byte SEED_NOT_TYPED = (byte)0x00;
    private static final byte SEED_ENCODED_AIRGAP = (byte)0xF0;
    private static final byte AVAILABLE_MODES[] = { MODE_WALLET, MODE_RELAXED_WALLET, MODE_SERVER, MODE_DEVELOPER };
    private static final byte JC_FEATURE_HAS_PROPRIETARY_API = (byte)0x01;
    public static byte[] scratch256;
    private static OwnerPIN transactionPin;
    private static OwnerPIN walletPin;
    private static byte walletPinSize;
    private static OwnerPIN secondaryPin;
    private static byte secondaryPinSize;
    private static byte setup;
    private static byte limitsSet;
    protected static DESKey chipKey;
    protected static DESKey trustedInputKey;
    protected static DESKey developerKey;
    private static byte supportedModes;
    protected static byte features;
    protected static byte currentMode;
    private static byte stdVersion;
    private static byte p2shVersion;
    protected static byte[] masterDerived;
    private static byte[] limits;
    protected static ProprietaryAPI proprietaryAPI;
    protected static AESKey pairingKey;
    protected static ECPrivateKey attestationPrivate;
    protected static byte[] attestationPublic;
    protected static byte[] attestationSignature;
    protected static boolean pairingDone;
}