/* ******************************************************************************* * 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; }