//-----------------------------------------------------------------------------
// Borrowed initially from https://nethemba.com/tag/darkside-attack/
// Copyright (C) mifare@nethemba.com, 2010
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// mifare commands
//-----------------------------------------------------------------------------
#include "mifarehost.h"

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "comms.h"
#include "commonutil.h"
#include "mifare4.h"
#include "ui.h"                 // PrintAndLog...
#include "crapto1/crapto1.h"
#include "crc16.h"
#include "protocols.h"
#include "mfkey.h"
#include "util_posix.h"         // msclock
#include "cmdparser.h"          // detection of flash capabilities
#include "cmdflashmemspiffs.h"  // upload to flash mem
#include "mifaredefault.h"      // default keys
#include "protocol_vigik.h"     // VIGIK struct
#include "crypto/libpcrypto.h"
#include "util.h" // xor
#include "mbedtls/sha1.h"       // SHA1
#include "cmdhf14a.h"
#include "gen4.h"
#include "parity.h"

int mf_dark_side(uint8_t blockno, uint8_t key_type, uint64_t *key) {
    uint32_t uid = 0;
    uint32_t nt = 0, nr = 0, ar = 0;
    uint64_t par_list = 0, ks_list = 0;
    uint64_t *keylist = NULL, *last_keylist = NULL;
    bool first_run = true;

    // message
    PrintAndLogEx(INFO, "Expected execution time is about " _YELLOW_("25") " seconds on average");
    PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort");

    while (true) {
        clearCommandBuffer();

        //TODO: Not really stopping the command in time.
        //flush queue
        while (kbd_enter_pressed()) {
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            PrintAndLogEx(WARNING, "Aborted via keyboard");
            return PM3_EOPABORTED;
        }

        struct {
            uint8_t first_run;
            uint8_t blockno;
            uint8_t key_type;
        } PACKED payload;
        payload.first_run = first_run;
        payload.blockno = blockno;
        payload.key_type = key_type;
        SendCommandNG(CMD_HF_MIFARE_READER, (uint8_t *)&payload, sizeof(payload));

        PrintAndLogEx(NORMAL, "");
        PrintAndLogEx(INFO, "Running darkside " NOLF);

        // wait cycle
        while (true) {
            PrintAndLogEx(NORMAL, "." NOLF);

            if (IsCommunicationThreadDead()) return PM3_EIO;

            //TODO: Not really stopping the command in time.
            if (kbd_enter_pressed()) {
                SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
                PrintAndLogEx(WARNING, "\nAborted via keyboard");
                return PM3_EOPABORTED;
            }

            PacketResponseNG resp;
            if (WaitForResponseTimeout(CMD_HF_MIFARE_READER, &resp, 2000)) {

                struct p {
                    int32_t isOK;
                    uint8_t cuid[4];
                    uint8_t nt[4];
                    uint8_t par_list[8];
                    uint8_t ks_list[8];
                    uint8_t nr[4];
                    uint8_t ar[4];
                } PACKED;

                struct p *package = (struct p *)resp.data.asBytes;

                if (resp.status != PM3_SUCCESS) {
                    PrintAndLogEx(NORMAL, "");

                    switch (package->isOK) {
                        case 2:
                            PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests).");
                            break;
                        case 3:
                            PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (its random number generator is not predictable).");
                            break;
                        case 4:
                            PrintAndLogEx(FAILED, "Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown");
                            PrintAndLogEx(FAILED, "generating polynomial with 16 effective bits only, but shows unexpected behaviour.");
                            break;
                        case 5:
                            PrintAndLogEx(WARNING, "Button pressed. aborted");
                            break;
                        case 6:
                            *key = 0101;
                            return PM3_SUCCESS;
                        default:
                            PrintAndLogEx(FAILED, "Unknown error. Darkside attack failed.");
                            break;
                    }

                    return resp.status;
                }

                uid = (uint32_t)bytes_to_num(package->cuid, sizeof(package->cuid));
                nt = (uint32_t)bytes_to_num(package->nt, sizeof(package->nr));
                par_list = bytes_to_num(package->par_list, sizeof(package->par_list));
                ks_list = bytes_to_num(package->ks_list, sizeof(package->ks_list));
                nr = (uint32_t)bytes_to_num(package->nr, 4);
                ar = (uint32_t)bytes_to_num(package->ar, 4);
                break;
            }
        }
        PrintAndLogEx(NORMAL, "");

        if (par_list == 0 && first_run == true) {
            PrintAndLogEx(SUCCESS, "Parity is all zero. Most likely this card sends NACK on every authentication.");
        }
        first_run = false;

        uint32_t keycount = nonce2key(uid, nt, nr, ar, par_list, ks_list, &keylist);

        if (keycount == 0) {
            PrintAndLogEx(FAILED, "Key not found (lfsr_common_prefix list is null). Nt = %08x", nt);
            PrintAndLogEx(FAILED, "This is expected to happen in 25%% of all cases.");
            PrintAndLogEx(FAILED, "Trying again with a different reader nonce...");
            continue;
        }

        // only parity zero attack
        if (par_list == 0) {
            qsort(keylist, keycount, sizeof(*keylist), compare_uint64);
            keycount = intersection(last_keylist, keylist);
            if (keycount == 0) {
                free(last_keylist);
                last_keylist = keylist;
                PrintAndLogEx(FAILED, "No candidates found, trying again");
                continue;
            }
        }

        PrintAndLogEx(SUCCESS, "found " _YELLOW_("%u") " candidate key%s", keycount, (keycount > 1) ? "s" : "");

        *key = UINT64_C(-1);
        uint8_t keyBlock[PM3_CMD_DATA_SIZE];
        uint32_t max_keys = KEYS_IN_BLOCK;
        for (uint32_t i = 0; i < keycount; i += max_keys) {

            uint8_t size = keycount - i > max_keys ? max_keys : keycount - i;
            register uint8_t j;
            for (j = 0; j < size; j++) {
                if (par_list == 0) {
                    num_to_bytes(last_keylist[i * max_keys + j], MIFARE_KEY_SIZE, keyBlock + (j * MIFARE_KEY_SIZE));
                } else {
                    num_to_bytes(keylist[i * max_keys + j], MIFARE_KEY_SIZE, keyBlock + (j * MIFARE_KEY_SIZE));
                }
            }

            if (mf_check_keys(blockno, key_type - 0x60, false, size, keyBlock, key) == PM3_SUCCESS) {
                break;
            }
        }

        if (*key != UINT64_C(-1)) {
            break;
        } else {
            PrintAndLogEx(FAILED, "All key candidates failed. Restarting darkside");
            free(last_keylist);
            last_keylist = keylist;
            first_run = true;
        }
    }
    free(last_keylist);
    free(keylist);
    return PM3_SUCCESS;
}

int mf_check_keys(uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key) {

    if (key) {
        *key = -1;
    }


    uint8_t data[PM3_CMD_DATA_SIZE] = {0};
    data[0] = keyType;
    data[1] = blockNo;
    data[2] = clear_trace;
    data[3] = 0;
    data[4] = keycnt;
    memcpy(data + 5, keyBlock, MIFARE_KEY_SIZE * keycnt);

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_CHKKEYS, data, (5 + MIFARE_KEY_SIZE * keycnt));
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_CHKKEYS, &resp, 2500) == false) {
        return PM3_ETIMEOUT;
    }

    if (resp.status != PM3_SUCCESS) {
        return resp.status;
    }

    struct kr {
        uint8_t key[MIFARE_KEY_SIZE];
        bool found;
    } PACKED;
    struct kr *keyresult = (struct kr *)&resp.data.asBytes;
    if (keyresult->found == false) {
        return PM3_ESOFT;
    }

    if (key) {
        *key = bytes_to_num(keyresult->key, sizeof(keyresult->key));
    }
    return PM3_SUCCESS;
}

// Sends chunks of keys to device.
// 0 == ok all keys found
// 1 ==
// 2 == Time-out, aborting
int mf_check_keys_fast_ex(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastChunk, uint8_t strategy,
                          uint32_t size, uint8_t *keyBlock, sector_t *e_sector, bool use_flashmemory,
                          bool verbose, bool quiet, uint16_t singleSectorParams) {

    uint64_t t2 = msclock();

    // send keychunk
    clearCommandBuffer();
    SendCommandOLD(CMD_HF_MIFARE_CHKKEYS_FAST
                   , (sectorsCnt | (firstChunk << 8) | (lastChunk << 12) | (singleSectorParams << 16))
                   , ((use_flashmemory << 8) | strategy)
                   , size
                   , keyBlock
                   , (MIFARE_KEY_SIZE * size)
                  );
    PacketResponseNG resp;

    uint32_t timeout = 0;
    while (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {

        while (kbd_enter_pressed()) {
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            PrintAndLogEx(NORMAL, "");
            return PM3_EOPABORTED;
        }

        if (quiet == false) {
            PrintAndLogEx((timeout) ? NORMAL : INFO, "." NOLF);
            fflush(stdout);
        }

        timeout++;

        // max timeout for one chunk of 85keys, 60*3sec = 180seconds
        // s70 with 40*2 keys to check, 80*85 = 6800 auth.
        // takes about 97s, still some margin before abort
        // timeout = 180 => ~360s @ Mifare Classic 1k @ ~2300 keys in dict
        // ~2300 keys @ Mifare Classic 1k => ~620s
        if (timeout > 60 * 12) {
            PrintAndLogEx(WARNING, "\nNo response from Proxmark3. Aborting...");
            return PM3_ETIMEOUT;
        }
    }
    t2 = msclock() - t2;

    if (timeout && (quiet == false)) {
        PrintAndLogEx(NORMAL, "");
    }
    // time to convert the returned data.
    uint8_t curr_keys = resp.oldarg[0];

    if ((singleSectorParams >> 15) & 1) {
        if (curr_keys) {
            // uint64_t foo = bytes_to_num(resp.data.asBytes, 6);
            PrintAndLogEx(NORMAL, "");
//            PrintAndLogEx(SUCCESS, "found Key %s for block %2i found: " _GREEN_("%012" PRIx64), (singleSectorParams >> 8) & 1 ? "B" : "A", singleSectorParams & 0xFF, foo);

            PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c -- found valid key [ " _GREEN_("%s") " ]",
                          singleSectorParams & 0xFF,
                          ((singleSectorParams >> 8) & 1) ? 'B' : 'A',
                          sprint_hex_inrow(resp.data.asBytes, MIFARE_KEY_SIZE)
                         );

            return PM3_SUCCESS;
        }
    }

    if (verbose) {
        PrintAndLogEx(INFO, "Chunk %.1fs | found %u/%u keys (%u)", (float)(t2 / 1000.0), curr_keys, (sectorsCnt << 1), size);
    }

    // all keys?
    if (curr_keys == sectorsCnt * 2 || lastChunk) {

        // success array. each byte is status of key
        uint8_t arr[80];
        uint64_t foo = 0;
        uint16_t bar = 0;
        foo = bytes_to_num(resp.data.asBytes + 480, 8);
        bar = (resp.data.asBytes[489]  << 8 | resp.data.asBytes[488]);

        for (uint8_t i = 0; i < 64; i++) {
            arr[i] = (foo >> i) & 0x1;
        }

        for (uint8_t i = 0; i < 16; i++) {
            arr[i + 64] = (bar >> i) & 0x1;
        }

        // initialize storage for found keys
        icesector_t *tmp = calloc(sectorsCnt, sizeof(icesector_t));
        if (tmp == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            return PM3_EMALLOC;
        }

        memcpy(tmp, resp.data.asBytes, sectorsCnt * sizeof(icesector_t));

        for (int i = 0; i < sectorsCnt; i++) {
            // key A
            if (!e_sector[i].foundKey[0]) {
                e_sector[i].Key[0] =  bytes_to_num(tmp[i].keyA, MIFARE_KEY_SIZE);
                e_sector[i].foundKey[0] = arr[(i * 2) ];
            }
            // key B
            if (!e_sector[i].foundKey[1]) {
                e_sector[i].Key[1] =  bytes_to_num(tmp[i].keyB, MIFARE_KEY_SIZE);
                e_sector[i].foundKey[1] = arr[(i * 2) + 1 ];
            }
        }
        free(tmp);

        // if all keys where found
        if (curr_keys == sectorsCnt * 2) {
            return PM3_SUCCESS;
        }

        // if some keys was found
        if (curr_keys > 0)  {
            return PM3_EPARTIAL;
        }

        if (lastChunk) {
            return PM3_ESOFT;
        }
    }
    return PM3_ESOFT;
}

int mf_check_keys_fast(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastChunk, uint8_t strategy
                       , uint32_t size, uint8_t *keyBlock, sector_t *e_sector, bool use_flashmemory
                       , bool verbose) {
    return mf_check_keys_fast_ex(sectorsCnt, firstChunk, lastChunk, strategy, size, keyBlock, e_sector, use_flashmemory, verbose, false, 0);
}

// Trigger device to use a binary file on flash mem as keylist for mfCheckKeys.
// As of now,  255 keys possible in the file
// 6 * 255 = 1500 bytes
int mf_check_keys_file(uint8_t *destfn, uint64_t *key) {

    if (key) {
        *key = -1;
    }

    struct {
        uint8_t filename[32];
    } PACKED payload_file;

    memcpy(payload_file.filename, destfn, sizeof(payload_file.filename) - 1);

    clearCommandBuffer();
    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_CHKKEYS_FILE, (uint8_t *)&payload_file, sizeof(payload_file));

    uint8_t retry = 10;

    while (WaitForResponseTimeout(CMD_HF_MIFARE_CHKKEYS, &resp, 2000) == false) {

        //flush queue
        while (kbd_enter_pressed()) {
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            return PM3_EOPABORTED;
        }

        retry--;
        if (retry == 0) {
            PrintAndLogEx(WARNING, "Chk keys file, command execution time out");
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            return PM3_ETIMEOUT;
        }
    }

    if (resp.status != PM3_SUCCESS) {
        return resp.status;
    }

    struct kr {
        uint8_t key[MIFARE_KEY_SIZE];
        bool found;
    } PACKED;
    struct kr *keyresult = (struct kr *)&resp.data.asBytes;

    if (keyresult->found == false) {
        return PM3_ESOFT;
    }

    *key = bytes_to_num(keyresult->key, sizeof(keyresult->key));
    return PM3_SUCCESS;
}

// PM3 imp of J-Run mf_key_brute (part 2)
// ref: https://github.com/J-Run/mf_key_brute
int mf_key_brute(uint8_t blockNo, uint8_t keyType, const uint8_t *key, uint64_t *resultkey) {

    uint64_t key64;
    uint8_t found = false;
    uint8_t candidates[CANDIDATE_SIZE] = {0x00};
    uint8_t keyBlock[KEYBLOCK_SIZE] = {0x00};

    memset(candidates, 0, sizeof(candidates));
    memset(keyBlock, 0, sizeof(keyBlock));

    // Generate all possible keys for the first two unknown bytes.
    for (uint16_t i = 0; i < 0xFFFF; ++i) {
        uint32_t j = i * MIFARE_KEY_SIZE;
        candidates[0 + j] = i >> 8;
        candidates[1 + j] = i;
        candidates[2 + j] = key[2];
        candidates[3 + j] = key[3];
        candidates[4 + j] = key[4];
        candidates[5 + j] = key[5];
    }
    uint32_t counter, i;
    for (i = 0, counter = 1; i < CANDIDATE_SIZE; i += KEYBLOCK_SIZE, ++counter) {

        key64 = 0;

        // copy candidatekeys to test key block
        memcpy(keyBlock, candidates + i, KEYBLOCK_SIZE);

        // check a block of generated key candidates.
        if (mf_check_keys(blockNo, keyType, true, KEYS_IN_BLOCK, keyBlock, &key64) == PM3_SUCCESS) {
            *resultkey = key64;
            found = true;
            break;
        }

        // progress
        if (counter % 20 == 0) {
            PrintAndLogEx(SUCCESS, "tried %s.. \t %u keys", sprint_hex(candidates + i, 6),  counter * KEYS_IN_BLOCK);
        }
    }
    return found;
}

// Compare 16 Bits out of cryptostate
inline static int Compare16Bits(const void *a, const void *b) {
    if ((*(uint64_t *)b & 0x00ff000000ff0000) == (*(uint64_t *)a & 0x00ff000000ff0000)) return 0;
    if ((*(uint64_t *)b & 0x00ff000000ff0000) > (*(uint64_t *)a & 0x00ff000000ff0000)) return 1;
    return -1;
}

// wrapper function for multi-threaded lfsr_recovery32
static void
#ifdef __has_attribute
#if __has_attribute(force_align_arg_pointer)
__attribute__((force_align_arg_pointer))
#endif
#endif
*nested_worker_thread(void *arg) {
    struct Crypto1State *p1;
    StateList_t *statelist = arg;
    statelist->head.slhead = lfsr_recovery32(statelist->ks1, statelist->nt_enc ^ statelist->uid);

    for (p1 = statelist->head.slhead; p1->odd | p1->even; p1++) {};

    statelist->len = p1 - statelist->head.slhead;
    statelist->tail.sltail = --p1;

    qsort(statelist->head.slhead, statelist->len, sizeof(uint64_t), Compare16Bits);

    return statelist->head.slhead;
}

int mf_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate) {

    uint32_t uid = 0;
    StateList_t statelists[2];
    struct Crypto1State *p1, *p2, *p3, *p4;

    struct {
        uint8_t block;
        uint8_t keytype;
        uint8_t target_block;
        uint8_t target_keytype;
        bool calibrate;
        uint8_t key[6];
    } PACKED payload;
    payload.block = blockNo;
    payload.keytype = keyType;
    payload.target_block = trgBlockNo;
    payload.target_keytype = trgKeyType;
    payload.calibrate = calibrate;
    memcpy(payload.key, key, sizeof(payload.key));

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_NESTED, (uint8_t *)&payload, sizeof(payload));

    if (WaitForResponseTimeout(CMD_HF_MIFARE_NESTED, &resp, 2000) == false) {
        SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
        return PM3_ETIMEOUT;
    }

    struct p {
        int16_t isOK;
        uint8_t block;
        uint8_t keytype;
        uint8_t cuid[4];
        uint8_t nt_a[4];
        uint8_t ks_a[4];
        uint8_t nt_b[4];
        uint8_t ks_b[4];
    } PACKED;
    struct p *package = (struct p *)resp.data.asBytes;

    // error during nested on device side
    if (package->isOK != PM3_SUCCESS)
        return package->isOK;

    memcpy(&uid, package->cuid, sizeof(package->cuid));

    for (uint8_t i = 0; i < 2; i++) {
        statelists[i].blockNo = package->block;
        statelists[i].keyType = package->keytype;
        statelists[i].uid = uid;
    }

    memcpy(&statelists[0].nt_enc,  package->nt_a, sizeof(package->nt_a));
    memcpy(&statelists[0].ks1, package->ks_a, sizeof(package->ks_a));

    memcpy(&statelists[1].nt_enc,  package->nt_b, sizeof(package->nt_b));
    memcpy(&statelists[1].ks1, package->ks_b, sizeof(package->ks_b));

    // calc keys
    pthread_t thread_id[2];

    // create and run worker threads
    for (uint8_t i = 0; i < 2; i++)
        pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]);

    // wait for threads to terminate:
    for (uint8_t i = 0; i < 2; i++)
        pthread_join(thread_id[i], (void *)&statelists[i].head.slhead);

    // the first 16 Bits of the cryptostate already contain part of our key.
    // Create the intersection of the two lists based on these 16 Bits and
    // roll back the cryptostate
    p1 = p3 = statelists[0].head.slhead;
    p2 = p4 = statelists[1].head.slhead;

    while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) {
        if (Compare16Bits(p1, p2) == 0) {

            struct Crypto1State savestate;
            savestate = *p1;
            while (Compare16Bits(p1, &savestate) == 0 && p1 <= statelists[0].tail.sltail) {
                *p3 = *p1;
                lfsr_rollback_word(p3, statelists[0].nt_enc ^ statelists[0].uid, 0);
                p3++;
                p1++;
            }
            savestate = *p2;
            while (Compare16Bits(p2, &savestate) == 0 && p2 <= statelists[1].tail.sltail) {
                *p4 = *p2;
                lfsr_rollback_word(p4, statelists[1].nt_enc ^ statelists[1].uid, 0);
                p4++;
                p2++;
            }
        } else {
            while (Compare16Bits(p1, p2) == -1) p1++;
            while (Compare16Bits(p1, p2) == 1) p2++;
        }
    }

    p3->odd = -1;
    p3->even = -1;
    p4->odd = -1;
    p4->even = -1;
    statelists[0].len = p3 - statelists[0].head.slhead;
    statelists[1].len = p4 - statelists[1].head.slhead;
    statelists[0].tail.sltail = --p3;
    statelists[1].tail.sltail = --p4;

    // the statelists now contain possible keys. The key we are searching for must be in the
    // intersection of both lists
    qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64);
    qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64);
    // Create the intersection
    statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead);

    //statelists[0].tail.keytail = --p7;
    uint32_t keycnt = statelists[0].len;
    if (keycnt == 0) {
        goto out;
    }

    PrintAndLogEx(SUCCESS, "Found " _YELLOW_("%u") " key candidates", keycnt);

    memset(resultKey, 0, MIFARE_KEY_SIZE);
    uint64_t key64 = -1;

    // The list may still contain several key candidates. Test each of them with mfCheckKeys
    uint32_t max_keys = keycnt > KEYS_IN_BLOCK ? KEYS_IN_BLOCK : keycnt;
    uint8_t keyBlock[PM3_CMD_DATA_SIZE] = {0x00};

    for (uint32_t i = 0; i < keycnt; i += max_keys) {

        uint64_t start_time = msclock();

        uint8_t size = keycnt - i > max_keys ? max_keys : keycnt - i;

        register uint8_t j;
        for (j = 0; j < size; j++) {
            crypto1_get_lfsr(statelists[0].head.slhead + i, &key64);
            num_to_bytes(key64, 6, keyBlock + j * MIFARE_KEY_SIZE);
        }

        if (mf_check_keys(statelists[0].blockNo, statelists[0].keyType, false, size, keyBlock, &key64) == PM3_SUCCESS) {
            free(statelists[0].head.slhead);
            free(statelists[1].head.slhead);
            num_to_bytes(key64, 6, resultKey);

            if (package->keytype < 2) {
                PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c -- found valid key [ " _GREEN_("%s") " ]",
                              package->block,
                              package->keytype ? 'B' : 'A',
                              sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE)
                             );
            } else {
                PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %02x -- found valid key [ " _GREEN_("%s") " ]",
                              package->block,
                              MIFARE_AUTH_KEYA + package->keytype,
                              sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE)
                             );
            }


            return PM3_SUCCESS;
        }

        float bruteforce_per_second = (float)(i + max_keys) / ((msclock() - start_time) / 1000.0);
        PrintAndLogEx(INPLACE, "%6d/%u keys | %5.1f keys/sec | worst case %6.1f seconds remaining", i, keycnt, bruteforce_per_second, (keycnt - i) / bruteforce_per_second);
    }

out:
    if (package->keytype < 2) {
        PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c",
                      package->block,
                      package->keytype ? 'B' : 'A'
                     );
    } else {
        PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %02x",
                      package->block,
                      MIFARE_AUTH_KEYA + package->keytype
                     );
    }
    free(statelists[0].head.slhead);
    free(statelists[1].head.slhead);
    return PM3_ESOFT;
}

int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey) {

    uint32_t uid = 0;
    StateList_t statelists[2];
    struct Crypto1State *p1, *p2, *p3, *p4;

    struct {
        uint8_t block;
        uint8_t keytype;
        uint8_t target_block;
        uint8_t target_keytype;
        uint8_t key[6];
    } PACKED payload;
    payload.block = blockNo;
    payload.keytype = keyType;
    payload.target_block = trgBlockNo;
    payload.target_keytype = trgKeyType;
    memcpy(payload.key, key, sizeof(payload.key));

    PacketResponseNG resp;
    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_STATIC_NESTED, (uint8_t *)&payload, sizeof(payload));

    if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_NESTED, &resp, 2000) == false)
        return PM3_ETIMEOUT;

    if (resp.status != PM3_SUCCESS)
        return resp.status;

    struct p {
        uint8_t block;
        uint8_t keytype;
        uint8_t cuid[4];
        uint8_t nt_a[4];
        uint8_t ks_a[4];
        uint8_t nt_b[4];
        uint8_t ks_b[4];
    } PACKED;
    struct p *package = (struct p *)resp.data.asBytes;

    memcpy(&uid, package->cuid, sizeof(package->cuid));

    for (uint8_t i = 0; i < 2; i++) {
        statelists[i].blockNo = package->block;
        statelists[i].keyType = package->keytype;
        statelists[i].uid = uid;
    }

    memcpy(&statelists[0].nt_enc, package->nt_a, sizeof(package->nt_a));
    memcpy(&statelists[0].ks1, package->ks_a, sizeof(package->ks_a));

    memcpy(&statelists[1].nt_enc, package->nt_b, sizeof(package->nt_b));
    memcpy(&statelists[1].ks1, package->ks_b, sizeof(package->ks_b));

    // calc keys
    pthread_t thread_id[2];

    // create and run worker threads
    for (uint8_t i = 0; i < 2; i++)
        pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]);

    // wait for threads to terminate:
    for (uint8_t i = 0; i < 2; i++)
        pthread_join(thread_id[i], (void *)&statelists[i].head.slhead);

    // the first 16 Bits of the cryptostate already contain part of our key.
    // Create the intersection of the two lists based on these 16 Bits and
    // roll back the cryptostate
    p1 = p3 = statelists[0].head.slhead;
    p2 = p4 = statelists[1].head.slhead;

    while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) {
        if (Compare16Bits(p1, p2) == 0) {

            struct Crypto1State savestate;
            savestate = *p1;
            while (Compare16Bits(p1, &savestate) == 0 && p1 <= statelists[0].tail.sltail) {
                *p3 = *p1;
                lfsr_rollback_word(p3, statelists[0].nt_enc ^ statelists[0].uid, 0);
                p3++;
                p1++;
            }
            savestate = *p2;
            while (Compare16Bits(p2, &savestate) == 0 && p2 <= statelists[1].tail.sltail) {
                *p4 = *p2;
                lfsr_rollback_word(p4, statelists[1].nt_enc ^ statelists[1].uid, 0);
                p4++;
                p2++;
            }
        } else {
            while (Compare16Bits(p1, p2) == -1) p1++;
            while (Compare16Bits(p1, p2) == 1) p2++;
        }
    }

    p3->odd = -1;
    p3->even = -1;
    p4->odd = -1;
    p4->even = -1;
    statelists[0].len = p3 - statelists[0].head.slhead;
    statelists[1].len = p4 - statelists[1].head.slhead;
    statelists[0].tail.sltail = --p3;
    statelists[1].tail.sltail = --p4;

    // the statelists now contain possible keys. The key we are searching for must be in the
    // intersection of both lists
    qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64);
    qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64);
    // Create the intersection
    statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead);

    uint32_t keycnt = statelists[0].len;
    if (keycnt == 0) {
        goto out;
    }

    PrintAndLogEx(SUCCESS, "Found " _YELLOW_("%u") " key candidates", keycnt);

    memset(resultKey, 0, MIFARE_KEY_SIZE);

    // The list may still contain several key candidates. Test each of them with mfCheckKeys
    uint32_t maxkeysinblock = IfPm3Flash() ? 1000 : KEYS_IN_BLOCK;
    uint32_t max_keys_chunk = keycnt > maxkeysinblock ? maxkeysinblock : keycnt;

    uint8_t *mem = NULL;
    uint8_t *p_keyblock = NULL;

    // if RDV4 and more than 10 candidate keys
    if (IfPm3Flash() && keycnt > 70) {

        // used for mfCheckKeys_file, which needs a header
        mem = calloc((maxkeysinblock * MIFARE_KEY_SIZE) + 5, sizeof(uint8_t));
        if (mem == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            free(statelists[0].head.slhead);
            return PM3_EMALLOC;
        }

        mem[0] = statelists[0].keyType;
        mem[1] = statelists[0].blockNo;
        mem[2] = 1;
        mem[3] = ((max_keys_chunk >> 8) & 0xFF);
        mem[4] = (max_keys_chunk & 0xFF);

        p_keyblock = mem + 5;
    } else {

        // used for mfCheckKeys, which adds its own header.
        mem = calloc((maxkeysinblock * MIFARE_KEY_SIZE), sizeof(uint8_t));
        if (mem == NULL) {
            PrintAndLogEx(WARNING, "Failed to allocate memory");
            free(statelists[0].head.slhead);
            return PM3_EMALLOC;
        }
        p_keyblock = mem;
    }

    uint8_t fn[32] = "static_nested_000.bin";

    uint64_t start_time = msclock();
    for (uint32_t i = 0; i < keycnt; i += max_keys_chunk) {

        //flush queue
        while (kbd_enter_pressed()) {
            SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
            PrintAndLogEx(NORMAL, "");
            free(mem);
            return PM3_EOPABORTED;
        }

        int res = 0;
        uint64_t key64 = 0;
        uint32_t chunk = keycnt - i > max_keys_chunk ? max_keys_chunk : keycnt - i;

        // copy x keys to device.
        for (uint32_t j = 0; j < chunk; j++) {
            crypto1_get_lfsr(statelists[0].head.slhead + i + j, &key64);
            num_to_bytes(key64, MIFARE_KEY_SIZE, p_keyblock + j * MIFARE_KEY_SIZE);
        }

        // check a block of generated key candidates.
        if (IfPm3Flash() && keycnt > 70) {

            mem[3] = ((chunk >> 8) & 0xFF);
            mem[4] = (chunk & 0xFF);

            // upload to flash.
            res = flashmem_spiffs_load((char *)fn, mem, 5 + (chunk * MIFARE_KEY_SIZE));
            if (res != PM3_SUCCESS) {
                PrintAndLogEx(WARNING, "\nSPIFFS upload failed");
                free(mem);
                return res;
            }
            res = mf_check_keys_file(fn, &key64);
        } else {
            res = mf_check_keys(statelists[0].blockNo, statelists[0].keyType, true, chunk, mem, &key64);
        }

        if (res == PM3_SUCCESS) {
            p_keyblock = NULL;
            free(statelists[0].head.slhead);
            free(mem);

            num_to_bytes(key64, MIFARE_KEY_SIZE, resultKey);

            if (IfPm3Flash() && keycnt > 70)
                PrintAndLogEx(NORMAL, "");

            PrintAndLogEx(SUCCESS, "target block %4u key type %c -- found valid key [ " _GREEN_("%s") " ]",
                          package->block,
                          package->keytype ? 'B' : 'A',
                          sprint_hex_inrow(resultKey, MIFARE_KEY_SIZE)
                         );
            return PM3_SUCCESS;
        } else if (res == PM3_ETIMEOUT || res == PM3_EOPABORTED) {
            PrintAndLogEx(NORMAL, "");
            free(mem);
            return res;
        }

        float bruteforce_per_second = (float)(i + max_keys_chunk) / ((msclock() - start_time) / 1000.0);
        PrintAndLogEx(INPLACE, "%6u/%u keys | %5.1f keys/sec | worst case %6.1f seconds", i, keycnt, bruteforce_per_second, (keycnt - i) / bruteforce_per_second);
    }

    p_keyblock = NULL;
    free(mem);

out:

    PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c",
                  package->block,
                  package->keytype ? 'B' : 'A'
                 );

    free(statelists[0].head.slhead);
    free(statelists[1].head.slhead);
    return PM3_ESOFT;
}

// MIFARE
int mf_read_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8_t *data) {

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_READSC, sectorNo, keyType, 0, (uint8_t *)key, MIFARE_KEY_SIZE);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) {
        uint8_t isOK  = resp.oldarg[0] & 0xFF;

        if (isOK) {
            memcpy(data, resp.data.asBytes, mfNumBlocksPerSector(sectorNo) * MFBLOCK_SIZE);
            return PM3_SUCCESS;
        } else {
            return PM3_EUNDEF;
        }
    } else {
        PrintAndLogEx(DEBUG, "command execution time out");
        return PM3_ETIMEOUT;
    }
    return PM3_SUCCESS;
}

int mf_read_block(uint8_t blockNo, uint8_t keyType, const uint8_t *key, uint8_t *data) {
    mf_readblock_t payload = {
        .blockno = blockNo,
        .keytype = keyType
    };
    memcpy(payload.key, key, sizeof(payload.key));

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_READBL, (uint8_t *)&payload, sizeof(mf_readblock_t));
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL, &resp, 1500)) {
        memcpy(data, resp.data.asBytes, MFBLOCK_SIZE);

        if (resp.status != PM3_SUCCESS) {
            PrintAndLogEx(DEBUG, "failed reading block");
            return PM3_ESOFT;
        }
    } else {
        PrintAndLogEx(DEBUG, "command execution time out");
        return PM3_ETIMEOUT;
    }
    return PM3_SUCCESS;
}

int mf_write_block(uint8_t blockno, uint8_t keyType, const uint8_t *key, uint8_t *block) {

    uint8_t data[26];
    memcpy(data, key, MIFARE_KEY_SIZE);
    memcpy(data + 10, block, MFBLOCK_SIZE);

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_WRITEBL, blockno, keyType, 0, data, sizeof(data));
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) {
        PrintAndLogEx(FAILED, "mfWriteBlock execution time out");
        return PM3_ETIMEOUT;
    }
    int res = PM3_SUCCESS;
    if ((resp.oldarg[0] & 0xff) != 1) {
        res = PM3_EFAILED;
    }
    return res;
}

int mf_write_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8_t *sector) {
    int res;
    for (int i = 0; i < mfNumBlocksPerSector(sectorNo); i++) {
        res = mf_write_block((mfFirstBlockOfSector(sectorNo)) + i, keyType, key, sector + (i * MFBLOCK_SIZE));
        if (res != PM3_SUCCESS) {
            return (i == 0) ? PM3_EFAILED : PM3_EPARTIAL;
        }
    }
    return PM3_SUCCESS;
}

// EMULATOR
int mf_eml_get_mem(uint8_t *data, int blockNum, int blocksCount) {
    return mf_eml_get_mem_xt(data, blockNum, blocksCount, MFBLOCK_SIZE);
}

int mf_eml_get_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) {

    size_t size = ((size_t) blocksCount) * blockBtWidth;
    if (size > PM3_CMD_DATA_SIZE) {
        return PM3_ESOFT;
    }

    struct {
        uint16_t blockno;
        uint8_t blockcnt;
        uint8_t blockwidth;
    } PACKED payload;

    payload.blockno = blockNum;
    payload.blockcnt = blocksCount;
    payload.blockwidth = blockBtWidth;

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_EML_MEMGET, (uint8_t *)&payload, sizeof(payload));

    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_EML_MEMGET, &resp, 1500) == false) {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }

    if (resp.status == PM3_SUCCESS) {
        memcpy(data, resp.data.asBytes, size);
    }

    return resp.status;
}

int mf_elm_set_mem(uint8_t *data, int blockNum, int blocksCount) {
    return mf_eml_set_mem_xt(data, blockNum, blocksCount, MFBLOCK_SIZE);
}

int mf_eml_set_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) {

    struct p {
        uint16_t blockno;
        uint8_t blockcnt;
        uint8_t blockwidth;
        uint8_t data[];
    } PACKED;

    size_t size = ((size_t) blocksCount) * blockBtWidth;
    if (size > (PM3_CMD_DATA_SIZE - sizeof(struct p))) {
        return PM3_EINVARG;
    }

    size_t paylen = sizeof(struct p) + size;
    struct p *payload = calloc(1, paylen);
    if (payload == NULL) {
        PrintAndLogEx(WARNING, "Failed to allocate memory");
        return PM3_EMALLOC;
    }

    payload->blockno = blockNum;
    payload->blockcnt = blocksCount;
    payload->blockwidth = blockBtWidth;
    memcpy(payload->data, data, size);

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_EML_MEMSET, (uint8_t *)payload, paylen);
    free(payload);
    return PM3_SUCCESS;
}

// "MAGIC" CARD
int mf_chinese_set_uid(uint8_t *uid, uint8_t uidlen, const uint8_t *atqa, const uint8_t *sak, uint8_t *old_uid, uint8_t *verifed_uid, uint8_t wipecard, uint8_t gdm) {

    uint8_t params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC);
    uint8_t block0[MFBLOCK_SIZE];
    memset(block0, 0x00, sizeof(block0));

    int res = mf_chinese_get_block(0, block0, params);
    if (res == 0) {
        PrintAndLogEx(SUCCESS, "old block 0... %s", sprint_hex_inrow(block0, sizeof(block0)));
        if (old_uid) {
            memcpy(old_uid, block0, uidlen);
        }
    } else {
        PrintAndLogEx(INFO, "couldn't get old data. Will write over the last bytes of block 0");
    }

    // fill in the new values
    // UID
    memcpy(block0, uid, uidlen);
    // Mifare UID BCC
    if (uidlen == 4) {
        block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3];

        // mifare classic SAK(byte 5) and ATQA(byte 6 and 7, reversed)
        if (sak)
            block0[5] = sak[0];

        if (atqa) {
            block0[6] = atqa[1];
            block0[7] = atqa[0];
        }

    } else if (uidlen == 7) {
        block0[7] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3] ^ block0[4] ^ block0[5] ^ block0[6];

        // mifare classic SAK(byte 8) and ATQA(byte 9 and 10, reversed)
        if (sak)
            block0[8] = sak[0];

        if (atqa) {
            block0[9] = atqa[1];
            block0[10] = atqa[0];
        }
    }

    PrintAndLogEx(SUCCESS, "new block 0... %s", sprint_hex_inrow(block0, sizeof(block0)));

    if (wipecard) {
        params |= MAGIC_WIPE;
    }

    res = mf_chinese_set_block(0, block0, NULL, params);
    if (res == PM3_SUCCESS) {
        params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC);
        memset(block0, 0, sizeof(block0));
        res = mf_chinese_get_block(0, block0, params);
        if (res == 0) {
            if (verifed_uid) {
                memcpy(verifed_uid, block0, uidlen);
            }
        }
    }
    return res;
}

int mf_chinese_wipe(uint8_t *uid, const uint8_t *atqa, const uint8_t *sak, uint8_t gdm) {
    uint8_t block0[MFBLOCK_SIZE] = {0x00, 0x56, 0x78, 0xBB, 0x95, 0x08, 0x04, 0x00, 0x02, 0xB2, 0x1E, 0x24, 0x23, 0x27, 0x1E, 0x1D};
    // uint8_t block0[MFBLOCK_SIZE] = {0x04, 0x03, 0x02, 0x01, 0x04, 0x08, 0x04, 0x00, 0x64, 0xB9, 0x95, 0x11, 0x4D, 0x20, 0x42, 0x09};
    uint8_t blockD[MFBLOCK_SIZE] = {0x00};
    // default transport ACL
    uint8_t blockK[MFBLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
    uint8_t params = MAGIC_SINGLE | (gdm ? MAGIC_GDM_ALT_WUPC : MAGIC_WUPC);

    if (uid != NULL) {
        memcpy(block0, uid, 4);
        block0[4] = block0[0] ^ block0[1] ^ block0[2] ^ block0[3];
    }

    if (sak != NULL) {
        block0[5] = sak[0];
    }

    if (atqa != NULL) {
        block0[6] = atqa[1];
        block0[7] = atqa[0];
    }

    int res;
    for (int blockNo = 0; blockNo < 4 * 16; blockNo++) {
        for (int retry = 0; retry < 3; retry++) {

            PrintAndLogEx(INPLACE, "wipe block %d", blockNo);

            if (blockNo == 0) {
                res = mf_chinese_set_block(blockNo, block0, NULL, params);
            } else {
                if (mfIsSectorTrailer(blockNo)) {
                    res = mf_chinese_set_block(blockNo, blockK, NULL, params);
                } else {
                    res = mf_chinese_set_block(blockNo, blockD, NULL, params);
                }
            }

            if (res == PM3_SUCCESS) {
                break;
            }

            PrintAndLogEx(WARNING, "retry block %d ...", blockNo);
        }

        if (res) {
            PrintAndLogEx(ERR, "error setting block %d (%d)", blockNo, res);
            return res;
        }
    }
    DropField();
    PrintAndLogEx(NORMAL, "");
    return PM3_SUCCESS;
}

int mf_chinese_set_block(uint8_t blockNo, uint8_t *data, uint8_t *uid, uint8_t params) {

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_CSETBL, params, blockNo, 0, data, MFBLOCK_SIZE);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 3500) == false) {
        PrintAndLogEx(NORMAL, "");
        PrintAndLogEx(WARNING, "Command execution time out");
        return PM3_ETIMEOUT;
    }

    uint8_t isOK  = resp.oldarg[0] & 0xFF;
    if (uid != NULL) {
        memcpy(uid, resp.data.asBytes, 4);
    }

    if (isOK == 0) {

        uint8_t reason = (resp.oldarg[1] & 0xFF);
        if (reason == 4) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "GDM magic write signature block failed");
        } else if (reason == 5) {
            PrintAndLogEx(NORMAL, "");
            PrintAndLogEx(WARNING, "Write block failed");
        }

        return PM3_EUNDEF;
    }

    return PM3_SUCCESS;
}

int mf_chinese_get_block(uint8_t blockNo, uint8_t *data, uint8_t params) {
    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_CGETBL, params, blockNo, 0, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_ACK, &resp, 1500)) {
        uint8_t isOK  = resp.oldarg[0] & 0xff;
        if (isOK == 0) {
            return PM3_EUNDEF;
        }
        memcpy(data, resp.data.asBytes, MFBLOCK_SIZE);
    } else {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }
    return PM3_SUCCESS;
}

int mf_chinese_gen_3_uid(uint8_t *uid, uint8_t uidlen, uint8_t *oldUid) {
    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_GEN3UID, uidlen, 0, 0, uid, uidlen);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3UID, &resp, 3500)) {
        if (resp.status == PM3_SUCCESS && oldUid) {
            memcpy(oldUid, resp.data.asBytes, uidlen);
        }
        return resp.status;
    } else {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }
}

int mf_chinese_gen_3_block(uint8_t *block, int blockLen, uint8_t *newBlock) {
    clearCommandBuffer();
    SendCommandMIX(CMD_HF_MIFARE_GEN3BLK, blockLen, 0, 0, block, MFBLOCK_SIZE);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3BLK, &resp, 3500) == false) {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }

    if (resp.status == PM3_SUCCESS && newBlock) {
        memcpy(newBlock, resp.data.asBytes, MFBLOCK_SIZE);
    }
    return resp.status;
}

int mf_chinese_gen_3_freeze(void) {
    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_GEN3FREEZ, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_GEN3FREEZ, &resp, 3500) == false) {
        PrintAndLogEx(WARNING, "command execution time out");
        return PM3_ETIMEOUT;
    }
    return resp.status;
}

void mf_crypto1_decrypt(struct Crypto1State *pcs, uint8_t *data, int len, bool isEncrypted) {
    if (len != 1) {
        for (int i = 0; i < len; i++) {
            data[i] = crypto1_byte(pcs, 0x00, isEncrypted) ^ data[i];
        }
    } else {
        uint8_t bt = 0;
        bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 0)) << 0;
        bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 1)) << 1;
        bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 2)) << 2;
        bt |= (crypto1_bit(pcs, 0, isEncrypted) ^ BIT(data[0], 3)) << 3;
        data[0] = bt;
    }
}

int try_decrypt_word(uint32_t nt, uint32_t ar_enc, uint32_t at_enc, uint8_t *data, int len) {

    PrintAndLogEx(SUCCESS, "encrypted data... %s", sprint_hex(data, len));
    uint32_t ks2 = ar_enc ^ prng_successor(nt, 64);
    uint32_t ks3 = at_enc ^ prng_successor(nt, 96);

    struct Crypto1State *s = lfsr_recovery64(ks2, ks3);
    mf_crypto1_decrypt(s, data, len, false);
    PrintAndLogEx(SUCCESS, "decrypted data... " _YELLOW_("%s"), sprint_hex(data, len));
    PrintAndLogEx(NORMAL, "");
    crypto1_destroy(s);
    return PM3_SUCCESS;
}

/* Detect Tag Prng,
* function performs a partial AUTH,  where it tries to authenticate against block0, key A, but only collects tag nonce.
* the tag nonce is check to see if it has a predictable PRNG.
* @returns
*   TRUE if tag uses WEAK prng (ie Now the NACK bug also needs to be present for Darkside attack)
*   FALSE is tag uses HARDEND prng (ie hardnested attack possible, with known key)
*/
int detect_classic_prng(void) {

    PacketResponseNG resp, respA;
    uint8_t cmd[] = {MIFARE_AUTH_KEYA, 0x00};
    uint32_t flags = ISO14A_CONNECT | ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_RATS;

    clearCommandBuffer();
    SendCommandMIX(CMD_HF_ISO14443A_READER, flags, sizeof(cmd), 0, cmd, sizeof(cmd));
    if (WaitForResponseTimeout(CMD_ACK, &resp, 2000) == false) {
        PrintAndLogEx(WARNING, "timeout while waiting for reply");
        return PM3_ETIMEOUT;
    }

    // if select tag failed.
    if (resp.oldarg[0] == 0) {
        PrintAndLogEx(ERR, "error:  selecting tag failed,  can't detect prng\n");
        return PM3_ERFTRANS;
    }
    if (WaitForResponseTimeout(CMD_ACK, &respA, 2500) == false) {
        PrintAndLogEx(WARNING, "timeout while waiting for reply");
        return PM3_ETIMEOUT;
    }

    // check respA
    if (respA.oldarg[0] != 4) {
        PrintAndLogEx(ERR, "PRNG data error: Wrong length: %"PRIu64, respA.oldarg[0]);
        return PM3_ESOFT;
    }

    uint32_t nonce = bytes_to_num(respA.data.asBytes, respA.oldarg[0]);
    return validate_prng_nonce(nonce);
}
/* Detect Mifare Classic NACK bug

returns:
0 = error during test / aborted
1 = has nack bug
2 = has not nack bug
3 = always leak nacks  (clones)
*/
int detect_classic_nackbug(bool verbose) {

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_NACK_DETECT, NULL, 0);
    PacketResponseNG resp;

    PrintAndLogEx(INFO, "Checking for NACK bug");

    if (verbose) {
        PrintAndLogEx(SUCCESS, "press " _GREEN_("pm3 button") " to abort both Proxmark3 and client\n");
    }

    PrintAndLogEx(INFO, "." NOLF);

    while (true) {

        PrintAndLogEx(NORMAL, "." NOLF);
        if (kbd_enter_pressed()) {
            return PM3_EOPABORTED;
        }

        if (WaitForResponseTimeout(CMD_HF_MIFARE_NACK_DETECT, &resp, 500)) {

            PrintAndLogEx(NORMAL, "");

            if (resp.status == PM3_EOPABORTED) {
                PrintAndLogEx(WARNING, "button pressed. Aborted.");
                return PM3_EOPABORTED;
            }

            uint8_t ok = resp.data.asBytes[0];
            uint8_t nacks = resp.data.asBytes[1];
            uint16_t auths = bytes_to_num(resp.data.asBytes + 2, 2);

            if (verbose) {
                PrintAndLogEx(SUCCESS, "num of auth requests  : %u", auths);
                PrintAndLogEx(SUCCESS, "num of received NACK  : %u", nacks);
            }

            switch (ok) {
                case 96 :
                case 98 : {
                    if (verbose)
                        PrintAndLogEx(FAILED, "card random number generator is not predictable.");
                    PrintAndLogEx(WARNING, "detection failed");
                    return PM3_SUCCESS;
                }
                case 97 : {
                    if (verbose) {
                        PrintAndLogEx(FAILED, "card random number generator seems to be based on the well-known generating polynomial");
                        PrintAndLogEx(FAILED, "with 16 effective bits only, but shows unexpected behavior, try again.");
                    }
                    return PM3_SUCCESS;
                }
                case  2: {
                    PrintAndLogEx(SUCCESS, "NACK test: " _GREEN_("always leak NACK"));
                    return PM3_SUCCESS;
                }
                case  1: {
                    PrintAndLogEx(SUCCESS, "NACK test: " _GREEN_("detected"));
                    return PM3_SUCCESS;
                }
                case  0: {
                    PrintAndLogEx(SUCCESS, "NACK test: " _GREEN_("no bug"));
                    return PM3_SUCCESS;
                }
                default: {
                    PrintAndLogEx(ERR, "errorcode from device " _RED_("[%i]"), ok);
                    return PM3_EUNDEF;
                }
            }
            break;
        }
    }
    return PM3_SUCCESS;
}

/* Detect Mifare Classic Static / Fixed nonce
detects special magic cards that has a static / fixed nonce
returns:
0  = has normal nonce
1  = has static/fixed nonce
2  = cmd failed
*/
int detect_classic_static_nonce(void) {

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_STATIC_NONCE, NULL, 0);
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_NONCE, &resp, 1000)) {

        if (resp.status == PM3_ESOFT) {
            return NONCE_FAIL;
        }

        return resp.data.asBytes[0];
    }
    return NONCE_FAIL;
}

/* Detect Mifare Classic static encrypted nonce
detects special magic cards that has a static / fixed nonce
returns:
0  = nonce ok
1  = has static/fixed nonce
2  = cmd failed
3  = has encrypted nonce
*/
int detect_classic_static_encrypted_nonce_ex(uint8_t block_no, uint8_t key_type, const uint8_t *key, uint8_t block_no_nested
                                             , uint8_t key_type_nested, const uint8_t *key_nested
                                             , uint8_t nr_nested, bool reset, bool hardreset
                                             , bool addread, bool addauth, bool incblk2
                                             , bool corruptnrar, bool corruptnrarparity, bool verbose) {

    uint8_t cdata[1 + 1 + MIFARE_KEY_SIZE + 1 + 1 + MIFARE_KEY_SIZE + 1 + 1 + 1 + 1 + 1 + 1 + 1] = { 0 };
    cdata[0] = block_no;
    cdata[1] = key_type;
    memcpy(&cdata[2], key, MIFARE_KEY_SIZE);
    cdata[8] = block_no_nested;
    cdata[9] = key_type_nested;
    memcpy(&cdata[10], key_nested, MIFARE_KEY_SIZE);
    cdata[16] = nr_nested;
    cdata[17] = hardreset << 1 | reset;
    cdata[18] = addread;
    cdata[19] = addauth;
    cdata[20] = incblk2;
    cdata[21] = corruptnrar;
    cdata[22] = corruptnrarparity;

    clearCommandBuffer();
    SendCommandNG(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, cdata, sizeof(cdata));
    PacketResponseNG resp;
    if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1000)) {

        if (resp.status == PM3_ESOFT) {
            return NONCE_FAIL;
        }

        if (verbose && (resp.data.asBytes[0] == NONCE_STATIC_ENC)) {

            uint32_t uid = resp.data.asBytes[1] << 24 |
                           resp.data.asBytes[2] << 16 |
                           resp.data.asBytes[3] << 8 |
                           resp.data.asBytes[4];

            uint32_t nt = resp.data.asBytes[5] << 24 |
                          resp.data.asBytes[6] << 16 |
                          resp.data.asBytes[7] << 8 |
                          resp.data.asBytes[8];

            uint32_t ntenc = resp.data.asBytes[9] << 24 |
                             resp.data.asBytes[10] << 16 |
                             resp.data.asBytes[11] << 8 |
                             resp.data.asBytes[12];

            uint8_t ntencparenc = resp.data.asBytes[13];

            // recompute nt on client, just because
            struct Crypto1State mpcs = {0, 0};
            struct Crypto1State *pcs;
            pcs = &mpcs;

            uint64_t ui64key = bytes_to_num(key_nested, MIFARE_KEY_SIZE);

            crypto1_init(pcs, ui64key); // key_nested
            uint32_t ks = crypto1_word(pcs, ntenc ^ uid, 1);

            uint32_t mynt = ks ^ ntenc;
            if (mynt != nt) {
                PrintAndLogEx(ERR, "Client computed nT " _YELLOW_("%08x") " does not match ARM computed nT " _YELLOW_("%08x"), mynt, nt);
            }

            ntencparenc >>= 4;
            // [...] Additionally, the bit of keystream used to encrypt the parity bits is reused to encrypt the next bit of plaintext.
            // we can decrypt first 3 parity bits, not last one as it's using future keystream
            uint8_t ksp = (((ks >> 16) & 1) << 3) | (((ks >> 8) & 1) << 2) | (((ks >> 0) & 1) << 1);
            uint8_t ntencpar = ntencparenc ^ ksp;
            if (validate_prng_nonce(nt)) {
                PrintAndLogEx(INFO, "nTenc " _GREEN_("%08x") " par {" _YELLOW_("%i%i%i%i") "}=" _YELLOW_("%i%i%ix") " | ks "  _GREEN_("%08x") " | nT " _GREEN_("%08x") " par " _YELLOW_("%i%i%i%i")" | lfsr16 index " _GREEN_("%i"),
                              ntenc,
                              (ntencparenc >> 3) & 1, (ntencparenc >> 2) & 1, (ntencparenc >> 1) & 1, ntencparenc & 1,
                              (ntencpar >> 3) & 1, (ntencpar >> 2) & 1, (ntencpar >> 1) & 1,
                              ks, nt,
                              oddparity8((nt >> 24) & 0xFF), oddparity8((nt >> 16) & 0xFF), oddparity8((nt >> 8) & 0xFF), oddparity8(nt & 0xFF),
                              nonce_distance(0, nt)
                             );
            } else {
                PrintAndLogEx(INFO, "nTenc " _GREEN_("%08x") " par {" _YELLOW_("%i%i%i%i") "}=" _YELLOW_("%i%i%ix") " | ks "  _YELLOW_("%08x") " | nT " _YELLOW_("%08x") " par " _YELLOW_("%i%i%i%i") " | " _RED_("not lfsr16") " (wrong key)",
                              ntenc,
                              (ntencparenc >> 3) & 1, (ntencparenc >> 2) & 1, (ntencparenc >> 1) & 1, ntencparenc & 1,
                              (ntencpar >> 3) & 1, (ntencpar >> 2) & 1, (ntencpar >> 1) & 1,
                              ks, nt,
                              oddparity8((nt >> 24) & 0xFF), oddparity8((nt >> 16) & 0xFF), oddparity8((nt >> 8) & 0xFF), oddparity8(nt & 0xFF)
                             );
            }
        }
        return resp.data.asBytes[0];
    }
    return NONCE_FAIL;
}

int detect_classic_static_encrypted_nonce(uint8_t block_no, uint8_t key_type, const uint8_t *key) {
    return detect_classic_static_encrypted_nonce_ex(block_no, key_type, key, block_no, key_type, key, 3, false, false, false, false, false, false, false, false);
}

// try to see if card responses to "Chinese magic backdoor" commands.
// returns flag
uint16_t detect_mf_magic(bool is_mfc, uint8_t key_type, uint64_t key) {

    PacketResponseNG resp;
    clearCommandBuffer();
    uint8_t payload[1 + 1 + MIFARE_KEY_SIZE] = { is_mfc, key_type };
    num_to_bytes(key, MIFARE_KEY_SIZE, payload + 2);

    SendCommandNG(CMD_HF_MIFARE_CIDENT, payload, sizeof(payload));
    if (WaitForResponseTimeout(CMD_HF_MIFARE_CIDENT, &resp, 1500)) {
        if (resp.status != PM3_SUCCESS) {
            return MAGIC_FLAG_NONE;
        }
    }

    uint16_t isMagic = MAGIC_FLAG_NONE;
    if ((resp.status == PM3_SUCCESS) && resp.length == sizeof(uint16_t)) {
        isMagic = MemLeToUint2byte(resp.data.asBytes);
    }

    if (isMagic) {
        PrintAndLogEx(NORMAL, "");
    }

    if ((isMagic & MAGIC_FLAG_GEN_1A) == MAGIC_FLAG_GEN_1A) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 1a"));
    }

    if ((isMagic & MAGIC_FLAG_GEN_1B) == MAGIC_FLAG_GEN_1B) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 1b"));
    }

    if ((isMagic & MAGIC_FLAG_GEN_2) == MAGIC_FLAG_GEN_2) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 2 / CUID"));
    }

    if ((isMagic & MAGIC_FLAG_GEN_3) == MAGIC_FLAG_GEN_3) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 3 / APDU") " ( possibly )");
    }

    if ((isMagic & MAGIC_FLAG_GEN_4GTU) == MAGIC_FLAG_GEN_4GTU) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GTU"));
    }

    if ((isMagic & MAGIC_FLAG_GDM_AUTH) == MAGIC_FLAG_GDM_AUTH) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Magic Auth )");
    }

    if ((isMagic & MAGIC_FLAG_GDM_WUP_20) == MAGIC_FLAG_GDM_WUP_20) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Gen4 Magic Wakeup )");
    }

    if ((isMagic & MAGIC_FLAG_GDM_WUP_40) == MAGIC_FLAG_GDM_WUP_40) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( Gen1 Magic Wakeup )");
    }

    if ((isMagic & MAGIC_FLAG_GDM_WUP_40_ZUID) == MAGIC_FLAG_GDM_WUP_40_ZUID) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Gen 4 GDM / USCUID") " ( ZUID Gen1 Magic Wakeup )");
    }

    if ((isMagic & MAGIC_FLAG_GEN_UNFUSED) == MAGIC_FLAG_GEN_UNFUSED) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Write Once / FUID"));
    }

    if ((isMagic & MAGIC_FLAG_SUPER_GEN1) == MAGIC_FLAG_SUPER_GEN1) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Super card ( ") _CYAN_("Gen 1") _GREEN_(" )"));
    }

    if ((isMagic & MAGIC_FLAG_SUPER_GEN2) == MAGIC_FLAG_SUPER_GEN2) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("Super card ( ") _CYAN_("Gen 2") _GREEN_(" )"));
    }

    if ((isMagic & MAGIC_FLAG_NTAG21X) == MAGIC_FLAG_NTAG21X) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("NTAG21x"));
    }

    if ((isMagic & MAGIC_FLAG_QL88) == MAGIC_FLAG_QL88) {
        PrintAndLogEx(SUCCESS, "Magic capabilities... " _GREEN_("QL88"));
    }
    return isMagic;
}

bool detect_mfc_ev1_signature(void) {
    uint64_t key = 0;
    int res = mf_check_keys(69, MF_KEY_B, false, 1, (uint8_t *)g_mifare_signature_key_b, &key);
    return (res == PM3_SUCCESS);
}

int read_mfc_ev1_signature(uint8_t *signature) {
    if (signature == NULL) {
        return PM3_EINVARG;
    }
    uint8_t sign[32] = {0};
    int res = mf_read_block(69, MF_KEY_B, g_mifare_signature_key_b, sign);
    if (res == PM3_SUCCESS) {
        res = mf_read_block(70, MF_KEY_B, g_mifare_signature_key_b, sign + 16);
        if (res ==  PM3_SUCCESS) {
            memcpy(signature, sign, sizeof(sign));
        }
    } else {
        // try QL88
        res = mf_read_block(69, MF_KEY_B, g_mifare_ql88_signature_key_b, sign);
        if (res == PM3_SUCCESS) {
            res = mf_read_block(70, MF_KEY_B, g_mifare_ql88_signature_key_b, sign + 16);
            if (res ==  PM3_SUCCESS) {
                memcpy(signature, sign, sizeof(sign));
            }
        }
    }
    return res;
}

int convert_mfc_2_arr(uint8_t *in, uint16_t ilen, uint8_t *out, uint16_t *olen) {
    if (in == NULL || out == NULL) {
        return PM3_EINVARG;
    }

    uint8_t blockno = 0;
    while (ilen) {

        if (mfIsSectorTrailer(blockno) == false) {
            memcpy(out, in, MFBLOCK_SIZE);
            out += MFBLOCK_SIZE;
            *olen += MFBLOCK_SIZE;
        }
        blockno++;
        in += MFBLOCK_SIZE;
        ilen -= MFBLOCK_SIZE;
    }
    return PM3_SUCCESS;
}

static const vigik_pk_t vigik_rsa_pk[] = {
    {"La Poste Service Universel", 0x07AA, "AB9953CBFCCD9375B6C028ADBAB7584BED15B9CA037FADED9765996F9EA1AB983F3041C90DA3A198804FF90D5D872A96A4988F91F2243B821E01C5021E3ED4E1BA83B7CFECAB0E766D8563164DE0B2412AE4E6EA63804DF5C19C7AA78DC14F608294D732D7C8C67A88C6F84C0F2E3FAFAE34084349E11AB5953AC68729D07715"},
    {"La Poste Service Universel", 0x07AA, "1577D02987C63A95B51AE149430834AEAF3F2E0F4CF8C6887AC6C8D732D79482604FC18DA77A9CC1F54D8063EAE6E42A41B2E04D1663856D760EABECCFB783BAE1D43E1E02C5011E823B24F2918F98A4962A875D0DF94F8098A1A30DC941303F98ABA19E6F996597EDAD7F03CAB915ED4B58B7BAAD28C0B67593CDFCCB5399AB"},

    {"La Poste Autres Services", 0x07AB, "A6D99B8D902893B04F3F8DE56CB6BF24338FEE897C1BCE6DFD4EBD05B7B1A07FD2EB564BB4F7D35DBFE0A42966C2C137AD156E3DAB62904592BCA20C0BC7B8B1E261EF82D53F52D203843566305A49A22062DECC38C2FE3864CAD08E79219487651E2F79F1C9392B48CAFE1BFFAFF4802AE451E7A283E55A4026AD1E82DF1A15"},
    {"La Poste Autres Services", 0x07AB, "151adf821ead26405ae583a2e751e42a80f4afff1bfeca482b39c9f1792f1e65879421798ed0ca6438fec238ccde6220a2495a3066358403d2523fd582ef61e2b1b8c70b0ca2bc92459062ab3d6e15ad37c1c26629a4e0bf5dd3f7b44b56ebd27fa0b1b705bd4efd6dce1b7c89ee8f3324bfb66ce58d3f4fb09328908d9bd9a6"},

    {"France Telecom", 0x07AC, "C44DBCD92F9DCF42F4902A87335DBB35D2FF530CDB09814CFA1F4B95A1BD018D099BC6AB69F667B4922AE1ED826E72951AA3E0EAAA7D49A695F04F8CDAAE2D18D10D25BD529CBB05ABF070DC7C041EC35C2BA7F58CC4C349983CC6E11A5CBE828FB8ECBC26F08E1094A6B44C8953C8E1BAFD214DF3E69F430A98CCC75C03669D"},
    {"France Telecom", 0x07AC, "9d66035cc7cc980a439fe6f34d21fdbae1c853894cb4a694108ef026bcecb88f82be5c1ae1c63c9849c3c48cf5a72b5cc31e047cdc70f0ab05bb9c52bd250dd1182daeda8c4ff095a6497daaeae0a31a95726e82ede12a92b467f669abc69b098d01bda1954b1ffa4c8109db0c53ffd235bb5d33872a90f442cf9d2fd9bc4dc4"},

    {"EDF-GDF", 0x07AD, "B35193DBD2F88A21CDCFFF4BF84F7FC036A991A363DCB3E802407A5E5879DC2127EECFC520779E79E911394882482C87D09A88B0711CBC2973B77FFDAE40EA0001F595072708C558B484AB89D02BCBCB971FF1B80371C0BE30CB13661078078BB68EBCCA524B9DD55EBF7D47D9355AFC95511350CC1103A5DEE847868848B235"},
    {"EDF-GDF", 0x07AD, "35b248888647e8dea50311cc50135195fc5a35d9477dbf5ed59d4b52cabc8eb68b0778106613cb30bec07103b8f11f97cbcb2bd089ab84b458c508270795f50100ea40aefd7fb77329bc1c71b0889ad0872c4882483911e9799e7720c5cfee2721dc79585e7a4002e8b3dc63a391a936c07f4ff84bffcfcd218af8d2db9351b3"},
    {NULL, 0, NULL}
};

const char *vigik_get_service(uint16_t service_code) {
    for (int i = 0; i < ARRAYLEN(vigik_rsa_pk); ++i)
        if (service_code == vigik_rsa_pk[i].code)
            return vigik_rsa_pk[i].desc;

    //No match, return default
    return vigik_rsa_pk[ARRAYLEN(vigik_rsa_pk) - 1].desc;
}


int vigik_verify(mfc_vigik_t *d) {
#define PUBLIC_VIGIK_KEYLEN 128

    // iso9796
    // Exponent V = 2
    // n = The public modulus n is the product of the secret prime factors p and q. Its length is 1024 bits.

    if (g_debugMode == DEBUG) {
        PrintAndLogEx(INFO, "Raw");
        print_hex_noascii_break((uint8_t *)d, sizeof(*d) - sizeof(d->rsa_signature), MFBLOCK_SIZE * 2);

        PrintAndLogEx(INFO, "Raw signature");
        print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2);
    }

    /*
        int dl = 0;

            param_gethex_to_eol("1C07D46DA3849326D24B3468BD76673F4F3C41827DC413E81E4F3C7804FAC727213059B21D047510D6432448643A92EBFC67FBEDDAB468D13D948B172F5EBC79A0E3FEFDFAF4E81FC7108E070F1E3CD0", 0, signature, PUBLIC_VIGIK_KEYLEN, &dl);

        param_gethex_to_eol("1AB86FE0C17FFFFE4379D5E15A4B2FAFFEFCFA0F1F3F7FA03E7DDDF1E3C78FFFB1F0E23F7FFF51584771C5C18307FEA36CA74E60AA6B0409ACA66A9EC155F4E9112345708A2B8457E722608EE1157408", 0, signature, PUBLIC_VIGIK_KEYLEN, &dl);
        signature_len = dl;
        */

    uint8_t rev_sig[128];
    reverse_array_copy(d->rsa_signature, sizeof(d->rsa_signature), rev_sig);

    PrintAndLogEx(INFO, "Raw signature reverse");
    print_hex_noascii_break(rev_sig, sizeof(d->rsa_signature), MFBLOCK_SIZE * 2);

    // t = 0xBC  = Implicitly known
    // t = 0xCC  = look at byte before to determine hash function
    // uint8_t T[] = {0x33, 0xCC};

    // Success decrypt would mean  0x4b BB ... BB BA padding
    // padding, message,  hash, 8 bits or 16 bits

    // signature = h( C || M1 || h(M2) )
    // 1024 - 786 - 160 - 16 -1
    // salt C
    // message M = 96 bytes,  768 bits
    // sha1 hash H = 20 bytes, 160 bits
    // padding = 20 bytes, 96 bits

    uint8_t i;
    bool is_valid = false;

    for (i = 0; i < ARRAYLEN(vigik_rsa_pk); i++) {
        if (vigik_rsa_pk[i].desc == NULL) {
            break;
        }

        mbedtls_mpi RN, E;
        mbedtls_mpi_init(&RN);

        // exponent 2 = even
        mbedtls_mpi_init(&E);
        mbedtls_mpi_add_int(&E, &E, 2);

        int dl = 0;
        uint8_t n[PUBLIC_VIGIK_KEYLEN];
        memset(n, 0, sizeof(n));
        param_gethex_to_eol(vigik_rsa_pk[i].n, 0, n, PUBLIC_VIGIK_KEYLEN, &dl);

        // convert
        mbedtls_mpi N, s, sqr, res;
        mbedtls_mpi_init(&N);
        mbedtls_mpi_init(&s);
        mbedtls_mpi_init(&sqr);
        mbedtls_mpi_init(&res);

        mbedtls_mpi_read_binary(&N, (const unsigned char *)n, PUBLIC_VIGIK_KEYLEN);

        //mbedtls_mpi_read_binary(&s, (const unsigned char*)signature, signature_len);
        mbedtls_mpi_read_binary(&s, (const unsigned char *)rev_sig, sizeof(d->rsa_signature));

        // check is sign < (N/2)

        mbedtls_mpi n_2;
        mbedtls_mpi_init(&n_2);
        mbedtls_mpi_copy(&n_2, &N);
        mbedtls_mpi_shift_r(&n_2, 1);
        bool is_less = (mbedtls_mpi_cmp_mpi(&s, &n_2) > 0) ? false : true;
        PrintAndLogEx(DEBUG, "z < (N/2) ..... %s", (is_less) ? _GREEN_("YES") : _RED_("NO"));
        mbedtls_mpi_free(&n_2);


        if (is_less) {
            mbedtls_mpi_exp_mod(&sqr, &s, &E, &N, &RN);
        } else {
            continue;
        }

        /*
            if v is even and
            ⎯ if J* mod 8 = 1, then f* = n–J*.
            ⎯ if J* mod 8 = 4, then f* = J*,
            ⎯ if J* mod 8 = 6, then f* = 2J*,
            ⎯ if J* mod 8 = 7, then f* = 2(n–J*),
        */
        uint8_t b2 = mbedtls_mpi_get_bit(&sqr, 2);
        uint8_t b1 = mbedtls_mpi_get_bit(&sqr, 1);
        uint8_t b0 = mbedtls_mpi_get_bit(&sqr, 0);
        uint8_t lsb = (b2 << 2) | (b1 << 1) | b0;

        /*
        //1
        mbedtls_mpi_sub_mpi(&res, &N, &sqr);
        mbedtls_mpi_write_file( "[=] 1... ", &res, 16, NULL );
        // 4
        mbedtls_mpi_copy(&res, &sqr);
        mbedtls_mpi_write_file( "[=] 4... ", &res, 16, NULL );
        // 6
        mbedtls_mpi_mul_int(&res, &sqr, 2);
        mbedtls_mpi_write_file( "[=] 6... ", &res, 16, NULL );
        // 7
        mbedtls_mpi foo;
        mbedtls_mpi_init(&foo);
        mbedtls_mpi_sub_mpi(&foo, &N, &sqr);
        mbedtls_mpi_mul_int(&res, &foo, 2);
        mbedtls_mpi_free(&foo);
        mbedtls_mpi_write_file( "[=] 7... ", &res, 16, NULL );
        */

        switch (lsb) {
            case 1: {
                mbedtls_mpi_sub_mpi(&res, &N, &sqr);
                break;
            }
            case 4: {
                mbedtls_mpi_copy(&res, &sqr);
                break;
            }
            case 6: {
                mbedtls_mpi_mul_int(&res, &sqr, 2);
                break;
            }
            case 7: {
                mbedtls_mpi foo2;
                mbedtls_mpi_init(&foo2);
                mbedtls_mpi_sub_mpi(&foo2, &N, &sqr);
                mbedtls_mpi_mul_int(&res, &foo2, 2);
                mbedtls_mpi_free(&foo2);
                break;
            }
            default: {
                continue;
            }
        }

        PrintAndLogEx(DEBUG, "LSB............ " _GREEN_("%u"), lsb);
        if (g_debugMode == DEBUG) {
            mbedtls_mpi_write_file("[=] N.............. ", &N, 16, NULL);
            mbedtls_mpi_write_file("[=] signature...... ", &s, 16, NULL);
            mbedtls_mpi_write_file("[=] square mod n... ", &sqr, 16, NULL);
            mbedtls_mpi_write_file("[=] n-fs........... ", &res, 16, NULL);
        }


        uint8_t nfs[128] = {0};
        mbedtls_mpi_write_binary(&res, nfs, sizeof(nfs));

        // xor 0xDC01
        int count_zero = 0;
        for (int x = 0; x < sizeof(nfs); x += 2) {
            nfs[x] ^= 0xDC;
            nfs[x + 1] ^= 0x01;

            if (nfs[x] == 0x00)
                count_zero++;
            if (nfs[x + 1] == 0x00)
                count_zero++;
        }

        if (count_zero > 10)  {
            PrintAndLogEx(INFO, "");
            PrintAndLogEx(INFO, "Message XORED");
            print_hex_noascii_break(nfs, sizeof(nfs), 32);
            PrintAndLogEx(INFO, "\n");
            is_valid = true;
            break;
        }

        /*
        if (bar == 0) {
            typedef struct vigik_rsa_s {
                uint8_t rsa[127];
                uint8_t hash;
            } vigik_rsa_t;

            vigik_rsa_t ts;
            memcpy(&ts, nfs, sizeof(ts));

            if ( ts.hash == 0xCC ) {
                PrintAndLogEx(INFO, "Hash byte... 0x%02X", ts.hash);
                switch(ts.rsa[126]) {
                    case 0x11:
                        PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - SHA1");
                        break;
                    case 0x22:
                        PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - RIPEMD");
                        break;
                    case 0x33:
                        PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - SHA1");
                        break;
                    default:
                        PrintAndLogEx(INFO, "Hash algo ( 0x%02X ) - " _RED_("err"));
                        break;
                }
            } else if ( ts.hash == 0xBC) {
                PrintAndLogEx(INFO, "Hash byte... 0x%02X - " _GREEN_("implict"), ts.hash);
            } else {
                PrintAndLogEx(INFO, "Hash byte... 0x%02x - " _RED_("err"), ts.hash);
            }

            PrintAndLogEx(INFO, "Message w padding");
            print_hex_noascii_break(ts.rsa, sizeof(ts.rsa) - 20, 32);
        }
        */

        mbedtls_mpi_free(&N);
        mbedtls_mpi_free(&s);
        mbedtls_mpi_free(&res);
        mbedtls_mpi_free(&RN);
        mbedtls_mpi_free(&E);
    }

    PrintAndLogEx(INFO, "");
    PrintAndLogEx(INFO, "--- " _CYAN_("Tag Signature"));
    PrintAndLogEx(INFO, "RSA: 1024bit");

    if (is_valid == false || i == ARRAYLEN(vigik_rsa_pk)) {
        PrintAndLogEx(INFO, "Signature:");
        print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature),  MFBLOCK_SIZE * 2);
        PrintAndLogEx(SUCCESS, "Signature verification: " _RED_("failed"));
        return PM3_ESOFT;
    }

    PrintAndLogEx(INFO, "Signature public key name: " _YELLOW_("%s"), vigik_rsa_pk[i].desc);
    PrintAndLogEx(INFO, "Signature public key value:");
    PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n);
    PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 64);
    PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 128);
    PrintAndLogEx(INFO, "%.64s", vigik_rsa_pk[i].n + 192);

    PrintAndLogEx(INFO, "Signature:");
    print_hex_noascii_break(d->rsa_signature, sizeof(d->rsa_signature),  MFBLOCK_SIZE * 2);

    PrintAndLogEx(SUCCESS, "Signature verification: " _GREEN_("successful"));

    return PM3_SUCCESS;
}

int vigik_annotate(mfc_vigik_t *d) {
    if (d == NULL)
        return PM3_EINVARG;

    PrintAndLogEx(INFO, "Manufacture......... %s", sprint_hex(d->b0, sizeof(d->b0)));
    PrintAndLogEx(INFO, "MAD................. %s", sprint_hex(d->mad, sizeof(d->mad)));
    PrintAndLogEx(INFO, "Counters............ %u", d->counters);
    PrintAndLogEx(INFO, "rtf................. %s", sprint_hex(d->rtf, sizeof(d->rtf)));
    PrintAndLogEx(INFO, "Service code........ 0x%08x / %u  - " _YELLOW_("%s"), d->service_code, d->service_code, vigik_get_service(d->service_code));
    PrintAndLogEx(INFO, "Info flag........... %u -", d->info_flag); // ,  sprint_bin(d->info_flag, 1));
    PrintAndLogEx(INFO, "Key version......... %u", d->key_version);
    PrintAndLogEx(INFO, "PTR Counter......... %u", d->ptr_counter);
    PrintAndLogEx(INFO, "Counter num......... %u", d->counter_num);
    PrintAndLogEx(INFO, "Slot access date.... %s", sprint_hex(d->slot_access_date, sizeof(d->slot_access_date)));
    PrintAndLogEx(INFO, "Slot dst duration... %u", d->slot_dst_duration);
    PrintAndLogEx(INFO, "Other Slots......... %s", sprint_hex(d->other_slots, sizeof(d->other_slots)));
    PrintAndLogEx(INFO, "Services counter.... %u", d->services_counter);
    PrintAndLogEx(INFO, "Loading date........ %s", sprint_hex(d->loading_date, sizeof(d->loading_date)));
    PrintAndLogEx(INFO, "Reserved null....... %u", d->reserved_null);
    PrintAndLogEx(INFO, "----------------------------------------------------------------");
    PrintAndLogEx(INFO, "");
    vigik_verify(d);
    PrintAndLogEx(INFO, "----------------------------------------------------------------");
    PrintAndLogEx(INFO, "");
    return PM3_SUCCESS;

}
