*These articles are my personal development logs, if I have wrong comprehension on some stuffs, feel free to let me know

**Get to know more about the blockchain technology we are developing: Zoobc in https://zoobc.com/

part 1: setting up development environment
part 2: Creating menu screens manually
part 3: Communicating with Ledger

So far, we have talked a lot stuffs until we can communicate with `Ledger Nano S`. But what else we can do with it?
In this article, I will explain how we can make use the Ledger specialty: cryptographic operations.

Ledger under actually supports a lot of cryptographic capabilities: hashing (with various algorithms like Sha256, sha3, keccak256, etc), deriving private key and public key from seed (with various algorithms and curves), and also signing data. But in this article, I will only explain about the algorithms that I have implemented:

  1. Deriving public key and private key with slip10 and ed25519
  2. Signing data with ed25519
  3. Hashing data with SHA3–256

But before I explain about them, I would like to give a note about a very important point when you perform with internal Ledger Nano S SDK capabilities: pay attention on your input data and variable you use. It may seem trivial, but we can not easily debug internal implementation of methods in the SDK, and a slight change in the input/difference data than we expect will result a completely different results for the topics we are covering. Paying attention to the variable your are using is also very important too, whether our variable of suitable type and size. Also we should also know how the function make use of the variable we are passing (evil pointers behavior, LoL).
Mind that I also simplify a lot of complications like copying the data from APDU buffer, getting data length, getting derivation index etc. If I don’t do that, this article will be much harder to understand.

Deriving public key and private key with slip10 and ed25519

Ledger stores the seed in it’s secure memory and we can use that seed to derive public key and private key as we need. The blockchain we are working on is using ED25519 but unfortunately ED25519 doesn’t support derivation natively, so we need to use SLIP10 to derive seed into public key and private key compatible to ED25519.

Using cryptographic methods in Ledger is actually quite straight forward, thanks to the SDK. But unfortunately, no complete documentation or tutorial to use them properly. This is one of the reason why I created this articles actually.

Deriving public key from the seed has this sequence of procedure:

  1. Get the seed and configure it with our intended algorithm (in this case `HDW_ED25519_SLIP10`)
  2. Initialize the private key and public key instance with our preferred curve (in this case `CX_CURVE_Ed25519`). This instance will contain the information required by functions that uses these variables (in this case I supply the curve information `CX_CURVE_Ed25519` and variable to store these instance).
  3. Generate the key pairs (public key & private key)
  4. Resetting variables that stores secret informations like seed and private key. This is to avoid the data being leak and used by the next operations.
  5. Reformat public key (because ledger has it’s own format of return value with total 64 bytes, where the first 32 bytes are the public key and code byte https://crypto.stackexchange.com/questions/72134/raw-curve25519-public-key-points)
static void process_get_public_key(unsigned int accIndex)
{
cx_ecfp_public_key_t publicKey;
uint8_t keySeed[32];
cx_ecfp_private_key_t pk;
// bip32 path for zoobc: 44'/883'/n'
uint32_t bip32Path[] = {44 | 0x80000000, 883 | 0x80000000, index | 0x80000000};
// getting the seed to derive and configuring it with SLIP10
os_perso_derive_node_bip32_seed_key(HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32Path, 3, keySeed, NULL, (unsigned char *)"ed25519 seed", 12);
// initializing the private key and public key instance
// with selected curve ED25519
cx_ecfp_init_private_key(CX_CURVE_Ed25519, keySeed, sizeof(keySeed), &pk);
cx_ecfp_init_public_key(CX_CURVE_Ed25519, NULL, 0, publicKey);
// generating the key pair
cx_ecfp_generate_pair(CX_CURVE_Ed25519, publicKey, &pk, 1);
// resetting the variables to avoid leak
os_memset(keySeed, 0, sizeof(keySeed));
os_memset(&pk, 0, sizeof(pk));
// reversing the public key and changing the last byte
for (int i = 0; i < 32; i++)
{
G_io_apdu_buffer[i] = publicKey->W[64 - i];
}
if (publicKey->W[32] & 1)
{
G_io_apdu_buffer[31] |= 0x80;
}
// appending success status
THROW(0x9000);
}

Signing data with ed25519

Now it’s an important capability of Ledger. The main purpose of using Ledger is that so the secret information stays in Ledger and not being able to be read by outside entities. Signing data inside the Ledger makes sure we make use of this capability. With this we can sign data without ever exposing our secret informations (like seed or private key), it will just output the result of the signing.

The main procedures of signing a data actually has similar sequence with producing public key (1–4). The difference is just instead of taking public key out of the result, we take the private key to be used to sign the data. The additional sequence needed to sign a data is signing the data with private key using method `cx_eddsa_sign`.

static void process_sign_data(const uint8_t *data, unsigned int dataLength, unsigned int derivationPathIndex)
{
unsigned int txDataLength;
uint32_t length = 0;

cx_ecfp_public_key_t publicKey;
cx_ecfp_private_key_t pk;
uint8_t keySeed[32];
// bip32 path for zoobc: 44'/883'/n'
uint32_t bip32Path[] = {44 | 0x80000000, 883 | 0x80000000, index | 0x80000000};
// getting the seed to derive and configuring it with SLIP10
os_perso_derive_node_bip32_seed_key(HDW_ED25519_SLIP10, CX_CURVE_Ed25519, bip32Path, 3, keySeed, NULL, (unsigned char *)"ed25519 seed", 12);
// initializing the private key and public key instance
// with selected curve ED25519
cx_ecfp_init_private_key(CX_CURVE_Ed25519, keySeed, sizeof(keySeed), &pk);
cx_ecfp_init_public_key(CX_CURVE_Ed25519, NULL, 0, publicKey);
// generating the key pair
cx_ecfp_generate_pair(CX_CURVE_Ed25519, publicKey, &pk, 1);
// signing the data
length = cx_eddsa_sign(&pk, CX_LAST, CX_SHA512, data, dataLength, NULL, 0, dst, NULL);
// resetting the variables to avoid leak
os_memset(keySeed, 0, sizeof(keySeed));
os_memset(&pk, 0, sizeof(pk));
// appending special code that indicates success status
G_io_apdu_buffer[length++] = 0x90;
G_io_apdu_buffer[length++] = 0x00;
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length);
// Return to the main screen.
ui_screen_menu_entry();
}

Hashing data with SHA3–256

Hash is an important functionality to provide a way to easily credibility of data with just a few bytes of data. In this case I am hashing data with Hash3–256.

Hashing data with function provided by SDK is easy. We only need to:

  1. Initialize the hash context. This context will contain informations required by our hash function (like in this case, it’s the length of hash we want to produce is 256 )
  2. Hash the data with `cx_hash` and supplying the hash context, data, and output buffer.
#define HASH256_LENGTH 32 // 32 bytesstatic void process_hash_data(unsigned int hashResult)
{
int length = HASH256_LENGTH;
uint8_t hashResult[HASH256_LENGTH];
// initializing hash context
cx_sha3_init(&hashContext, 256);
// hashing the data
cx_hash((cx_hash_t *)hashContext, CX_LAST, G_io_apdu_buffer + APDU_OFFSET_DATA_START, hashResult, hashResult);
// moving the hash result to output buffer
os_memmove(G_io_apdu_buffer, hashResult, length);
// appending status indicating successful operation
G_io_apdu_buffer[length++] = 0x90;
G_io_apdu_buffer[length++] = 0x00;
io_exchange(CHANNEL_APDU | IO_RETURN_AFTER_TX, length);
}

Important points

With my explanation above, now you should be able to these 2 operations. If you are doing these operations with different algorithm or curve, you might have to have a slight change in the parameters passed, or even need to use another function supporting your curve or algorithm.

Checkout the function documentation in the SDK

SDK usually provides documentation of what parameters to pass to its function. Even though you don’t get information of how the functions are performed, making sure you are passing proper parameter should be enough. Besides, Ledger said they are performing operations they provide according to standard for each operations.

Be careful with your varible (pointers)

Most of functions in SDK usually requires data to be passed in the form of pointer of byte array. This is fine so far. But because we don’t get to know the detail operation of each function, we may caught our selves off guard.

We know the flexibility of pointer to a variable is that we can change the content of that variable as long as we have the correct pointer, but on the other side this can be dangerous as well.

The signing algorithm I used in the above topic apparently performs like this:

  1. Process the data and produce the first 32 bytes of the signature, then WRITE DIRECTLY TO THE DESTINATION BUFFER
  2. Process the data again then write the latter 32 bytes to the destination buffer

Because I didnt know this before, during implementation I supplied the same buffer for input data and output buffer. Then I hit a roadblock where the signature Ledger produces always valid only for the first 32 bytes, and the latter 32 bytes alwasys wrong. After digging into documentation of how ed25519 performs, I noticed that the 2 parts of the signature is written separately. So the algorithm in (1) changes some portion of data I am passing to be signed, consequently the function in (2) always sign different data compared to data signed by (1). And unfortunately the function doesn’t copy the data to be processed to other varible first before running the signing process.

This causes me to waste a lot of time researching and debugging. Turns out it’s basically my mistake in using the buffer. And I found leads about it in NO WHERE, I had to wiggle with my code so many times to find this problem. Even the Slack community could not figure out this problem as I consulted with them initially. And I figured out about this problem also accidently, it wasn’t my initial intention to separate the data input and output buffer, but it turns out to solve the problem. LoL.

Always pass correct data length

This important point is to avoid buffer overflow problem that we all know. I am just repeating my self just to be sure. LoL.

Initialize cryptographic instance properly

Most of the cryptographic method needs a context of its own. I think it’s just how Ledger Nano S SDK is designed to make the usage of cryptographic functions more efficient.

So here’s the end of this part. Now you would be able to perform those cryptographic capalbility with your Ledger Nano S. You would only need to create a script to supply the data you need for the functions.

I might have skipped a lot of detail in this article, but it’s for the sake of easier to understand. If necessary I will create another article to explain about some detail points that needs extra explanation.

Thank you for reading this article. I hope it can be useful for you, especially who are currently facing problem in developing application for Ledger Nano S and wishing there is explanation or at least leads to problem you are facing. Just the way I did. LoL.

Cheers!! 🎉🎉

--

--