{"id":21529181,"url":"https://github.com/everx-labs/tonnfcclientandroid","last_synced_at":"2025-06-15T08:33:03.867Z","repository":{"id":51245358,"uuid":"331755905","full_name":"everx-labs/TonNfcClientAndroid","owner":"everx-labs","description":null,"archived":false,"fork":false,"pushed_at":"2021-09-05T17:41:56.000Z","size":1570,"stargazers_count":3,"open_issues_count":0,"forks_count":4,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-24T01:35:36.271Z","etag":null,"topics":["android","everscale","nfc"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/everx-labs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-21T21:10:34.000Z","updated_at":"2023-03-03T14:41:22.000Z","dependencies_parsed_at":"2022-09-07T14:11:12.794Z","dependency_job_id":null,"html_url":"https://github.com/everx-labs/TonNfcClientAndroid","commit_stats":null,"previous_names":["everx-labs/tonnfcclientandroid"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everx-labs%2FTonNfcClientAndroid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everx-labs%2FTonNfcClientAndroid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everx-labs%2FTonNfcClientAndroid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/everx-labs%2FTonNfcClientAndroid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/everx-labs","download_url":"https://codeload.github.com/everx-labs/TonNfcClientAndroid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131467,"owners_count":21052819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","everscale","nfc"],"created_at":"2024-11-24T01:56:08.323Z","updated_at":"2025-04-09T23:43:15.503Z","avatar_url":"https://github.com/everx-labs.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TonNfcClientAndroid\n\nThe library is developed to handle communication of Android smartphones with NFC TON Labs Security cards. It provides a useful API to work with all functionality (i.e. APDU commands) supported by NFC TON Labs Security card. The technical specification of TON Labs Security card can be found here https://ton.surf/scard.\n\n## Installation\n\nThe library is published on  https://jitpack.io, see https://jitpack.io/#tonlabs/TonNfcClientAndroid.\n\nTo use TonNfcClientAndroid library in Android project you must go through the following steps.\n\n+ Add it in your root build.gradle at the end of repositories.\n```ruby\nallprojects {\n\trepositories {\n\t\t...\n\t\tmaven { url 'https://jitpack.io' }\n\t}\n}\n```\n\n\t\n+ Add the dependency and replace here Tag by the necessary version.\n```ruby\ndependencies {\n\timplementation 'com.github.tonlabs:TonNfcClientAndroid:Tag'\n}\n```\n\n+ Take care of AndroidManifest.xml. It must contain NFC permission and special intent filter.\n\n\u003c!--\n\u003cuses-permission android:name=\"android.permission.NFC\" /\u003e\n\u003cuses-feature android:name=\"android.hardware.nfc\" android:required=\"true\" /\u003e\n\u003caction android:name=\"android.nfc.action.TAG_DISCOVERED\" /\u003e\n--\u003e\n\n```xml\n\u003cintent-filter\u003e\u003e\n    \t\u003caction android:name=\"android.nfc.action.TECH_DISCOVERED\" /\u003e\t\n\u003c/intent-filter\u003e\n\u003cmeta-data android:name=\"android.nfc.action.TECH_DISCOVERED\" android:resource=\"@xml/nfc_tech_filter\" /\u003e\n```\n\nFor this to work you must have an appropriate nfc_tech_filter.xml file in your xml subfolder (\\app\\src\\main\\res\\xml). File nfc_tech_filter.xml must looks as follows.\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003cresources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\"\u003e\n\t\u003ctech-list\u003e\n\t\t\u003ctech\u003eandroid.nfc.tech.IsoDep\u003c/tech\u003e\n        \t\u003ctech\u003eandroid.nfc.tech.NfcA\u003c/tech\u003e\n    \t\u003c/tech-list\u003e\n\u003c/resources\u003e\n```\n\t\t\nTo get the full picture of how AndroidManifest.xml should look like you may walk through the exemplary app inside https://github.com/tonlabs/TonNfcClientAndroid/tree/master/app/.\n\n_Note:_ minSdkVersion now is 24 to use the library.\n\n## Usage (Simple example)\n\nLet's suppose you want to work with NFC TON Labs security card in your MainActivity. And you want to make a simple request to the card: return the maximum number of card's PIN tries. For this request there is a special APDU command supported by the card. And there is a corresponding function in TonNfcClientAndroid library sending it to the card and making postprocessing of card's response for you. To make it work add the following snippet.\n\n```java\nimport com.tonnfccard.CardCoinManagerApi;\nimport com.tonnfccard.nfc.NfcApduRunner;\n\nprivate CardCoinManagerApi cardCoinManagerNfcApi;\n\t\t\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n\tsuper.onCreate(savedInstanceState);\n\tsetContentView(R.layout.activity_main);\n\t...\n\ttry {\n\t\tNfcApduRunner nfcApduRunner = NfcApduRunner.getInstance(MainActivity.this);\n\t\tcardCoinManagerNfcApi = new CardCoinManagerApi(MainActivity.this,  nfcApduRunner);\n\t}\n\tcatch (Exception e) {\n\t\tLog.e(\"TAG\", \"Error happened : \" + e.getMessage());;\n\t}\n\t...\n}\n```\n\n+ Also take care of onNewIntent method. It intercepts the intent created after NFC card (tag) connection. And you must extract the data about the tag from the intent. You need it to start work with the tag.\n\n```java\n@Override\npublic void onNewIntent(Intent intent) {\n\tsuper.onNewIntent(intent);\n\ttry {\n\t\tif (cardCoinManagerNfcApi.setCardTag(intent)) {\n\t\t\tToast.makeText(this, \"NFC hardware touched\", Toast.LENGTH_SHORT).show();\n\t\t}\n\t}\n\tcatch (Exception e) {\n\t\tLog.e(\"TAG\", \"Error happened : \" + e.getMessage());\n\t}\n}\n```\n+ Finally make the request to the card. In this example we send it after pressing the button.\n\n```java\npublic void addListenerOnButton() {\n\tbutton = (Button) findViewById(R.id.button);\n\tbutton.setOnClickListener(new View.OnClickListener() {\n\t\t@Override\n            \tpublic void onClick(View arg0) {\n\t\t\tnew Thread(new Runnable() {\n                   \t\tpublic void run() {\n                        \t\ttry {\n                            \t\t\tString response = cardCoinManagerNfcApi.getMaxPinTriesAndGetJson();\n                            \t\t\tLog.d(\"TAG\", \"Card response : \" + response);\n                        \t\t}\n                        \t\tcatch (Exception e) {\n                            \t\t\te.printStackTrace();\n                            \t\t\tLog.e(\"TAG\", \"Error happened : \" + e.getMessage());\n                        \t\t}\n                    \t\t}\n                \t}).start();\n            \t}\n        });\n}\n```\n\nHere json variable contains the response from card wrapped into json of the following simple format: \n```\t\t\n{\"message\":\"10\",\"status\":\"ok\"}\n```\n\nAnother way to make getMaxPinTries request looks like this.\n\n```java\npublic void addListenerOnButton() {\n\tbutton = (Button) findViewById(R.id.button);\n\tbutton.setOnClickListener(new View.OnClickListener() {\n\t\t@Override\n            \tpublic void onClick(View arg0) {\n\t\t\tboolean showDialog = ...; //false or true?\n                \tcardCoinManagerNfcApi.getRemainingPinTries(new NfcCallback((result) -\u003e textView.setText(String.valueOf(result)), System.out::println), showDialog);\n            \t}\n        });\n}\n```\nFunction _getRemainingPinTries_ (in contrast to _getMaxPinTriesAndGetJson_) creates AsyncTask to perform card operation. And it puts the result (or error message) into callback.\n\nTo get the full picture of the simplest MainActivity you may look like at https://github.com/tonlabs/TonNfcClientAndroid/tree/master/app/ .\n\n## More about responses format\n\n### Case of successful operation\n\nIn the case of successful operation with the card any function of TonNfcClientAndroid library returns json string with two fields \"message\" and \"status\". \"status\" contains \"ok\". In the field \"message\" there's an expected payload. So jsons may look like this.\n\n```\n{\"message\":\"done\",\"status\":\"ok\"}\n{\"message\":\"generated\",\"status\":\"ok\"}\n{\"message\":\"HMac key to sign APDU data is generated\",\"status\":\"ok\"}\n{\"message\":\"980133A56A59F3A59F174FD457EB97BE0E3BAD59E271E291C1859C74C795A83368FD8C7405BC37E1C4146F4D175CF36421BF6AD2AFF4329F5A6C6D772247ED03\",\"status\":\"ok\"}\n\tetc.\n```\n\nIn some cases we put payload into fields with another titles. See functions _getHashes, getKeyChainInfo, getKeyChainDataAboutAllKeys_ in [here](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/docs/FuntionsList.md).\n\n### Case of error\n\nIf some error happened then functions of TonNfcClientAndroid library produce error messages represented by json strings. The structure of json depends on the error class. There are two main classes of errors.\n\n#### Applet (card) errors\n\nIt is the case when applet (installed on the card) threw some error status word (SW). So Android code just catches it and throws away. The typical error json looks like this.\n```\n{\n\t\"message\":\"Incorrect PIN (from Ton wallet applet).\",\n\t\"status\":\"fail\",\n\t\"code\":\"6F07\",\n\t\"errorTypeId\":0,\n\t\"errorType\":\"Applet fail: card operation error\",\n\t\"cardInstruction\":\"VERIFY_PIN\",\n\t\"apdu\":\"B0 A2 00 00 44 35353538EA579CD62F072B82DA55E9C780FCD0610F88F3FA1DD0858FEC1BB55D01A884738A94113A2D8852AB7B18FFCB9424B66F952A665BF737BEB79F216EEFC3A2EE37 FFFFFFFF \"\n}\n```\nHere:\n+ *code* — error status word (SW) produced by the card (applet)\n\n+ *cardInstruction* — title of failed APDU command \n\n+ *errorTypeId* — id of error type (it will always be zero here)\n\n+ *errorType* — description of error type \n\n+ *message* — error message corresponding to error code thrown by applet.\n\n+ *apdu* — full text of failed APDU command in hex format\n\n#### Android errors\n\nIt is the case when error happened in Android code itself. The basic examples: troubles with NFC connection or incorrect format of input data passed into TonNfcClientAndroid. The typical error json looks like this.\n\n```\n{\n\t\"errorType\": \"Native code fail: incorrect format of input data\",\n\t\"errorTypeId\": \"3\",\n\t\"code\": \"30006\",\n\t\"message\": \"Pin must be a numeric string of length 4.\",\n\t\"status\": \"fail\"\n}\n```\t\n\nIn this [document](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/docs/ErrorList.md) you may find the full list of json error messages (and their full classification) that can be thrown by the library.\n\n### String format\n\nThe majority of input data passed into TonNfcClientAndroid library is represented by hex strings of even length \u003e 0. These hex strings are naturally converted into byte arrays inside the library, like: \"0A0A\" → new byte[]{10, 10}. \n\nAnd also the payload produced by the card and wrapped into json responses is usually represented by hex strings of even length \u003e 0.  For example, this is a response from getPublicKey function  returning ed25519 public key.\n\n```\n{\"message\":\"B81F0E0E07316DAB6C320ECC6BF3DBA48A70101C5251CC31B1D8F831B36E9F2A\",\"status\":\"ok\"}\n```\n\nHere B81F0E0E07316DAB6C320ECC6BF3DBA48A70101C5251CC31B1D8F831B36E9F2A is a 32 bytes length ed25519 public key in hex format.\n\n## Test work with the card\n\nYou can work with NFC card only on your Android device, not simulator. There are two basic scenarios of work with the card for Android.\n\n### With invitation dialog\n\nYou call the necessary function from TonNfcClientAndroid API (in the above example we call getMaxPinTries with argument _showDialog = true_). It starts NFC session for you. On the screen you get an invitation dialog to connect the card. To establish the connection hold the card to the top of Android smartphone (field near the camera) as close as possible. Usually smartphone vibrates after establishing NFC connection. And if you use the above example, you must get the toast with the message \"NFC hardware touched\". It means that NFC connection is established. After that smartphone sends APDU commands to the card. \n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"../master/docs/images/invitationDialog.png\" width=\"200\"\u003e\n\u003c/p\u003e\n\nIf you close invitation dialog by pressing 'Cancel' button it will stop NFC session and disconnect the card.\n\nAfter invitation dialog shows up you have 30 seconds to establish NFC connection. If you did not connect the card, the dialog will be closed after 30 seconds passed.\n\n### Without invitation dialog\n\nThere is an option not to use invitation dialogs. Call any API function with argument _showDialog = false_ or use versions of API functions not working with callbacks, like _getMaxPinTriesAndGetJson_ in above example. Such mode is convinient for testing when you call card operations one by one without any delay in the workflow. Here it's a good practice to start with establishing NFC connection. After NFC connection is ready we can send APDU commands to the card. So you may call API functions. For above example push the button to make request getMaxPinTries after you connected the card. \n\nCheck your Logcat console in Android Studio. \n\n```\n===============================================================\n===============================================================\n\u003e\u003e\u003e Send apdu  00 A4 04 00 \n(SELECT_COIN_MANAGER)\nSW1-SW2: 9000, No error., response data bytes: \t6F5C8408A000000151000000A...\n===============================================================\n===============================================================\n\u003e\u003e\u003e Send apdu  80 CB 80 00 05 DFFF028103 \n(GET_PIN_TLT)\nSW1-SW2: 9000, No error., response data bytes: 0A\nCard response : {\"message\":\"10\",\"status\":\"ok\"}\n```\n\nHere you see the log of APDU commands sent to the card and their responses in raw format. And in the end there is a final wrapped response.\n\nIn both scenarios to keep NFC connection alive you must not move the card and smartphone. They should have constant physical contact at least until you will get the message on the toast \"NFC Card operation is finished!\". In the case of fail you'll get the message on the toast \"NFC Card operation failed!\".\n\nAndroid smartphone is capable to hold NFC connection for a very long time if this connection is loaded (i.e. we continue sending APDU commands). If you stop sending APDU commands, NFC card will be disconnected after 30 seconds passed. Also if your screen went out, smartphone interrupts the connection.\n\n## Card activation\n\nWhen user gets NFC TON Labs security card  at the first time, the applet on the card is in a special state.  The main functionality of applet is blocked for now. Applet waits for user authentication. To pass authentication user must have three secret hex strings **authenticationPassword, commonSecret, initialVector**. The tuple **(authenticationPassword, commonSecret, initialVector)** is called _card activation data._  The user is going to get (using debots) his activation data from Tracking Smartcontract deployed for his security card.\n\nAt this step not only the card waits for user authentication. The user also authenticates the card by verification of some hashes.\n\n*Note:* There is a bijection between serial number (SN) printed on the card and activation data.\n\nThe detailed info about card activation and related workflow is [here]().\n\nFor now let's suppose the user somehow got activation data into his application from debot (the details of working with debot will be given later). Then to activate the card he may use the following snippets.\n\n\n```java\nimport com.tonnfccard.CardCoinManagerApi;\nimport com.tonnfccard.CardActivationApi;\nimport com.tonnfccard.nfc.NfcApduRunner;\nimport static com.tonnfccard.helpers.JsonHelper.*;\nimport static com.tonnfccard.helpers.ResponsesConstants.*;\nimport static com.tonnfccard.smartcard.TonWalletAppletConstants.*;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nprivate CardActivationApi cardActivationApi;\nprivate CardCoinManagerApi cardCoinManagerNfcApi;\nprivate static final String DEFAULT_PIN = \"5555\";\nprivate static final String SERIAL_NUMBER = \"504394802433901126813236\";\nprivate static final String COMMON_SECRET = \"7256EFE7A77AFC7E9088266EF27A93CB01CD9432E0DB66D600745D506EE04AC4\";\nprivate static final String IV = \"1A550F4B413D0E971C28293F9183EA8A\";\nprivate static final String PASSWORD  = \"F4B072E1DF2DB7CF6CD0CD681EC5CD2D071458D278E6546763CBB4860F8082FE14418C8A8A55E2106CBC6CB1174F4BA6D827A26A2D205F99B7E00401DA4C15ACC943274B92258114B5E11C16DA64484034F93771547FBE60DA70E273E6BD64F8A4201A9913B386BCA55B6678CFD7E7E68A646A7543E9E439DD5B60B9615079FE\";\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n\ttry {\n\t\tNfcApduRunner nfcApduRunner = NfcApduRunner.getInstance(MainActivity.this);\n\t\tcardCoinManagerNfcApi = new CardCoinManagerApi(MainActivity.this,  nfcApduRunner);\n\t\tcardActivationApi = new CardActivationApi(MainActivity.this,  nfcApduRunner);\n\t}\n\tcatch (Exception e) {\n\t  \tLog.e(\"TAG\", e.getMessage());\n\t}\n}\n\t\nprivate String extractMessage(String jsonStr, String field) throws JSONException { \n\tJSONObject jObject = new JSONObject(jsonStr);\n\treturn jObject.getString(field);\n}\n```\n\t\nAnd use the following code to start card activation.\n\n``` java    \nString hashesJsonStr = cardActivationApi.generateSeedAndGetHashesAndGetJson();\nString hashOfEncryptedCommonSecret = extractMessage(hashesJsonStr, ECS_HASH_FIELD);\nString hashOfEncryptedPassword = extractMessage(hashesJsonStr, EP_HASH_FIELD);\nString serialNumber = extractMessage(hashesJsonStr, SN_FIELD);\nLog.d(\"TAG\", \"hashOfEncryptedCommonSecret : \" + hashOfEncryptedCommonSecret);\nLog.d(\"TAG\", \"hashOfEncryptedPassword : \" + hashOfEncryptedPassword);\nLog.d(\"TAG\", \"serialNumber : \" + serialNumber);\n\nString newPin = \"5555\";\nappletState = extractMessage(cardActivationApi.turnOnWalletAndGetJson(newPin, PASSWORD, COMMON_SECRET, IV),  MESSAGE_FIELD);\nLog.d(\"TAG\", \"Card response (state) : \" + appletState);     \n```\t\n\t\n    \n## About applet states and provided functionality\n\nApplet installed onto NFC TON Labs security card may be in the one of the following states (modes):\n\n1. TonWalletApplet waits two-factor authentication.\n2. TonWalletApplet is personalized.\n3. TonWalletApplet is blocked.\n4. TonWalletApplet is personalized and waits finishing key deleting from keychain.\n\n**Some details of states transitions:**\n\n- When user gets the card at the first time, applet must be in the state 1 (see previous section).\n- If user would try to pass incorrect activation data more than 20 times, then applet state will be switched on state 3. And this is irreversable. In this state all functionality of applet is blocked and one may call only getTonAppletState and getSerialNumber (see the below section Full functions list for more details).\n- After correct activation (≤ 20 attempts to pass activation data) applet goes into state 2. And after this one can not go back to state 1. State 1 becomes unreachable. And at state 2 the main applet functionality is available.\n- If user started operation of deleting a key from card's keychain, then applet is switched on state 4. And it stays in this state until the current delete operation will not be finished. After correct finishing of delete operation applet goes back into state 2. The other way to go back into state 2 is to call resetKeychain function (see the details below).\n- Applet in states 2, 4 may be switched into state 3 in the case if HMAC SHA256 signature verification was failed by applet 20 times successively (more details below).\n\nThe functionality provided by NFC TON Labs security card can be divided into several groups.\n\n- Module for card activation (available in state 1).\n- Crypto module providing ed22519 signature  (available in states 2, 4).\n- Module for maintaining recovery data  (available in states 2, 4).\n- Keychain module  (available in states 2, 4).\n- CoinManager module providing some auxiliary functions (available in any state).\n\n## Protection against MITM\n\nWe protect the most critical card operations (APDU commands) against MITM attack by HMAC SHA256 signature. In this case the data field of such APDU is extended by 32-bytes sault generated by the card and the final byte array is signed. The obtained signature is added to the end of APDU data, i.e. its data field has the structure: payload || sault ||  HMAC(payload || sault). When the card gets such APDU, first it verifies sault and HMAC signature.  \n\nThe secret key for HMAC SHA256 is produced based on card activation data (see above section). This key is saved into Android keystore under alias \"hmac_key_alias_SN\" (SN is replaced by serial number printed on the card) and then it is used by the app to sign APDU commands data fields. Usually after correct card activation in the app (call of cardActivationApi.turnOnWalletAndGetJson) this key is produced and saved into keystore. So no extra code is required.\n\nAnother situation is possible. Let's suppose you activated the card earlier. After that you reinstalled the app working with NFC TON Labs security card or you started using new Android device. Then Android keystore does not have the key to sign APDU commands. So you must create it.\n\n```java\ncardCryptoApi.createKeyForHmacAndGetJson(authenticationPassword, commonSecret, serialNumber));\n```\n\nYou may work with multiple NFC TON Labs security cards. In this case in your Android keystore there is a bunch of keys. Each keys is marked by corresponding SN. And you can get the list of serial numbers for which you have the key in keystore.\n\nThe list of operations protected by HMAC SHA256:\n\n- verifyPin, signForDefaultHdPath, sign, verifyPinAndSign, verifyPinAndSignForDefaultHdPath, checkSerialNumberAndSign, checkSerialNumberAndVerifyPinAndSign, checkSerialNumberAndSignForDefaultHdPath, checkSerialNumberAndVerifyPinAndSignForDefaultHdPath.\n- all functions related to card keychain\n\t\n\t\n## Request ED25519 signature\n\nThe basic functionality provided by NFC TON Labs security card is Ed25519 signature. You may request public key and request the signature for some message.\n\n```java\nnfcApduRunner = NfcApduRunner.getInstance(MainActivity.this);\nCardCryptoApi cardCryptoApi = new CardCryptoApi(MainActivity.this, nfcApduRunner);\nString hdInd = \"1\";\nString response = cardCryptoApi.getPublicKeyAndGetJson(hdInd);\nString msg = \"A10D\";\nString pin = \"5555\";\nString response = cardCryptoApi.verifyPinAndSignAndGetJson(msg, hdInd, pin);\n```\n\n_Note:_ Functions signForDefaultHdPath, sign are protected by HMAC SHA256 signature (see previous section). But also there is an additional protection for them by PIN code. You have 10 attempts to enter PIN, after 10th fail you will not be able to use existing seed (keys for ed25519) . The only way to unblock these functions is to reset the seed (see resetWallet function) and generate new seed (see generateSeed). After resetting the seed PIN will be also reset to default value 5555.\n\n## Card keychain\n\nInside NFC TON Labs security card we implemented small flexible independent keychain. It allows to store some user's keys and secrets. The maximum number of keys is 1023, maximum key size — 8192 bytes and the total available volume of storage — 32767 bytes.\n\nEach key has its unique id. This is its HMAC SHA256 signature created using the key elaborated based on card activation data (see the above section _Protection against MITM_). So id is a hex a string of length 64.\n\nThe below snippet demonstrates the work with the keychain. We add one key, then retrieve it from the card. Then we replace it by a new key of the same key. At the end we delete the key.\n\n_Note:_ This test is quite long working. So take care of your NFC connection. To keep it alive your screen must not go out. You may increase timeout for your Android device to achieve this.\n\n```java\nimport com.tonnfccard.CardKeyChainApi;\n\nprivate CardKeyChainApi cardKeyChainApi;\n\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n\ttry {\n\t\tNfcApduRunner nfcApduRunner = NfcApduRunner.getInstance(MainActivity.this);\n\t\tcardKeyChainApi = new CardKeyChainApi(MainActivity.this,  nfcApduRunner);\n\t}\n\tcatch (Exception e) {\n\t\tLog.e(\"TAG\", \"Error happened : \" + e.getMessage());\n\t}\n}\n```\n\n```java\nString status = cardCryptoApi.createKeyForHmacAndGetJson(PASSWORD, COMMON_SECRET, SERIAL_NUMBER);\nLog.d(\"TAG\", \"status : \" + status);\n\nString response = cardKeyChainApi.resetKeyChainAndGetJson();\nLog.d(\"TAG\", \"resetKeyChain response : \" + response);\n\nresponse = cardKeyChainApi.getKeyChainInfoAndGetJson();\nLog.d(\"TAG\", \"getKeyChainInfo response : \" + response);\n\nString keyInHex = StringHelper.getInstance().randomHexString(2 * MAX_KEY_SIZE_IN_KEYCHAIN);\nLog.d(\"TAG\", \"key to add :  : \" + keyInHex .length());\nresponse = cardKeyChainApi.addKeyIntoKeyChainAndGetJson(keyInHex);\nLog.d(\"TAG\", \"addKeyIntoKeyChain response : \" + response);\nString keyHmac = extractMessage(response, MESSAGE_FIELD);\nLog.d(\"TAG\", \"keyHmac : \" + response);\n\nresponse = cardKeyChainApi.getKeyChainInfoAndGetJson();\nLog.d(\"TAG\", \"getKeyChainInfo response : \" + response);\n\nresponse = cardKeyChainApi.getKeyFromKeyChainAndGetJson(keyHmac);\nString keyFromCard = extractMessage(response, MESSAGE_FIELD);\nLog.d(\"TAG\", \"keyFromCard : \" + response);\n\nif (!keyInHex.toLowerCase().equals(keyFromCard.toLowerCase())) {\n\tthrow  new Exception(\"Bad key from card : \" + keyFromCard);\n}\n\nString newKeyInHex =  StringHelper.getInstance().randomHexString(2 * MAX_KEY_SIZE_IN_KEYCHAIN);\nLog.d(\"TAG\", \"new key :  : \" +  newKeyInHex.length());\nresponse = cardKeyChainApi.changeKeyInKeyChainAndGetJson(newKeyInHex, keyHmac);\nLog.d(\"TAG\", \"changeKeyInKeyChain response : \" + response);\nString newKeyHmac = extractMessage(response, MESSAGE_FIELD);\n\nresponse = cardKeyChainApi.getKeyChainInfoAndGetJson();\nLog.d(\"TAG\", \"getKeyChainInfo response : \" + response);\n\nresponse = cardKeyChainApi.getKeyFromKeyChainAndGetJson(newKeyHmac);\nString newKeyFromCard = extractMessage(response, MESSAGE_FIELD);\nLog.d(\"TAG\", \"keyFromCard : \" + response);\n\nif (!newKeyInHex.toLowerCase().equals(newKeyFromCard.toLowerCase())) {\n\tthrow  new Exception(\"Bad key from card : \" + newKeyFromCard);\n}\n\nresponse = cardKeyChainApi.deleteKeyFromKeyChainAndGetJson(newKeyHmac);\nLog.d(\"TAG\", \"deleteKeyFromKeyChain response : \" + response);\n\nresponse = cardKeyChainApi.getKeyChainInfoAndGetJson();\nLog.d(\"TAG\", \"getKeyChainInfo response : \" + response);\n\nJSONObject jObject = new JSONObject(response);\nint num  =  Integer.parseInt(jObject.getString(NUMBER_OF_KEYS_FIELD));\n\nif (num != 0) {\n\tthrow  new Exception(\"Bad number of keys : \" + num);\n}\n```\n\nWe also give here another example using API functions working with callbacks. \n\n```java\nimport org.riversun.promise.Action;\nimport org.riversun.promise.Promise;\n\npublic NfcCallback createCallback(Action action){\n        return new NfcCallback((result) -\u003e {\n            System.out.println(result);\n            textView.append(\"\\n\");\n            textView.append(String.valueOf(result));\n            action.resolve(result);\n        }, (error) -\u003e {\n            System.out.println(error);\n            action.reject(error);\n        });\n}\n```\n\n```java\nPromise.resolve(\"start\")\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n\t\tcardKeyChainApi.resetKeyChain(createCallback(action), showDialog);\n\t});\n}))\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n\t\tSystem.out.println(\"resetKeyChain result = \" + data);\n\t\tcardKeyChainApi.getKeyChainInfo(createCallback(action), showDialog);\n\t});\n}))\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n\t\tSystem.out.println(\"getKeyChainInfo result = \" + data);\n\t\tString keyInHex = \"001122334455\";\n\t\tcardKeyChainApi.addKeyIntoKeyChain(keyInHex, createCallback(action), showDialog);\n\t});\n}))\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n\t\tSystem.out.println(\"addKeyIntoKeyChain result = \" + data);\n\t\tString keyInHex = \"667788\";\n\t\tcardKeyChainApi.addKeyIntoKeyChain(keyInHex, createCallback(action), showDialog);\n\t});\n}))\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n\t\tSystem.out.println(\"addKeyIntoKeyChain result #2 = \" + data);\n                cardKeyChainApi.getKeyChainInfo(createCallback(action), showDialog);\n\t});\n}))\n.then(new Promise((action, data) -\u003e {\n\trunOnUiThread(() -\u003e {\n        \tSystem.out.println(\"getKeyChainInfo result = \" + data);\n                cardKeyChainApi.getKeyChainDataAboutAllKeys(createCallback(action), showDialog);\n       \t});\n}))\n.start();\n```\n\nIn this example we create two keys in card's keychain and in the end read info about all keys added into keychain. Each API function creates AsyncTask. But since card operations must be done successively, we should organize a chain of AsyncTasks in which a new task is started only after the previous was finished. To achieve this we used _promises_ implemented in [library](https://github.com/riversun/java-promise).\n\n## Recovery module\n\nThis module is to store/maintain the data for recovering service: multisignature wallet address (hex string of length 64), TON Labs Surf public key (hex string of length 64) and part of card's activation data: authenticationPassword (hex string of length 256), commonSecret(hex string of length 64). This data will allow to recover access to multisignature wallet in the case when user has lost Android device with installed Surf application and also a seed phrase for Surf account.\n\nThere is an snippet demonstrating the structure of recovery data and the way of adding it into NFC TON Labs security card.\n\n```java\nimport com.tonnfccard.RecoveryDataApi;\nimport com.tonnfccard.nfc.NfcApduRunner;\nimport com.tonnfccard.utils.ByteArrayUtil;\n\nprivate static final int AES_KEY_SIZE = 128; // in bits\nprivate static final int AES_COUNTER_SIZE = 16; // in bytes\nprivate static final String SURF_PUBLIC_KEY = \"B81F0E0E07416DAB6C320ECC6BF3DBA48A70101C5251CC31B1D8F831B36E9F2A\";\nprivate static final String MULTISIG_ADDR = \"A11F0E0E07416DAB6C320ECC6BF3DBA48A70121C5251CC31B1D8F8A1B36E0F2F\";\n\nprivate RecoveryDataApi recoveryDataApi;\nprivate SecureRandom sr = new SecureRandom();\nprivate KeyGenerator kg;\nprivate SecretKey key;\nprivate byte[] counter = new byte[AES_COUNTER_SIZE];\n\t\n@Override\nprotected void onCreate(Bundle savedInstanceState) {\n\ttry {\n\t\tNfcApduRunner nfcApduRunner = NfcApduRunner.getInstance(MainActivity.this);\n\t\trecoveryDataApi = new RecoveryDataApi(MainActivity.this,  nfcApduRunner);\n\t\tkg = KeyGenerator.getInstance(\"AES\");\n\t\tkg.init(AES_KEY_SIZE);\n\t\tkey = kg.generateKey();\n\t}\n\tcatch (Exception e) {\n\t\tLog.e(\"TAG\", e.getMessage());\n\t}\n}\n```\nAnd use the following code to test recovery data adding.\n\n```java\nString response = recoveryDataApi.resetRecoveryDataAndGetJson();\nLog.d(\"TAG\", \"resetRecoveryData response : \" + response);\nresponse = recoveryDataApi.isRecoveryDataSetAndGetJson();\nLog.d(\"TAG\", \"isRecoveryDataSet response : \" + response);\n                            \nJSONObject recoveryData = new JSONObject();\nrecoveryData.put(\"surfPublicKey\", SURF_PUBLIC_KEY);\nrecoveryData.put(\"multisigAddress\", MULTISIG_ADDR);\nrecoveryData.put(\"p1\", PASSWORD);\nrecoveryData.put(\"cs\", COMMON_SECRET);\n\nLog.d(\"TAG\", \"recoveryData : \" + recoveryData.toString());\nbyte[] recoveryDataBytes = recoveryData.toString().getBytes(StandardCharsets.UTF_8);\nLog.d(\"TAG\", \"recoveryDataBytes length : \" + recoveryDataBytes.length);\n\nCipher aesCtr = Cipher.getInstance(\"AES/CTR/NoPadding\");\nsr.nextBytes(counter);\naesCtr.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(counter));\n\nbyte[] encryptedRecoveryDataBytes = aesCtr.doFinal(recoveryDataBytes);\n\nString encryptedRecoveryDataHex = ByteArrayUtil.getInstance().hex(encryptedRecoveryDataBytes);\nLog.d(\"TAG\", \"encryptedRecoveryDataHex : \" + encryptedRecoveryDataHex);\n\nresponse = recoveryDataApi.addRecoveryDataAndGetJson(encryptedRecoveryDataHex );\nLog.d(\"TAG\", \"addRecoveryData response : \" + response);\n\nresponse = recoveryDataApi.isRecoveryDataSetAndGetJson();\nLog.d(\"TAG\", \"isRecoveryDataSet response : \" + response);\n\nresponse = recoveryDataApi.getRecoveryDataLenAndGetJson();\nLog.d(\"TAG\", \"getRecoveryDataLen response : \" + response);\n\nresponse = recoveryDataApi.getRecoveryDataHashAndGetJson();\nLog.d(\"TAG\", \"getRecoveryDataHash response : \" + response);\n```\n \nThere is a short code snippet to get recovery data from the card.\n```java\nString response = recoveryDataApi.isRecoveryDataSetAndGetJson();\nLog.d(\"TAG\", \"isRecoveryDataSet response : \" + response);\nString status = extractMessage(response, MESSAGE_FIELD);\nif (status.equals(TRUE_MSG)) {\n\tresponse = recoveryDataApi.getRecoveryDataAndGetJson();\n        Log.d(\"TAG\", \"getRecoveryData response : \" + response);\n        String encryptedRecoveryDataHex = extractMessage(response, MESSAGE_FIELD);\n\tLog.d(\"TAG\", \"encryptedRecoveryDataHex : \" + encryptedRecoveryDataHex);\n\n        byte[] encryptedRecoveryDataBytes = ByteArrayUtil.getInstance().bytes(encryptedRecoveryDataHex);\n        Log.d(\"TAG\", \"encryptedRecoveryDataBytes length : \" + encryptedRecoveryDataBytes.length);\n\n        Cipher aesCtr = Cipher.getInstance(\"AES/CTR/NoPadding\");\n        aesCtr.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(counter));\n\n        byte[] recoveryDataBytes = aesCtr.doFinal(encryptedRecoveryDataBytes);\n\n        String recoveryData = new String(recoveryDataBytes, StandardCharsets.UTF_8);\n\n        Log.d(\"TAG\", \"Got recoveryData from card : \" + recoveryData);\n}\nelse {\n\tLog.d(\"TAG\", \"Recovery data is not set yet.\");\n}\n```\n\n## About NfcCallback\t\n\nFor each card operation now there is a pair of functions in our API. For example let's look at operation getMaxPinTries. Previously we tried it already. There are two functions for it.\n\n```java\npublic String getMaxPinTriesAndGetJson(Boolean... showDialog) throws Exception\npublic void getMaxPinTries(final NfcCallback callback, Boolean... showDialog) \n```\n\n1) The first one returns json response or throws a exception containing json error message. Call it inside new thread or AsyncTask. Otherwise your card operation will block the application. \n\n2) The second function does the same work, but it creates AsyncTask. And all work with the card is done inside _doInBackground_ function. In the end it puts json response/json error message into callback. For this we defined NfcCallback.\n\n```java\npublic class NfcCallback {\n  \tprivate NfcResolver resolve;\n  \tprivate NfcRejecter reject;\n  \tpublic NfcCallback(NfcResolver resolve, NfcRejecter reject) {\n    \t\tset(resolve, reject);\n  \t}\n}\n\n@FunctionalInterface\npublic interface NfcRejecter {\n  \tvoid reject(String errorMsg);\n}\n\n@FunctionalInterface\npublic interface NfcResolver {\n  \tvoid resolve(Object value);\n}\n```\n\nTo use you must override NfcRejecter and NfcResolver interfaces.\n\n```java\nimport com.facebook.react.bridge.Promise;\n...\ncardCoinManagerNfcApi.getMaxPinTries(NfcCallback(promise::resolve, promise::reject));\n```\n\t\n## Full functions list \n\nThe full list of functions provided by the library to communicate with the card you will find [here](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/docs/FuntionsList.md)\n\n## Auxiliary classes\n\nTonNfcClientAndroid provides also additional entities. \n+ In [TonWalletConstants](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/tonnfcclientandroid/src/main/java/com/tonnfccard/TonWalletConstants.java) one may find the list of all constants required for work. \n + Class [ByteArrayUtil](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/tonnfcclientandroid/src/main/java/com/tonnfccard/utils/ByteArrayUtil.java) provides functions to handle byte arrays, hex representations of byte arrays and integer numbers. It is to simplify the work with the main API. \n + Class [NfcApduRunner](https://github.com/tonlabs/TonNfcClientAndroid/tree/master/tonnfcclientandroid/src/main/java/com/tonnfccard/nfc) provides functionality to connect NFC smart card and send arbitrary APDU to it. You can play with it, but normally you should not use it to work with TON Wallet functionality. Use functions from classes with ending 'Api' to communicate with the card correctly and get well formed json responses.\n + Class wrappers for APDU command [CAPDU](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/tonnfcclientandroid/src/main/java/com/tonnfccard/smartcard/CAPDU.java) and its response [RAPDU](https://github.com/tonlabs/TonNfcClientAndroid/blob/master/tonnfcclientandroid/src/main/java/com/tonnfccard/smartcard/RAPDU.java). They are to play with NfcApduRunner.\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverx-labs%2Ftonnfcclientandroid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Feverx-labs%2Ftonnfcclientandroid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Feverx-labs%2Ftonnfcclientandroid/lists"}