Skip to content

Reversible Examples

To provide better orientation, we highlight specific properties of each example, namely:

  • Reversibility,
  • Model compatibility,
  • Level of complexity (🐣 -> 🐀 -> πŸ“).

The source code for the examples can be found in the examples/ directory and also within this document inside "Source code" boxes.

Tip

You can copy the example code using the icon located in the top-right corner of each source code listing.

lt_ex_hello_world.c

  • βœ… Reversible
  • βœ… Compatible with the model
  • 🐣 Level: Basic

This example demonstrates the basic libtropic API and can be used to verify that the chip works correctly. In this example, you will learn about the following functions:

  • lt_init(): function used to initialize context for communication with the TROPIC01,
  • lt_verify_chip_and_start_secure_session(): helper function to start Secure Session and allow L3 communication,
  • lt_ping(): L3 command to verify communication with the TROPIC01,
  • lt_session_abort(): L3 command to abort Secure Session,
  • lt_deinit(): function used to deinitialize context.
Source code
/**
 * @file lt_ex_hello_world.c
 * @brief Establishes Secure Session and executes Ping L3 command.
 * @author Tropic Square s.r.o.
 *
 * @license For the license see file LICENSE.txt file in the root directory of this source tree.
 */

#include "libtropic.h"
#include "libtropic_common.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"

/** @brief Message to send with Ping L3 command. */
#define PING_MSG "This is Hello World message from TROPIC01!!"
/** @brief Size of the Ping message, including '\0'. */
#define PING_MSG_SIZE 44

int lt_ex_hello_world(lt_handle_t *h)
{
    LT_LOG_INFO("======================================");
    LT_LOG_INFO("==== TROPIC01 Hello World Example ====");
    LT_LOG_INFO("======================================");

    lt_ret_t ret;

    LT_LOG_INFO("Initializing handle");
    ret = lt_init(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Starting Secure Session with key %d", (int)TR01_PAIRING_KEY_SLOT_INDEX_0);
    ret = lt_verify_chip_and_start_secure_session(h, sh0priv, sh0pub, TR01_PAIRING_KEY_SLOT_INDEX_0);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to start Secure Session with key %d, ret=%s", (int)TR01_PAIRING_KEY_SLOT_INDEX_0,
                     lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    uint8_t recv_buf[PING_MSG_SIZE];
    LT_LOG_INFO("Sending Ping command with message:");
    LT_LOG_INFO("\t\"%s\"", PING_MSG);
    ret = lt_ping(h, (const uint8_t *)PING_MSG, recv_buf, PING_MSG_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Ping command failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    LT_LOG_INFO("Message received from TROPIC01:");
    LT_LOG_INFO("\t\"%s\"", recv_buf);
    LT_LOG_LINE();

    LT_LOG_INFO("Aborting Secure Session");
    ret = lt_session_abort(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Deinitializing handle");
    ret = lt_deinit(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
        return -1;
    }

    return 0;
}

lt_ex_show_chip_id_and_fwver.c

  • βœ… Reversible
  • βœ… Compatible with the model
  • 🐣 Level: Basic

Demonstrates how to read and display the chip’s unique ID and firmware version information (bootloader, application and SPECT firmware versions). You will learn about following functions:

  • lt_reboot(): L2 request to reboot to either application or maintenance mode,
  • lt_get_info_riscv_fw_ver(), lt_get_info_spect_fw_ver(): L2 requests to read CPU and SPECT firmware versions,
  • lt_get_info_chip_id(): L2 request to read chip identification (e.g., serial number),
Source code
/**
 * @file lt_ex_show_chip_id_and_fwver.c
 * @name Show chip ID and firmware versions
 * @brief This example shows how to read TROPIC01's chip ID and firmware versions
 * @note We recommend reading TROPIC01's datasheet before diving into this example!
 *
 * @author Tropic Square s.r.o.
 *
 * @license For the license see file LICENSE.txt file in the root directory of this source tree.
 */

#include <inttypes.h>

#include "libtropic.h"
#include "libtropic_common.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"
#include "string.h"

int lt_ex_show_chip_id_and_fwver(lt_handle_t *h)
{
    LT_LOG_INFO("=============================================================");
    LT_LOG_INFO("==== TROPIC01 show chip ID and firmware versions example ====");
    LT_LOG_INFO("=============================================================");

    // This variable is reused on more places in this example to store different firmware versions
    uint8_t fw_ver[TR01_L2_GET_INFO_RISCV_FW_SIZE] = {0};

    lt_ret_t ret = lt_init(h);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    // First we check versions of both updateable firmwares. For this we need to be in APPLICATION mode.
    // If there are valid firmwares, chip will execute them on boot. In any case we will try to reboot into application,
    // in case chip would be in maintenance mode (executing bootloader)
    LT_LOG_INFO("Rebooting into APPLICATION mode to check FW versions");
    ret = lt_reboot(h, TR01_REBOOT);
    if (ret != LT_OK) {
        LT_LOG_ERROR("lt_reboot() failed, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    if (h->l2.mode == LT_TR01_APP_MODE) {
        // App runs so we can see what firmwares are running
        // Getting RISCV app firmware version
        LT_LOG_INFO("Reading RISC-V FW version");
        ret = lt_get_info_riscv_fw_ver(h, fw_ver);
        if (ret == LT_OK) {
            LT_LOG_INFO("Chip is executing RISC-V application FW version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8
                        "    (+ .%02" PRIX8 ")",
                        fw_ver[3], fw_ver[2], fw_ver[1], fw_ver[0]);
        }
        else {
            LT_LOG_ERROR("Failed to get RISC-V FW version, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }

        LT_LOG_INFO("Reading SPECT FW version");
        ret = lt_get_info_spect_fw_ver(h, fw_ver);
        if (ret == LT_OK) {
            LT_LOG_INFO("Chip is executing SPECT firmware version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8
                        "    (+ .%02" PRIX8 ")",
                        fw_ver[3], fw_ver[2], fw_ver[1], fw_ver[0]);
        }
        else {
            LT_LOG_ERROR("Failed to get SPECT firmware version, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }
    }
    else {
        LT_LOG_ERROR("Chip couldn't get into APP mode, APP and SPECT firmwares in fw banks are not valid");
    }
    LT_LOG_LINE();

    LT_LOG_INFO("Rebooting into MAINTENANCE mode to check bootloader version and fw bank headers");
    ret = lt_reboot(h, TR01_MAINTENANCE_REBOOT);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to reboot into MAINTENANCE mode, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    if (h->l2.mode == LT_TR01_MAINTENANCE_MODE) {
        LT_LOG_INFO("Reading RISC-V FW version (during maintenance chip actually returns bootloader version):");
        ret = lt_get_info_riscv_fw_ver(h, fw_ver);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed to get RISC-V FW version, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }

        LT_LOG_INFO("Bootloader version: %02" PRIX8 ".%02" PRIX8 ".%02" PRIX8 "    (+ .%02" PRIX8 ")", fw_ver[3] & 0x7f,
                    fw_ver[2], fw_ver[1], fw_ver[0]);
        LT_LOG_LINE();

        LT_LOG_INFO("Reading and printing headers of all 4 FW banks:");
        ret = lt_print_fw_header(h, TR01_FW_BANK_FW1, printf);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed to print TR01_FW_BANK_FW1 header, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }
        ret = lt_print_fw_header(h, TR01_FW_BANK_FW2, printf);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed to print TR01_FW_BANK_FW2 header, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }
        ret = lt_print_fw_header(h, TR01_FW_BANK_SPECT1, printf);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed to print TR01_FW_BANK_SPECT1 header, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }
        ret = lt_print_fw_header(h, TR01_FW_BANK_SPECT2, printf);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed to print TR01_FW_BANK_SPECT2 header, ret=%s", lt_ret_verbose(ret));
            lt_deinit(h);
            return -1;
        }
    }
    else {
        LT_LOG_ERROR("Chip couldn't get into MAINTENANCE mode");
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    struct lt_chip_id_t chip_id = {0};

    LT_LOG_INFO("Reading Chip ID:");
    ret = lt_get_info_chip_id(h, &chip_id);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to get chip ID, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    ret = lt_print_chip_id(&chip_id, printf);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to print chip ID, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    LT_LOG_INFO("Deinitializing handle");
    ret = lt_deinit(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
        return -1;
    }

    return 0;
}

lt_ex_hello_world_separate_API.c

  • βœ… Reversible
  • βœ… Compatible with the model
  • 🐀 Level: Moderate

Functionally similar to lt_ex_hello_world.c, but it uses distinct API calls for incoming and outgoing data. This approach is useful for secure, tunneled communication, such as during chip provisioning in a factory.

You will learn about low-level API functions used to process outgoing and incoming data. For example:

  • lt_out__session_start(): prepare Handshake_Req L2 request (for Secure Session establishment),
  • lt_l2_send(): send L2 request,
  • lt_l2_receive(): receive L2 response.
  • lt_in__session_start(): process L2 response to the Handshake_Req,
Source code
/**
 * @file lt_ex_hello_world_separate_API.c
 * @brief Establishes Secure Session and executes Ping L3 command using separated API.
 * @author Tropic Square s.r.o.
 *
 * @license For the license see file LICENSE.txt file in the root directory of this source tree.
 */

#include "libtropic.h"
#include "libtropic_examples.h"
#include "libtropic_l2.h"
#include "libtropic_l3.h"
#include "libtropic_logging.h"

/** @brief Message to send with Ping L3 command. */
#define PING_MSG "This is Hello World message from TROPIC01!!"
/** @brief Size of the Ping message, including '\0'. */
#define PING_MSG_SIZE 44

int lt_ex_hello_world_separate_API(lt_handle_t *h)
{
    LT_LOG_INFO("=========================================================");
    LT_LOG_INFO("====  TROPIC01 Hello World with Separate API Example ====");
    LT_LOG_INFO("=========================================================");

    lt_ret_t ret;

    LT_LOG_INFO("Initializing handle");
    ret = lt_init(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Getting Certificate Store from TROPIC01");
    uint8_t cert1[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE], cert2[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE],
        cert3[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE], cert4[TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE];
    struct lt_cert_store_t store
        = {.certs = {cert1, cert2, cert3, cert4},
           .buf_len = {TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE, TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE,
                       TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE, TR01_L2_GET_INFO_REQ_CERT_SIZE_SINGLE}};
    ret = lt_get_info_cert_store(h, &store);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to get Certificate Store, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    // Get only stpub, we don't verify certificate chain here
    LT_LOG_INFO("Getting stpub key from Certificate Store");
    uint8_t stpub[TR01_STPUB_LEN];
    ret = lt_get_st_pub(&store, stpub);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to get stpub key, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    //---------------------------------------------------------------------------------------//
    // Separated API calls for starting a secure session:
    lt_host_eph_keys_t host_eph_keys = {0};

    // Initialize session from a server side by creating host_eph_keys->ehpriv and host_eph_keys->ehpub,
    // l2 request is prepared into handle's buffer (h->l2_buff)
    LT_LOG_INFO("Executing lt_out__session_start()...");
    ret = lt_out__session_start(h, TR01_PAIRING_KEY_SLOT_INDEX_0, &host_eph_keys);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_out__session_start() failed, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    // handle's buffer (h->l2_buff) now contains data which must be transferred over tunnel to TROPIC01

    // Following l2 functions are called on remote host
    LT_LOG_INFO("Executing lt_l2_send()...");
    ret = lt_l2_send(&h->l2);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_l2_send() failed, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("Executing lt_l2_receive()...");
    ret = lt_l2_receive(&h->l2);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_l2_receive() failed, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    // Handle's buffer (h->l2_buff) now contains data which must be transferred over tunnel back to the server

    // Once data are back on server's side, bytes are copied into h->l2_buff
    // Then following l2 function is called on server side
    // This function establishes gcm contexts for a session
    LT_LOG_INFO("Executing lt_in__session_start()...");
    ret = lt_in__session_start(h, stpub, TR01_PAIRING_KEY_SLOT_INDEX_0, sh0priv, sh0pub, &host_eph_keys);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_in__session_start failed, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }
    LT_LOG_LINE();

    // Now we can use lt_ping() to send a message to TROPIC01 and receive a response, this is done with separate API
    // calls
    uint8_t recv_buf[PING_MSG_SIZE];
    LT_LOG_INFO("Executing lt_out__ping() with message:");
    LT_LOG_INFO("\t\"%s\"", PING_MSG);
    ret = lt_out__ping(h, (const uint8_t *)PING_MSG, PING_MSG_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_out__ping failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Executing lt_l2_send_encrypted_cmd()...");
    ret = lt_l2_send_encrypted_cmd(&h->l2, h->l3.buff, h->l3.buff_len);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_l2_send_encrypted_cmd failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("Executing lt_l2_recv_encrypted_res()...");
    ret = lt_l2_recv_encrypted_res(&h->l2, h->l3.buff, h->l3.buff_len);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_l2_recv_encrypted_res failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Executing lt_in__ping()...");
    ret = lt_in__ping(h, recv_buf, PING_MSG_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_in__ping failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Message received from TROPIC01:");
    LT_LOG_INFO("\t\"%s\"", recv_buf);
    LT_LOG_LINE();

    LT_LOG_INFO("Aborting Secure Session");
    ret = lt_session_abort(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Deinitializing handle");
    ret = lt_deinit(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
        return -1;
    }

    return 0;
}

lt_ex_macandd.c

  • βœ… Reversible
  • βœ… Compatible with the model
  • πŸ“ Level: Advanced

This example illustrates the MAC-and-destroy feature. You will learn about the following functions:

  • lt_hmac_sha256(): function to compute HMAC based on SHA256,
  • lt_mac_and_destroy(): L3 command to process MAC-and-Destroy operation,
  • lt_r_mem_data_erase(): L3 command to erase R-memory data slot,
  • lt_r_mem_data_write(): L3 command to write R-memory data slot,
  • lt_r_mem_data_read(): L3 command to read R-memory data slot,
  • lt_random_bytes(): function to generate random number using platform's RNG (not TROPIC01's),

In this example, we also define two functions to implement PIN verification functionality: lt_PIN_set() and lt_PIN_check(). You can use these functions as inspiration for your project.

You can find more information in the application note and code comments.

Source code
/**
 * @file lt_ex_macandd.c
 * @brief Example usage of TROPIC01 flagship feature - 'Mac And Destroy' PIN verification engine.
 * For more info please refer to ODN_TR01_app_002_pin_verif.pdf
 * @author Tropic Square s.r.o.
 *
 * @license For the license see file LICENSE.txt file in the root directory of this source tree.
 */

#include <inttypes.h>

#include "libtropic.h"
#include "libtropic_examples.h"
#include "libtropic_logging.h"
#include "string.h"

// Needed to access to lt_random_bytes()
#include "lt_random.h"
// Needed to access HMAC_SHA256
#include "lt_hmac_sha256.h"

/** @brief Last slot in User memory used for storing of M&D related data (only in this example). */
#define R_MEM_DATA_SLOT_MACANDD (511)
/** @brief The size of random data passed during MAC-and-Destroy PIN set */
#define TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE 32u

/** @brief Size of the print buffer. */
#define PRINT_BUFF_SIZE 196

#ifndef MACANDD_ROUNDS
#define MACANDD_ROUNDS 12
#endif

#if (MACANDD_ROUNDS > 12)
#error \
    "MACANDD_ROUNDS must be less than 12 here, or generally than TR01_MACANDD_ROUNDS_MAX. Read explanation at the beginning of this file"
#endif

/** @brief Minimal size of MAC-and-Destroy additional data */
#define MAC_AND_DESTROY_ADD_SIZE_MIN 0
/** @brief Maximal size of MAC-and-Destroy additional data */
#define MAC_AND_DESTROY_ADD_SIZE_MAX 128u
/** @brief Minimal size of MAC-and-Destroy PIN input */
#define MAC_AND_DESTROY_PIN_SIZE_MIN 4u
/** @brief Maximal size of MAC-and-Destroy PIN input */
#define MAC_AND_DESTROY_PIN_SIZE_MAX 8u

/**
 * @brief This structure holds data used by host during MAC and Destroy sequence
 * Content of this struct must be stored in non-volatile memory, because it is used
 * between power cycles
 */
struct lt_macandd_nvm_t {
    uint8_t i;
    uint8_t ci[MACANDD_ROUNDS * TR01_MAC_AND_DESTROY_DATA_SIZE];
    uint8_t t[LT_HMAC_SHA256_HASH_LEN];
} __attribute__((__packed__));

/**
 * @brief Simple XOR "encryption" function. Replace with another encryption algorithm if needed.
 *
 * @param data         32B of data to be encrypted
 * @param key          32B key used for encryption
 * @param destination  Buffer into which 32B of encrypted data will be placed
 */
static void encrypt(const uint8_t *data, const uint8_t *key, uint8_t *destination)
{
    for (uint8_t i = 0; i < 32; i++) {
        destination[i] = data[i] ^ key[i];
    }
}

/**
 * @brief Simple XOR "decryption" function. Replace with another decryption algorithm if needed.
 *
 * @param data         32B of data to be decrypted
 * @param key          32B key used for decryption
 * @param destination  Buffer into which 32B of decrypted data will be placed
 */
static void decrypt(const uint8_t *data, const uint8_t *key, uint8_t *destination)
{
    for (uint8_t i = 0; i < 32; i++) {
        destination[i] = data[i] ^ key[i];  //*(data + i) ^= *(key + i);
    }
}

/**
 * @brief Example function for setting PIN with Mac And Destroy.
 *
 * @details The New PIN Setup procedure takes the user PIN, add data and high entropy master_secret as an input,
 * initializes the scheme slots and returns a 32-byte key final_key as derivative of the master_secret.
 *
 * The MAC-and-Destroy PIN verification scheme uses slots located in the TROPIC01’s flash memory – one slot per
 * PIN entry attempt. These slots are first initialized when a new PIN is being set up.
 * The slots are then invalidated (destroyed) one by one with each PIN entry attempt. When the correct PIN is
 * entered, the slots are initialized again, therefore the PIN entry limit is reset.
 * PIN entry attempt fails if:
 *  * PIN is invalid
 *  * The current slot is not initialized for a given PIN
 *  * The current slot is destroyed by previous invalid PIN entry attempt.
 *
 * There are more ways how to implement Mac And Destroy 'PIN set' functionality, differences could be in way of
 * handling nvm data, number of tries, algorithm used for encryption, etc. This function is just one of the possible
 * implementations of "PIN set".
 *
 * Take it as an inspiration, copy it into your project and adapt it to your specific hw resources.
 *
 * @param h           Device's handle
 * @param master_secret  32 bytes of random data (determines final_key)
 * @param PIN         Array of bytes (size between MAC_AND_DESTROY_PIN_SIZE_MIN and MAC_AND_DESTROY_PIN_SIZE_MAX)
 * representing PIN
 * @param PIN_size    Length of the PIN field
 * @param add         Additional data to be used in M&D sequence (size between MAC_AND_DESTROY_ADD_SIZE_MIN and
 * MAC_AND_DESTROY_ADD_SIZE_MAX). Pass NULL if no additional data should be used.
 * @param add_size    Length of additional data
 * @param final_key      Buffer into which final key will be placed when all went successfully
 * @return lt_ret_t   LT_OK if correct, otherwise LT_FAIL
 */
static lt_ret_t lt_new_PIN_setup(lt_handle_t *h, const uint8_t *master_secret, const uint8_t *PIN,
                                 const uint8_t PIN_size, const uint8_t *add, const uint8_t add_size, uint8_t *final_key)
{
    if (!h || !master_secret || !PIN || (PIN_size < MAC_AND_DESTROY_PIN_SIZE_MIN)
        || (PIN_size > MAC_AND_DESTROY_PIN_SIZE_MAX) || (add_size > MAC_AND_DESTROY_ADD_SIZE_MAX) || !final_key) {
        // add parameter is not checked for NULL, because it can be NULL (handled in the lines below)
        return LT_PARAM_ERR;
    }
    if (h->l3.session_status != LT_SECURE_SESSION_ON) {
        return LT_HOST_NO_SESSION;
    }

    uint8_t add_size_checked = add_size;
    if (!add) {
        add_size_checked = 0;
    }

    // Clear variable for released final_key so there is known data (zeroes) in case this function ended sooner then
    // final_key was prepared
    memset(final_key, 0, TR01_MAC_AND_DESTROY_DATA_SIZE);

    // Variable used during a process of getting a encryption key k_i
    uint8_t v[LT_HMAC_SHA256_HASH_LEN] = {0};
    // Variable used during a process of getting a encryption key k_i
    uint8_t w_i[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
    // Encryption key
    uint8_t k_i[LT_HMAC_SHA256_HASH_LEN] = {0};
    // Variable used to initialize slot(s)
    uint8_t u[LT_HMAC_SHA256_HASH_LEN] = {0};

    // This organizes data which will be stored into nvm
    struct lt_macandd_nvm_t nvm = {0};

    // User is expected to pass not only PIN, but might also pass another data (e.g. HW ID, ...)
    // Both arrays are concatenated and used together as an input for KDF
    uint8_t kdf_input_buff[MAC_AND_DESTROY_PIN_SIZE_MAX + MAC_AND_DESTROY_ADD_SIZE_MAX];
    memcpy(kdf_input_buff, PIN, PIN_size);
    if (!add || add_size_checked == 0) {
        LT_LOG_INFO("No additional data will be used in the following M&D sequence");
    }
    else {
        memcpy(kdf_input_buff + PIN_size, add, add_size_checked);
    }

    // Erase a slot in R memory, which will be used as a storage for NVM data
    LT_LOG_INFO("Erasing R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
    lt_ret_t ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // Store number of attempts
    nvm.i = MACANDD_ROUNDS;
    // Compute tag t = KDF(s, 0x00), save into nvm struct
    // Tag will be later used during lt_PIN_entry_check() to verify validity of final_key
    lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t[]){0x00}, 1, nvm.t);

    // Compute u = KDF(s, 0x01)
    // This value will be sent through M&D sequence to initialize a slot
    lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t[]){0x01}, 1, u);

    // Compute v = KDF(0, PIN||A) where 0 is all zeroes key
    const uint8_t zeros[32] = {0};
    lt_hmac_sha256(zeros, sizeof(zeros), kdf_input_buff, PIN_size + add_size_checked, v);

    for (int i = 0; i < nvm.i; i++) {
        uint8_t ignore[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};

        // This call of a M&D sequence results in initialization of one slot
        LT_LOG_INFO("Doing M&D sequence to initialize a slot...");
        ret = lt_mac_and_destroy(h, i, u, ignore);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
            goto exit;
        }
        LT_LOG_INFO("\tOK");

        // This call of a M&D sequence overwrites a previous slot, but key w is returned.
        // This key is later used to derive k_i (used to encrypt precious final_key)
        LT_LOG_INFO("Doing M&D sequence to overwrite previous slot...");
        ret = lt_mac_and_destroy(h, i, v, w_i);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
            goto exit;
        }
        LT_LOG_INFO("\tOK");

        // Now the slot is initialized again by calling M&D sequence again with 'u'
        LT_LOG_INFO("Doing M&D sequence again to initialize a slot...");
        ret = lt_mac_and_destroy(h, i, u, ignore);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
            goto exit;
        }
        LT_LOG_INFO("\tOK");

        // Derive k_i = KDF(w_i, PIN||A); k_i will be used to encrypt master_secret
        lt_hmac_sha256(w_i, sizeof(w_i), kdf_input_buff, PIN_size + add_size_checked, k_i);

        // Encrypt master_secret using k_i as a key and store ciphertext into non volatile storage
        encrypt(master_secret, k_i, nvm.ci + (i * TR01_MAC_AND_DESTROY_DATA_SIZE));
    }

    // Persistently save nvm data into TROPIC01's R memory slot
    LT_LOG_INFO("Writing NVM data into R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
    ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }

    // final_key is released to the caller
    lt_hmac_sha256(master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE, (uint8_t *)"2", 1, final_key);

// Cleanup all sensitive data from memory
exit:
    memset(kdf_input_buff, 0, PIN_size + add_size_checked);
    memset(u, 0, sizeof(u));
    memset(v, 0, sizeof(v));
    memset(w_i, 0, sizeof(w_i));
    memset(k_i, 0, sizeof(k_i));

    return ret;
}

/**
 * @brief Example function for checking PIN with Mac And Destroy.
 *
 * @details The Pin Entry Check procedure takes the PIN and additional add data entered by the user as an input, and
 * checks the PIN. If successful, the correct key k is returned.
 *
 * There are more ways how to implement Mac And Destroy 'PIN check' functionality, differences could be in way
 * of handling nvm data, number of tries, algorithm used for decryption, etc. This function is just one of the possible
 * implementations of "PIN check".
 *
 * Take it as an inspiration, copy it into your project and adapt it to your specific hw resources.
 *
 * @param h           Device's handle
 * @param PIN         Array of bytes (size between MAC_AND_DESTROY_PIN_SIZE_MIN and MAC_AND_DESTROY_PIN_SIZE_MAX)
 * representing PIN
 * @param PIN_size    Length of the PIN field
 * @param add         Additional data to be used in M&D sequence (size between MAC_AND_DESTROY_ADD_SIZE_MIN and
 * MAC_AND_DESTROY_ADD_SIZE_MAX). Pass NULL if no additional data should be used.
 * @param add_size    Length of additional data
 * @param final_key   Buffer into which final_key will be saved
 * @return lt_ret_t   LT_OK if correct, otherwise LT_FAIL
 */
static lt_ret_t lt_PIN_entry_check(lt_handle_t *h, const uint8_t *PIN, const uint8_t PIN_size, const uint8_t *add,
                                   const uint8_t add_size, uint8_t *final_key)
{
    if (!h || !PIN || (PIN_size < MAC_AND_DESTROY_PIN_SIZE_MIN) || (PIN_size > MAC_AND_DESTROY_PIN_SIZE_MAX)
        || (add_size > MAC_AND_DESTROY_ADD_SIZE_MAX) || !final_key) {
        // add parameter is not checked for NULL, because it can be NULL (handled in the lines below)
        return LT_PARAM_ERR;
    }
    if (h->l3.session_status != LT_SECURE_SESSION_ON) {
        return LT_HOST_NO_SESSION;
    }

    uint8_t add_size_checked = add_size;
    if (!add) {
        add_size_checked = 0;
    }

    // Clear variable for released final_key so there is known data (zeroes) in case this function ended sooner then
    // final_key was prepared
    memset(final_key, 0, TR01_MAC_AND_DESTROY_DATA_SIZE);

    // Variable used during a process of getting a decryption key k_i
    uint8_t v_[LT_HMAC_SHA256_HASH_LEN] = {0};
    // Variable used during a process of getting a decryption key k_i
    uint8_t w_i[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
    // Decryption key
    uint8_t k_i[LT_HMAC_SHA256_HASH_LEN] = {0};
    // Secret
    uint8_t s_[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
    // Tag
    uint8_t t_[LT_HMAC_SHA256_HASH_LEN] = {0};
    // Value used to initialize Mac And Destroy's slot after a correct PIN try
    uint8_t u[LT_HMAC_SHA256_HASH_LEN] = {0};

    // This organizes data which will be read from nvm
    struct lt_macandd_nvm_t nvm = {0};

    // User might pass not only PIN, but also another data(e.g. HW ID, ...) if needed
    // Both arrays are concatenated and used together as an input for KDF
    uint8_t kdf_input_buff[MAC_AND_DESTROY_PIN_SIZE_MAX + MAC_AND_DESTROY_ADD_SIZE_MAX];
    memcpy(kdf_input_buff, PIN, PIN_size);
    if (!add || add_size_checked == 0) {
        LT_LOG_INFO("No additional data will be used in the following M&D sequence");
    }
    else {
        memcpy(kdf_input_buff + PIN_size, add, add_size_checked);
    }

    // Load M&D data from TROPIC01's R memory
    LT_LOG_INFO("Reading M&D data from R_Mem User slot %d...", R_MEM_DATA_SLOT_MACANDD);
    uint16_t read_size;
    lt_ret_t ret = lt_r_mem_data_read(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm), &read_size);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to read User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // if i == 0: FAIL (no attempts remaining)
    LT_LOG_INFO("Checking if nvm.i != 0...");
    if (nvm.i == 0) {
        LT_LOG_ERROR("nvm.i == 0");
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // Decrement variable which holds number of tries
    // Let i = i - 1
    nvm.i--;

    // and store M&D data back to TROPIC01's R memory
    LT_LOG_INFO("Writing back M&D data into R_Mem User slot %d (erase, then write)...", R_MEM_DATA_SLOT_MACANDD);
    ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // Compute v’ = KDF(0, PIN’||A).
    const uint8_t zeros[32] = {0};
    lt_hmac_sha256(zeros, sizeof(zeros), kdf_input_buff, PIN_size + add_size_checked, v_);

    // Execute w’ = MACANDD(i, v’)
    LT_LOG_INFO("Doing M&D sequence...");
    ret = lt_mac_and_destroy(h, nvm.i, v_, w_i);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // Compute k’_i = KDF(w’, PIN’||A)
    lt_hmac_sha256(w_i, sizeof(w_i), kdf_input_buff, PIN_size + add_size_checked, k_i);

    // Read the ciphertext c_i and tag t from NVM,
    // decrypt c_i with k’_i as the key and obtain s_
    decrypt(nvm.ci + (nvm.i * TR01_MAC_AND_DESTROY_DATA_SIZE), k_i, s_);

    // Compute tag t = KDF(s_, "0x00")
    lt_hmac_sha256(s_, sizeof(s_), (uint8_t[]){0x00}, 1, t_);

    // If t’ != t: FAIL
    if (memcmp(nvm.t, t_, sizeof(t_)) != 0) {
        ret = LT_FAIL;
        goto exit;
    }

    // Pin is correct, now initialize macandd slots again:
    // Compute u = KDF(s’, "0x01")
    lt_hmac_sha256(s_, sizeof(s_), (uint8_t[]){0x01}, 1, u);

    for (int x = nvm.i; x < MACANDD_ROUNDS - 1; x++) {
        uint8_t ignore[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};

        LT_LOG_INFO("Doing M&D sequence...");
        ret = lt_mac_and_destroy(h, x, u, ignore);
        if (ret != LT_OK) {
            LT_LOG_ERROR("Failed while doing M&D sequence, ret=%s", lt_ret_verbose(ret));
            goto exit;
        }
        LT_LOG_INFO("\tOK");
    }

    // Set variable which holds number of tries back to initial state MACANDD_ROUNDS
    nvm.i = MACANDD_ROUNDS;

    // Store NVM data for future use
    LT_LOG_INFO("Writing M&D data into R_Mem User slot %d for future use (erase, then write)...",
                R_MEM_DATA_SLOT_MACANDD);
    ret = lt_r_mem_data_erase(h, R_MEM_DATA_SLOT_MACANDD);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to erase User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    ret = lt_r_mem_data_write(h, R_MEM_DATA_SLOT_MACANDD, (uint8_t *)&nvm, sizeof(nvm));
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to write User slot, ret=%s", lt_ret_verbose(ret));
        goto exit;
    }
    LT_LOG_INFO("\tOK");

    // Calculate final_key and store it into passed array
    lt_hmac_sha256(s_, sizeof(s_), (uint8_t *)"2", 1, final_key);

// Cleanup all sensitive data from memory
exit:
    memset(kdf_input_buff, 0, PIN_size + add_size_checked);
    memset(w_i, 0, sizeof(w_i));
    memset(k_i, 0, sizeof(k_i));
    memset(v_, 0, sizeof(v_));

    return ret;
}

int lt_ex_macandd(lt_handle_t *h)
{
    LT_LOG_INFO("==========================================");
    LT_LOG_INFO("==== TROPIC01 Mac and Destroy Example ====");
    LT_LOG_INFO("==========================================");

    lt_ret_t ret;

    LT_LOG_INFO("Initializing handle");
    ret = lt_init(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to initialize handle, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Starting Secure Session with key %d", (int)TR01_PAIRING_KEY_SLOT_INDEX_0);
    ret = lt_verify_chip_and_start_secure_session(h, sh0priv, sh0pub, TR01_PAIRING_KEY_SLOT_INDEX_0);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to start Secure Session with key %d, ret=%s", (int)TR01_PAIRING_KEY_SLOT_INDEX_0,
                     lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    // This variable stores final_key which is released to the user after successful PIN check or PIN set
    uint8_t final_key_initialized[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};

    // Additional data passed by user besides PIN - this is optional, but recommended
    uint8_t additional_data[]
        = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
           0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};

    // User's PIN
    uint8_t pin[] = {1, 2, 3, 4};
    uint8_t pin_wrong[] = {2, 2, 3, 4};

    LT_LOG_LINE();

    LT_LOG_INFO("Initializing Mac And Destroy");
    LT_LOG_INFO("Generating random master_secret...");
    uint8_t master_secret[TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE] = {0};
    ret = lt_random_bytes(h, master_secret, TR01_MAC_AND_DESTROY_MASTER_SECRET_SIZE);
    if (ret != LT_OK) {
        LT_LOG_ERROR("Failed to get random bytes, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("\tOK");
    char print_buff[PRINT_BUFF_SIZE];
    ret = lt_print_bytes(master_secret, sizeof(master_secret), print_buff, PRINT_BUFF_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("Generated master_secret: %s", print_buff);

    // Set the PIN and log out the final_key
    LT_LOG("Setting the user PIN...");
    ret = lt_new_PIN_setup(h, master_secret, pin, sizeof(pin), NULL, sizeof(additional_data), final_key_initialized);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to set the user PIN, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("\tOK");

    ret = lt_print_bytes(final_key_initialized, sizeof(final_key_initialized), print_buff, PRINT_BUFF_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("Initialized final_key: %s", print_buff);
    LT_LOG_LINE();

    uint8_t final_key_exported[TR01_MAC_AND_DESTROY_DATA_SIZE] = {0};
    LT_LOG_INFO("Doing %d PIN check attempts with wrong PIN...", MACANDD_ROUNDS);
    for (int i = 1; i < MACANDD_ROUNDS; i++) {
        LT_LOG_INFO("\tInputting wrong PIN -> slot #%d destroyed", i);
        ret = lt_PIN_entry_check(h, pin_wrong, sizeof(pin_wrong), NULL, sizeof(additional_data), final_key_exported);
        if (LT_FAIL != ret) {
            LT_LOG_ERROR("Return value is not LT_FAIL, ret=%s", lt_ret_verbose(ret));
            lt_session_abort(h);
            lt_deinit(h);
            return -1;
        }
        ret = lt_print_bytes(final_key_exported, sizeof(final_key_exported), print_buff, PRINT_BUFF_SIZE);
        if (LT_OK != ret) {
            LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
            lt_session_abort(h);
            lt_deinit(h);
            return -1;
        }
        LT_LOG_INFO("\tSecret: %s", print_buff);
    }
    LT_LOG_INFO("\tOK");

    LT_LOG_INFO("Doing Final PIN attempt with correct PIN, slots are reinitialized again...");
    ret = lt_PIN_entry_check(h, pin, sizeof(pin), NULL, sizeof(additional_data), final_key_exported);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Attempt with correct PIN failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    ret = lt_print_bytes(final_key_exported, sizeof(final_key_exported), print_buff, PRINT_BUFF_SIZE);
    if (LT_OK != ret) {
        LT_LOG_ERROR("lt_print_bytes failed, ret=%s", lt_ret_verbose(ret));
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    LT_LOG_INFO("\tExported final_key: %s", print_buff);
    LT_LOG_INFO("\tOK");
    LT_LOG_LINE();

    if (memcmp(final_key_initialized, final_key_exported, sizeof(final_key_initialized))) {
        LT_LOG_ERROR("final_key and final_key_exported DO NOT MATCH");
        lt_session_abort(h);
        lt_deinit(h);
        return -1;
    }
    else {
        LT_LOG_INFO("final_key and final_key_exported MATCH");
    }

    LT_LOG_INFO("Aborting Secure Session");
    ret = lt_session_abort(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to abort Secure Session, ret=%s", lt_ret_verbose(ret));
        lt_deinit(h);
        return -1;
    }

    LT_LOG_INFO("Deinitializing handle");
    ret = lt_deinit(h);
    if (LT_OK != ret) {
        LT_LOG_ERROR("Failed to deinitialize handle, ret=%s", lt_ret_verbose(ret));
        return -1;
    }

    return 0;
}