Ledger Application API
Reference Repository: Archethic BOLOS App
[TOC]
GET VERSION
Get version returns the version of Device Application on Ledger
Function Signature handleGetVersion(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, volatile unsigned int *flags)
p1 -> APDU Parameter (Not Concerned in our context)
p2 -> APDU Parameter (Not Concerned in our context)
*dataBuffer -> NULL (Not required for getting version)
dataLength -> Length of the dataBuffer
*flags -> Modes (respond immediately(sync), process and respond(async))
There is a general buffer G_io_apdu_buffer (already in ledger SDK) for both input and output, we copy version to this buffer from APPVERSION which is defined in Makefile. As we only need integer, so we subtract '0' from the APPVERSION[0 | 2 | 4]. APPVERSION and APPNAME are defined in the makefile of the project.
io_exchange_with_code(SW_OK, 3)
- It adds status at the end of the Buffer (G_io_apdu_buffer).
- **SW_OK** (0x9000) adds status code at the end of the buffer.
- 3 is the length of the data in the buffer.
GET DEVICE ORIGIN PUBLIC KEY
Gets the hardware(device origin) public key from ledger device.
It returns the public key derived from the hardware ledger device seed (HARDWARE SEED).
- Function Signature
handleGetPublicKey(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, volatile unsigned int *flags)
- *flags |= IO_ASYNCH_REPLY; This is added to get response from the user of HID. So needed to be set to the ASYNC.
- getOriginPublicKey(cx_ecfp_public_key_t *publicKey)
This calls deriveArchethicKeyPair and return it.
deriveArchethicKeyPair(CX_CURVE_SECP256K1, 650, 0xffff, 0, NULL, 0, NULL, publicKey);
- First Param** is the curve type which is fixed in our case.
- Second Param** is the coin type which is defined for the [UCO Token]()
- 3rd Param** is for the account which account to get, which is defined as:
- $account =$ Depends on the intended use of the key.
- Currently fixed values: 0xFFFF for $o_{key}$, 0x0000 for a simple $w_{key}$
- 4th Param** is address index
- 5th Param** is masterSeed which is set to NULL because we need the device origin master seed.
- 6th Param** is the master seed Length
- 7th param** is the structure to store public key
Function Signature deriveArchethicKeyPair(cx_curve_t curve, uint32_t coin_type, uint32_t account, uint32_t address_index, uint8_t *masterSeed, uint8_t masterSeedLen, cx_ecfp_private_key_t *privateKey, cx_ecfp_public_key_t *publicKey)
This function first checks the curve type of the
cx_curve_t
, which sets the mode for the which it needs to perform operation.Next depending upon the account type as defined in the specification (0xffff | 0x0000) it derives the private key from the masterseed.
- When the account type is
0xffff
then os_perso_derive_node_with_seed() function is called which derives the key from the ledger hardware origin key. - When account type is
0x0000
then it derives the rawPrivateKey from the from archethic_derive_with_seed_key() function which derives the key as per provided params.
- When the account type is
Next it checks if there is a publicKey
- If that is the case then it derives the corresponding public key and generates from the walletPrivateKey
If there is privateKey the it simple returns the privateKey.
As Public Key is in format: [curve_type, device_origin, 04xy]
02 -> Curve Type 04 -> Device Origin 04 -> uncompressed
X -> 52e77c79bea0f84e2d9c40356932fb0dd31688c23d6b6e9ac99d2869dc407692
Y -> 1e731fb84d0a094c6273fffed68147c381a273c83868cdeb9b81c7ec790c6d38
Since raw private key is in bytes it needs to be converted which is done by the function cx_ecfp_init_private_key(curve, rawPrivateKey, 32, &walletPrivateKey).
GET ARCHETHIC ADDRESS
Return the Archethic address from the ledger including curve type in the start
INS = 04 Lc = Data Length address_index = 4 bytes (bip44) ewkaes = encrypted wallet key
- Function Signature
handleGetAddress(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, volatile unsigned int *flags)
- First 4 bytes are address index hence we store then in
address_index
from the data buffer (Converting them according to thier byte ordering.) - We performECDH to get the secret pointX on the curve,
- Fn Signature performECDH(uint8_t *ephPublicKey, uint8_t ephPublicKeySize, uint8_t *ecdhPointX)
- This derives an Archethic keypair first with function
deriveArchethicKeyPair
and gets originPrivateKey - The performs ecdh with function
cx_ecdh
with the originPrivateKey and takes publickey and finds the ecdhPointX cx_ecdh(&originPrivateKey, CX_ECDH_X, ephPublicKey, ephPublicKeySize, ecdhPointX, 32);
- publicKey is taken from the
dataBuffer
- Now we decrypt the wallet with the ecdhPointX
- Fn Signature decryptWallet(ecdhPointX, sizeof(ecdhPointX), dataBuffer, dataLength, g_wallet.encodedWallet, &g_wallet.walletLen);
- Since we have moved pointer we now only have
auth_tag
16 bytes and enc($wk_{aes}$) 32bytes- Inside Decrypt wallet we first calculate the
aes_key_iv_tag
from the ecdhPointX by double hashing it from the sha512 function $sha512(sha512(ecdh_secret\ or\ PointX))$. - we get 3 parts from above double hashing in 64 bytes which are
- aes_key [32bytes]
- iv [16bytes]
- auth_seed [16bytes]
- Now we get sha256 has of auth_seed in
auth_key
- And now we do hmac(authkey , enc($wk{aes}$)) and get
auth_tag
which are first 16 bytes from hmac result. $Rest\ 16\ bytes\ are\ reserved$ - Now we compare if auth_tag equals to auth_tag supplied in the
dataBuffer
- If if doesn't return
0xBADDECODE
- If it does
- Decrypt the wallet and return the encoded wallet
- If if doesn't return
- Inside Decrypt wallet we first calculate the
- Now get the BIP44Paths from the encoded wallet according to specificaton
- Fn Signature getBIP44Path(address_index, g_wallet.encodedWallet, g_wallet.walletLen, 0, g_bip44_path, &bip44pathlen);
- This does according to address index which were first 4 bytes of
dataBuffer
- This returns the bip44Path in
char* string_bip_44
- Now Generate Archethic Address from the Encoded Wallet and set as required.
- Fn Signature generateArchethicAddress(uint8_t hash_type, uint32_t address_index, uint8_t *encoded_wallet, uint8_t *wallet_len, uint32_t sequence_no, uint8_t *address, uint8_t *address_len)
- For now only support hash_type
sha256
- Returns address in
uint8_t *address
- It generates key from the encoded wallet and storesit in
cx_ecfp_public_key_t
structure - Fn Signature generateKeyFromWallet(address_index, encoded_wallet, wallet_len, sequence_no, &curve_type, NULL, &publicKey)
- Derives Archethic Keypair depending on the supplied params:
- coin_type
- account
- curve
- address_index
- publicKey
- privateKey
- Derives Archethic Keypair depending on the supplied params:
- It generates key from the encoded wallet and storesit in
- First 4 bytes are address index hence we store then in
SIGN TRANSACTION
Returns APDU response as Transaction Hash | Public Key | ASN_DER_SIGN
in success
INS = 08 address_index = 4 bytes (bip44)
- Funcion Signature:
handleSignHash(uint8_t p1, uint8_t p2, uint8_t *dataBuffer, uint16_t dataLength, volatile unsigned int *flags)
- First 4 bytes are address index hence we store then in
address_index
from the data buffer (Converting them according to thier byte ordering.) - Then we check for addresslength
- if %2 == 0 then SHA256 or SHA3_256 and length = 34
- if %2 == 1 then SHA512 or SHA3_512 and length = 66
- We copy the recieverAddress upto the addressLength in previous step to
g_tx.recieverAddr
- Then get next 8 bytes as amount and convert it to big endian
- Then for display convert it to float and set in
g_amount
for display
- Then for display convert it to float and set in
- performECDH to get ecdhPointX
- We performECDH to get the secret pointX on the curve,
- Fn Signature performECDH(uint8_t *ephPublicKey, uint8_t ephPublicKeySize, uint8_t *ecdhPointX)
- This derives an Archethic keypair first with function
deriveArchethicKeyPair
and gets originPrivateKey - The performs ecdh with function
cx_ecdh
with the originPrivateKey and takes publickey and finds the ecdhPointX cx_ecdh(&originPrivateKey, CX_ECDH_X, ephPublicKey, ephPublicKeySize, ecdhPointX, 32);
- We performECDH to get the secret pointX on the curve,
- Now we decrypt the wallet with the ecdhPointX
- Fn Signature decryptWallet(ecdhPointX, sizeof(ecdhPointX), dataBuffer, dataLength, g_wallet.encodedWallet, &g_wallet.walletLen);
- Since we have moved pointer we now only have
auth_tag
16 bytes and enc($wk_{aes}$) 32bytes- Inside Decrypt wallet we first calculate the
aes_key_iv_tag
from the ecdhPointX by double hashing it from the sha512 function $sha512(sha512(ecdh_secret\ or\ PointX))$. - we get 3 parts from above double hashing in 64 bytes which are
- aes_key [32bytes]
- iv [16bytes]
- auth_seed [16bytes]
- Now we get sha256 has of auth_seed in
auth_key
- And now we do hmac(authkey , enc($wk{aes}$)) and get
auth_tag
which are first 16 bytes from hmac result. $Rest\ 16\ bytes\ are\ reserved$ - Now we compare if auth_tag equals to auth_tag supplied in the
dataBuffer
- If if doesn't return
0xBADDECODE
- If it does
- Decrypt the wallet and return the encoded wallet
- If if doesn't return
- Inside Decrypt wallet we first calculate the
- Now Generate Archethic Address from the Encoded Wallet and set as required.
- Fn Signature generateArchethicAddress(uint8_t hash_type, uint32_t address_index, uint8_t *encoded_wallet, uint8_t *wallet_len, uint32_t sequence_no, uint8_t *address, uint8_t *address_len)
- For now only support hash_type
sha256
- Returns address in
uint8_t *address
- Now get the BIP44Paths from the encoded wallet according to specificaton
- Fn Signature getBIP44Path(address_index, g_wallet.encodedWallet, g_wallet.walletLen, 0, g_bip44_path, &bip44pathlen);
- This does according to address index which were first 4 bytes of
dataBuffer
- This returns the bip44Path in
char* string_bip_44
- Next we calulate the transactionHash
- Function Signature getTransactionHash(uint8_t *senderAddr, uint8_t senderAddrLen, uint8_t *receiveAddr, uint8_t receiveAddrLen, uint8_t *amount, uint8_t *txHash, uint8_t *txHashLen)
- Format for Transaction Hash Generation
- tx_version | senderAddr | tx_type | code_size | content_size | ownership_length | total_uco_transfers | recieverAddr | amount | total_token_transfers | recipients
- We apply
cx_hash_sha256()
and return the hash intxHash
- We prompt user in GUI to approve the transaction
- Once approve we performECDSA on the transactionHash
- Function Signature performECDSA(uint8_t *txHash, uint8_t txHashLen, uint32_t address_index, uint8_t *encoded_wallet, uint8_t *wallet_len, uint8_t sequence_no, uint8_t *asn_sign, uint8_t *sign_len)
- We generateKey from Wallet Similar to when getting address and give back
publicKey
andprivateKey
- We copy curve_type on asn_sign buffer
- We set 0 to signify onchain wallet
- We copy the publicKey on asn_sign buffer
- We sign the txnHash with the privateKet and append signature to asn_sign buffer
- We increase sign_len by publicKey length + 2 bytes
- Once signature is performed we copy the txnHash to the
G_io_apdu_buffer
- Then, we copy the contents of encodedWallet on which we have overwritten the signature to the
G_io_apdu_buffer
- And we return the APDU with SW_OK and total length as
txnhash length + walletLen
- First 4 bytes are address index hence we store then in