-
Notifications
You must be signed in to change notification settings - Fork 15
Home
Integration guide for creating a wallet using Zion Key Management APIs
ZKMA (Zion Key Management API) is a library which provides a way for developers to manage seed security built into HTC EXODUS devices, which integrates Zion protection. All secure operations (input pin, display seed, sign transaction…) will be performed by the trusted OS and no secure data exposed to the rest of Android.
Figure 1. ZKMA API call flow
ZKMA APIs can be called directly or bound as ZKMS of HTC Zion Vault. For performance, our suggestion is you can call ZKMA by ZKMA.aar. Another way which is ZKMS can be used for reducing your APK size. This document will focus on ZKMA API call flow via ZKMA.aar.
ZKMA | ZKMS | |
---|---|---|
Library name | ZKMA.aar | ZKMS.aar |
File Size | 7 MB~ | 20 KB~ |
Architecture | Direct call API via ZKMA library | Call API via ZKMS service |
HTC Zion Vault App | Unnecessary | Mandatory |
Table 1. A comparison between ZKMA and ZKMS libraries
Any collaborator can get the latest ZKMA .AAR library — HtcWalletSDK-Htc_partner1-release.aar — from https://github.com/htczion/ZKMA/releases .
Most functions in ZKMA return a value. Definitions are listed in the RESULT class. If a function doesn't seem to work, please check the return value in the RESULT class. Return value definitions can be found at RESULT.java
ZKMA is packaged as an AAR file that can be imported into any android app project.
Copy this .AAR file to your app project lib path:
<Project Name>\app\libs\ZKMA-release.aar
dependencies{
...
compile(name:'ZKMA-release', ext:'aar')
...
}
Press the “Make project” button or run the command below to build your app:
./gradlew assembleRelease
The HTC wallet SDK manager is an agent which wraps all seed operations. You must get the instance (singleton) before performaning any operation related to seed access.
HtcWalletSdkManager mZKMA = HtcWalletSdkManager.getInstance();
Initialization prepares all resources and verifies that API function calls work correctly. For example, RESULT.E_SDK_SERVICE_TOO_OLD during initialization means the system service is too old to run, and to prompt the user to do a ROM update. Otherwise all APIs will return a failure or RuntimeException.
mZKMA = HtcWalletSdkManager.getInstance();
int result = mZKMA.init(getApplicationContext());
switch (result) {
case RESULT.E_SDK_ROM_SERVICE_TOO_OLD:
case RESULT.E_SDK_ROM_TZAPI_TOO_OLD:
// App should prompt the user to update ROM
showUpdateDialog(mActivity, "PLEASE UPDATE YOUR SYSTEM");
break;
case RESULT.E_TEEKM_TAMPERED:
// App should prompt the user it’s rooted device
showUpdateDialog(mActivity, "SDK can't support Rooted device");
break;
default:
Log.w(TAG, "init() result="+intValue);
}
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
Compatibility between ZKMA versions and ROM ApiVersion is described in README.md in the following format.:
1.1.0 (0.0003.01010001)
1.1.1
1.1.2
1.1.3
1.2.0 (0.0004.01010005)
1.2.1 (0.0004.01000006)
2.0.0 , ZKMS_ver= 2.0.0
For example, 1.1.0 (0.0003.01010001) can be broken down like this:
1.1.0
indicates ZKMA version based on the ZKMA.AAR version.
0.0003.01010001
indicates the combined API version based on the ROM version.
The combined API version number can be further divided into three numbers and read as follows:
0: The HW wallet.
0003: A hexadecimal number referring to the ROM currentServiceVer.
01010001: A hexadecimal number referring to the ROM currentTzapiVer.
ZKMS_ver= 2.0.0
indicates the supported ZKMS version.
If the combined API version field is empty, such as the listing for ZKMA 1.1.2, it means the ZKMA is compatible with the previous version, and no ROM update is necessary.
ZKMS is a service based on ZKMA. As such, the ZKMS version shouldn't be lower than the ZKMA version otherwise ZKMS will return the E_ZKMA_TOO_OLD error.
Figure 4. Init API check sequence
To check Trust Zone compatibility of the ROM, the init API sequence will check minServiceVer and minTzApiVer. If the ROM is too old, ZKMA will return either E_SDK_ROM_SERVICE_TOO_OLD or E_SDK_ROM_TZAPI_TOO_OLD to the caller.
The current ZKMA version of the HTC Zion Vault app can be gotten with getModuleVersion().
String SdkVersion= mZKMA.getModuleVersion();
API version information is distinct, and refers to the API level supported by your current EXODUS device hardware. If the API version is lower than the minimum defined by ZKMA, the app should show a dialog to prompt the user to update ROM.
String apiVersion= mZKMA.getApiVersion();
You can get more ZKMA information with getExportFields(). Support includes but is not limited to bTZ_support, minServiceVer, minTzApiVer, etc.
ExportFields mExportFields= mZKMA.getExportFields();
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
For security, ZKMA does not support rooted S-ON devices. If ZKMA detects this situation, it will return the error RESULT.E_TEEKM_TAMPERED to the developers. isRooted() allows the developer to check if the device rooted at any point.
int isRooted(); // 0: not root yet(RESULT.NOT_ROOTED), others: rooted or other errors
To prevent the Trust Zone blocked UI thread, the developer must call this API in a background thread.
ZKMA uses unique IDs to distinguish between callers of seed operations. These IDs are generated by register(). If there is any change in the parameters wallet_name or value, a new UID will be generated. This allows apps requiring multiple seeds to adjust the value parameter to get multiple IDs allowing access to different seeds.
long unique_id = mZKMA.register(wallet_name, sha256);
If unique_id returns 0, registration has failed. Additionally, a wallet_name cannot be more than 32 characters.
To prevent the Trust Zone blocked UI thread, the developer must call this API in a background thread.
For security concern, all UIs,show by ZKMA, will disappear after 30sec. The only special case is the “12-word recovery phrase“ screen. In this case, the screen timeout will be 3 minutes for the user hand writing 12-word recovery phrase.
If no seed exists, you can set keyboard type as either qwertypad for a QWERTY layout or numberpad for a numeric layout.
// nType= 0:qwertypad(default), 1:numberpad
int result = mZKMA.setKeyboardType(unique_id, nType);
To prevent the Trust Zone blocked UI thread, the developer must call this API in a background thread.
Figure 5-1. two keyboard type
If the user has not used cryptocurrency before, they will need to create a new wallet and generate new seeds. createSeed() has been provided for this purpose. After invoking this function, the user will be prompted to set a passcode. Any seed-related function will prompt the user for this passcode.
Once passcode setup is complete, the user will be asked to write down 12 words that represent their cryptographic seed (we call this the “12-word recovery phrase”). To ensure the user has accurately recorded the 12-word recovery phrase, they will be prompted to re-enter for confirmation before continuing.
int result = mZKMA.createSeed(unique_id);
Figure 5. Seed creation process from the perspective of the user
To prevent the Trust Zone blocked UI thread, the developer must call this API in a background thread.
If there is a preexisting seed, the keyboard can still be specified as qwertypad or numberpad with the function changePIN_v2. The first screen will instruct the user to input the old PIN, and then the following screen will show the new keyboard specified by the nType parameter.
// nType= 0:qwertypad(default), 1:numberpad
int result = mZKMA.changePIN_v2(unique_id, nType);
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
Figure 5-3. Change PIN V2
If the user has used cryptocurrency before, they may wish to restore an existing wallet instead of creating a new one. This capability is provided by restoreSeed(). The user need only enter their 12-word recovery phrase to restore their wallet. Users will also be prompted to set a passcode.
int result = mZKMA.restoreSeed(unique_id);
Prompt the user to enter their 12-word recovery phrase
Figure 6. Seed restoration process from the perspective of the user
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
An app needs to be able to distinguish between use cases where the user needs to create a new wallet, restore an existing wallet, or let the user check current wallet details. This function can be used to check if the device has an existing seed and wallet. If there is no preexisting seed, the user has not completed wallet setup. Users should be prompted to create a wallet or restore an existing one.
int result = mZKMA.isSeedExists(unique_id);
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
When creating a new wallet, the user will be prompted to write down their 12-word recovery phrase. If the user has lost their recovery phrase, the wallet app can use this function to display the recovery phrase again so that the user can record it in a safe place.
int result = mZKMA.showSeed(unique_id);
Figure 7. Trusted UI for the user to check their seed
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
Because different types of crypto currency have different algorithms to generate the wallet address, ZKMA does not provide a way to retrieve an account address. Instead, it provides a way to retrieve the public key. Once the key is obtained, apps can generate their own account addresses.
// A holder for store your key
PublicKeyHolder sendPublicKeyHolder, receivePublicKeyHolder;
// You can get a public key for send or receive purpose, index can be increased by SDK or parameter.
// coin_type: 0=BitCoin, 2=LiteCoin, 60=Ethereum, 145=Bitcoin Cash(BCH)
sendPublicKeyHolder = mZKMA.getSendPublicKey(unique_id, coin_type);
sendPublicKeyHolder = mZKMA.getSendPublicKey(unique_id, coin_type, keyIdx);
receivePublicKeyHolder = mZKMA.getReceivePublicKey(unique_id, coin_type);
receivePublicKeyHolder = mZKMA.getReceivePublicKey(unique_id, coin_type, keyIdx);
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
Wallet apps can use this function to transfer crytocurrency to other account addresses. Users are prompted for their passcode before executing the sign operation. Note: Signing operation will not upload the transaction on chains. Your app must upload it on its own.
With the seed and public key, you can sign transactions as appropriate for your coin type. To sign a transaction, you will need to pass data in the JSON format - including all raw transaction data for signTransaction(). Signed raw transaction data bytes will be returned by the byteArrayHolder parameter.
int result = mZKMA.signTransaction(unique_id, coin_type, rates, strJson, byteArrayHolder);
// a byte array holder to receive the transaction data
public class ByteArrayHolder {
private static final int DEFAULT_ARRAY_SIZE = 2*1024; // 2KB
public byte[] byteArray;
public long receivedLength;
public ByteArrayHolder() {
byteArray = new byte[DEFAULT_ARRAY_SIZE];
receivedLength = 0;
}
}
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
// A bitcoin JSON sample
{
"tx_version": "01",
"tx_inputs_count": "01",
"tx_inputs": [
{
"path": "m/44'/0'/0'/0/0",
"tx_id": "9c6700b994e811bc6c4ca6c83a3776fafa4f720f06a2240d3efb451791aa7b8b",
"tx_index": "00",
"scriptSig": "4104e0bf225f8998c7e3dc99899ce2f08d87969ee1bdc0f0759941134f375fd1e8eecba4db44e3f76dce7fd16262c2ca9e93628eafde3d5a6ffa99ae13e74bbc54f6ac",
"sequence": "ffffffff",
"amount": "e86f7932"
}
],
"tx_outputs_count": "01",
"tx_outputs": [
{
"amount": "48bc4631",
"address": "3P14159f73E4gFr7JterCCQh9QjiTjiZrG"
}
],
"tx_changes_count": "01",
"tx_changes": [
{
"path": "m/44'/0'/0'/0/0",
"amount": "002d3101"
}
],
"lock_time": "1234"
}
// A litecoin JSON sample
{
"tx_version": "01",
"tx_inputs_count": "01",
"tx_inputs": [
{
"path": "m/44'/02'/0'/0/0",
"tx_id": "9c6700b994e811bc6c4ca6c83a3776fafa4f720f06a2240d3efb451791aa7b8b",
"tx_index": "00",
"scriptSig": "76a914feb5f43851477b22f68db0523351c4debbc67a7588ac",
"sequence": "ffffffff",
"amount": "e86f7932"
}
],
"tx_outputs_count": "01",
"tx_outputs": [
{
"amount": "48bc4631",
"address": "n4jjuARyw1nnHhta1fR5khPFS5i1BDakX6"
}
],
"tx_changes_count": "01",
"tx_changes": [
{
"path": "m/44'/02'/0'/0/0",
"amount": "002d3101"
}
],
"lock_time": "00"
}
// A ethereum JSON sample
{
"path": "m/44'/60'/0'/0/0",
"tx": {
"nonce": "01",
"gas_price": "09184e72a000",
"gas_limit": "493e0",
"to": "d8A7297522A2e30bE59f66e8CB2B06c89a50490c",
"value": "38d7ea4c68000",
"erc_flag": "0",
"data": "",
"chain_id": "04"
}
}
// A Ethereum ERC20 JSON sample
{
"path": "m/44'/60'/0'/0/0",
"tx": {
"nonce": "04",
"gas_price": "0165a0bc00",
"gas_limit": "928a",
"to": "B8c77482e45F1F44dE1745F52C74426C631bDD52",
"value": "",
"erc_flag": "20",
"data": "a9059cbb00000000000000000000000058a61a7144c8c7545794447a9a3e9a3d56066c200000000000000000000000000000000000000000000000001bc16d674ec80000",
"chain_id": "04"
}
}
// A Bitcoin Cash(BCH) JSON sample
{
"tx_version": "02",
"tx_inputs_count": "01",
"tx_inputs": [
{
"path": "m/44'/145'/0'/0/0",
"tx_id": "42a9afa7d0be8aa574ba9e5fd27f07c31610469eb86395de6f69386a259d43aa",
"tx_index": "00",
"scriptSig": "76a914c6ab328b8bd42e6986948b3e0206e2a33c5ff0ee88ac",
"sequence": "FEFFFFFF",
"amount": "80841e"
}
],
"tx_outputs_count": "01",
"tx_outputs": [
{
"amount": "40420f",
"address": "bitcoincash:qppfx64ss3k5smqj8v8yua28amsysdhdhv5ymjlr64"
}
],
"tx_changes_count": "01",
"tx_changes": [
{
"path": "m/44'/145'/0'/0/0",
"amount": "d63d0f"
}
],
"lock_time": "0"
}
Figure 8. Show the security UI for sign a transaction by coin type
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
Signing a transaction for Ethereum ERC20:
For the Ethereum ERC20 Trusted UI, the parameters of extra tag can be used for customizing the icons, background color, token name, full display name of token, and number of decimal places for the Trusted UI. The example below illustrates the extra tag with JSON data that will display corresponding content in the Trusted UI.
// A Ethereum Cash(ETH) JSON sample for ERC20
{
"path":"m\/44'\/60'\/0'\/0\/0",
"tx":{
"nonce":"5",
"gas_price":"12a05f200",
"gas_limit":"5d82",
"to":"0d8775f648430679a709e98d2b0cb6250d2887ef",
"value":"",
"erc_flag":"20",
"data":"a9059cbb000000000000000000000000c24c375a77baf82750c46f1c76c809d5ad4c6769000000000000000000000000000000000000000000000003db72f26a08530000",
"chain_id":"1"
},
"currency":"USD",
"extra": {
"erc20_icon": "**<146*146(Exodus) or 72*72(Breeze2) png image file raw data converted as HEX string>**",
"erc20_symbol": "HAK",
"erc20_displayname": "HAWK_TOKEN",
"erc20_decimal": "18"
}
}
The parameters of extra tag are specific as:
erc20_icon: ERC20 Token icon binary data. The icon should be in PNG format, and no larger than 146x146(Exodus) or 72x72(Breeze2) pixels. Must be converted to HEX String before being passed to the JSON field.
erc20_symbol: ERC20 token name
erc20_displayname: full display name of ERC20 token
erc20_decimal: Number of decimal places to display. The correct number of decimal places for the target token is required. All tokens have their own specifications for decimal places.
Figure 9. Show the security UI with ERC20 extra tag for sign a transaction
For Ethereum ERC20 smart contract methods, ZKMA supports the ERC20 transfer function with data parsing. For other functions, the trusted UI will show an unknown function with raw HEX data for user confirmation.
contract ERC20Interface {
function totalSupply() public view returns (uint);
function balanceOf(address tokenOwner) public view returns (uint balance);
function allowance(address tokenOwner, address spender) public view returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success); // ZKMA support it now.
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
Signing Ethereum ERC721 transactions:
For Ethereum ERC721, the parameters of extra tag can be used to customize the icon and the full name of ERC721 token in the Trusted UI. Unlike ERC20, the erc_flag and erc20_symbol for the ERC721 tag can’t be used for customization. For the Trusted UI, erc_flag must be set to “721” and erc20_symbol must be set to "ERC-721". The following example demonstrates parameters in JSON format corresponding to the Trusted UI output in the figure below.
// A Ethereum Cash(ETH) JSON sample for ERC721
{
"path": "m/44'/60'/0'/0/0",
"tx" : {
"nonce": "04",
"gas_price": "0165a0bc00",
"gas_limit": "493e0",
"to": "B8c77482e45F1F44dE1745F52C74426C631bDD52",
"value": "",
"erc_flag": "721",
"data": "a9059cbb00000000000000000000000058a61a7144c8c7545794447a9a3e9a3d56066c200000000000000000000000000000000000000000000000001bc16d674ec80000",
"chain_id": "03"
},
"extra": {
"erc20_icon": "**<146*146(Exodus) or 72*72(Breeze2) png image file raw data converted as HEX string>**",
"erc20_symbol": "ERC-721",
"erc20_displayname": "Crypto Kitties"
}
}
Figure 10. Trusted UI for signing an ERC721 transaction
For Ethereum ERC721 smart contract methods, ZKMA only supports the ERC20 transfer method now. For data formats not supported by ZKMA, the Trusted UI signature screen shows an unknown method with raw HEX data for the user to confirm
If the user wants to create a new wallet, an app needs to clear current wallet first. Clearing the current wallet completely is necessary for its continued security. To that end, clearSeed() will clear all secure data related to the current wallet.
int result = mZKMA.clearSeed(unique_id);
Figure 11. Trusted UI for clearing an existing seed.
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
When you create a new wallet or restore an existing wallet, you will be asked to create the passcode to protect seed access. If it is decided that a passcode is inadequate or otherwise needs to be changed, the following function can be used:
int result = mZKMA.changePIN(unique_id);
Figure 12. Trusted UI prompting the user to change their passcode
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
confirmPIN() can be used to prompt for passcode confirmation at any time through the Trusted UI.
int result = mZKMA.confirmPIN(unique_id, resId = 0);
Figure 13. Show the security UI for user to confirm their PIN code
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
unregister() releases the unique id, and it will also release seed data stored by ZKMA. If this function is called, the seed data will also be cleared.
int result = mZKMA.unregister(wallet_name, sha256, unique_id);
To prevent the blocked Trust Zone UI thread, the developer must call this API in a background thread.
This function releases all resources which allocated by ZKMA, and no future ZKMA functions will be executed.
int result = mZKMA.deinit();
Error codes are delivered by return values, and are handled by ZKMA in three different types. Type1 refers to a normal behavior, like E_TEEKM_UI_BACK, E_TEEKM_UI_CANCEL and so on, and can be safely ignored. Type2 is a silent error, and will not show any UI prompt by ZKMA, and apps are required to handle this without a ZKMA error prompt, such as E_TEEKM_SEED_NOT_FOUND, E_TEEKM_TIME_TIMEOUT, E_SDK_ROM_SERVICE_TOO_OLD, E_SDK_ROM_TZAPI_TOO_OLD and so on. If the error code is neither type1 nor type2, ZKMA will handle it through the default error dialog on the screen.
Figure 14. Default error dialogs if an error is detected by ZKMA
All error codes are defined in RESULT.java and are named accordingly.
ZKMA supports message signing. If a message contains non-ASCII characters, the Trusted UI will show hex data instead of a readable string.
int result = mZKMA.signMessage(unique_id, coin_type, strJson, ByteArrayHolder);
A Ethereum sample JSON message format for signing, the data field in JSON can only support ASCII string. In the sample JSON data field, “48656c6c6f” is HEX string data of “Hello”. In other word, you must convert your ASCII string to HEX string data first, then put it into the data TAG for composing the input strJson parameter. About version TAG, this is implemented by EIP191 and must set to 0x45.
{
"path": "m/44'/60'/0'/0/0",
"message" : {
"version": "45",
"data": "48656c6c6f"
}
}
Message data in JSON should contain version and data tags.
Figure 15. Trusted UI message signing confirmation
This is a simple demo for API usage.
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private static Activity sActivity;
HtcWalletSdkManager mZKMA;
int intValue = RESULT.UNKNOWN;
String mZKMA_version;
String mApi_Version;
long uid;
String wallet_name = "testZKMA";
String sha256 = "24681012579";
Handler mHandler;
HandlerThread mHandlerThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sActivity = this;
setContentView(R.layout.activity_main);
mZKMA_version = com.htc.htcwalletsdk.BuildConfig.VERSION_NAME;
((TextView)findViewById(R.id.text1)).setText(mZKMA_version);
}
public void demoAPIs(View v){ // 1. call ZKMA APIs in background thread
if( mZKMA == null ) {
mZKMA = HtcWalletSdkManager.getInstance();
}
if( mHandlerThread == null ) {
mHandlerThread = new HandlerThread("ZKMA_BackgroundThread");
}
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
mHandler.post(apiRunnable);
}
final Runnable apiRunnable = new Runnable() {
@Override
public void run() {
try {
// 1-1. init
intValue = mZKMA.init(getApplicationContext());
// 1-2. getApiVersion
mApi_Version = mZKMA.getApiVersion();
// 1-3. register
uid = mZKMA.register(wallet_name, sha256);
// 1-4. call ZKMA APIs, ex: create Seed
intValue = mZKMA.createSeed(uid);
} catch (Exception e) {
e.printStackTrace();
}
}
};
}