/***
 * This example of ESP32 (Pycom LoPy4) firmware was developed by Javier Arcenegui at "Instituto de 
 * Microelectrónica de Sevilla IMSE-CNM (Universidad de Sevilla-CSIC)" for the EIP4519. This firmware makes the device generate its own Ethereum account and save in the EEPROM the data required in the future to regenerate 
 * the same account. In the newest versions of ESP32 ("ESP32 S2" or 
 * "ESP32 S3") these data can be saved in a protected area. 
 * 
 * The Smart Contract is published on "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97".
 * It was proved in the Ethereum Kovan testnet.
 * 
 * This code was developed with the infuraIO extension for Visual Studio Code and the Arduino Framework. 
 * The alphawallet/Web3E@^1.22 library must be added in the configuration file (platformio.ini)
 * 
 * Helper data are employed to regenerate the same Ethereum account based on CTR-DRBG PRNG.
 * The helper data format is the following:
 * 
 * ------------------------- Helper data Format -----------------------
 *  |---------------|----------------|----------------|----|----|----|
 *  |     key_ctr   |Personalization | contex_counter | rc | el | ri |
 *  |---------------|----------------|----------------|----|----|----|
 * 51              35               19                3    2    1    0 (Byte)
 * 
 ***/

#include <Arduino.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include <Web3.h>
#include <EEPROM.h>
#include "esp_wifi.h"
#include <WiFi.h>
#include <Contract.h>
#include <string>
#include <Util.h>
#include "Trezor/secp256k1.h"
#include "Trezor/ecdsa.h"
#include "Trezor/sha3.h"

#define EEPROM_SIZE 52 //The size required by the 51 bytes of helper data and the byte to know if the token is registered on the blockchain
#define EIP4519CONTRACT "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97"  //This is the reference for the EIP4519 Smart Non-Fungible Token on Kovan (IT MUST BE CHANGED)

//These are the states of the EIP4519
enum STATES{
    waitingForOwner = 0,
    engagedWithOwner = 1,
    waitingForUser = 2,
    engagedWithUser = 3
};

void setupWifi(); //This function is the same as the wifi example of the ESP32 Arduino Wifi
uint8_t HexTo4Bits(uint8_t Hex); //This function is used to change an hexadecimal defined in ASCII character to a uint8_t
STATES queryTokenStatus(); //This function is used to recover the information of the SmartNFT associated to this device
void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL); //This function is needed to convert an uint8_t to two ASCII characters

void sendEngage(); //This function sends the hash of the shared key to complete the transaction in Ethereum

//Declaration of the variable to save the state of the SmartNFT
STATES tokenState;

//Declaration of the variables for the private/public Keys and the Address associated

unsigned char privateKey_ethAccount[32];
unsigned char publicKey_ethAccount[64];
unsigned char ethAddress[20];

//Declaration of the variable to save the helper data generated/recovered, which are employed to obtain the private Key of Ethereum account
uint8_t helperData[51];

//These variables are used to save the addresses in ASCII associated to the SmartNFT and the SmartNFT ID
unsigned char OwnerAddress[42];
unsigned char UserAddress[42];
unsigned char deviceAddress[43];
uint32_t tokenID;

//Wifi setup parameters: IT MUST BE CHANGED
int wificounter = 0;
const char *ssid = "<your SSID>";
const char *password = "<your password";

//Infura setup parameters: IT MUST BE CHANGED
const char *INFURA_HOST = "kovan.infura.io"; 
const char *INFURA_PATH = "/v3/c7df4c29472d4d54a39f7aa78f146843"; //Please, use your own path because maybe this path is disabled

Web3 web3(INFURA_HOST, INFURA_PATH);
Crypto crypto(&web3);

//These variables are required to check the authentication with the Owner and the User
uint8_t ADDRESS_E[20];
uint8_t ADDRESS_E_C[20];
uint8_t ADDRESS_V[20];
uint8_t PUBLIC_KEY_E[64];
uint8_t NONCE_E[32];
uint8_t SIGNATURE_E[65];
uint8_t nonceDevice[32];
uint8_t signedDevice[65];
uint8_t PK_XD[64];
uint8_t K_XD[32];
uint8_t hash_K_XD[32];

uint8_t HDState; //This variable is needed to know if the device is registered. If the value is 0, then the helper data are saved

//This defines the string for the token address. It is needed to define correctly the string
string tokenDevice = "0x0000000000000000000000000000000000000000";

void setup() {
  //Initialization of the entropy context of the CTR-DRBG
  mbedtls_entropy_context entropy_context;    
  mbedtls_entropy_init( &entropy_context);    
  mbedtls_ctr_drbg_context ctr_drbg_context;  

  //Check if the device is already registered or a new Ethereum account must be generated
  EEPROM.begin(EEPROM_SIZE);
  HDState = EEPROM.read(0);
  printf("\n%d\n", HDState);
  
  if(HDState){
    //The device is no registered, then a new blockchain account is generated
    esp_fill_random(helperData,51); //esp_fill_random is a "TRNG" to generate all the helper data

    //Setup the startup values of the CT-DRBG PRNG
    char key_ctr[17];

    int i = 0;
  
    for (i = 0; i<16;i++){
      key_ctr[i] = helperData[i+35];
    }

    key_ctr[16] = 0;

    char personalization[17];
    for (i= 0; i<16;i++){
      personalization[i] = helperData[i+19];
    }
    personalization[16] = 0;

    mbedtls_ctr_drbg_init( &ctr_drbg_context);
    
    int ret = mbedtls_ctr_drbg_seed( &ctr_drbg_context , mbedtls_entropy_func, &entropy_context,(const unsigned char *) personalization,strlen( personalization ) );
  
    char ctr_drbg_context_counter[17];
    for (i= 0; i<16;i++){
      personalization[i] = helperData[i+19];
    }
    personalization[16] = 0;

    for (i = 0 ; i<16 ;i++){
      ctr_drbg_context.counter[i] = helperData[i+3];
    }
  
    ctr_drbg_context.reseed_counter = helperData[2];
    ctr_drbg_context.entropy_len = helperData[1];
    ctr_drbg_context.reseed_interval = helperData[0];

    if( ( ret = mbedtls_aes_setkey_enc( &ctr_drbg_context.aes_ctx, (const unsigned char*)&key_ctr, 256 ) ) != 0 ){
      printf("%d\n",ret);
    }
    
    //Generate the Private Key using CTR-DRBG
    ret = mbedtls_ctr_drbg_random( &ctr_drbg_context, privateKey_ethAccount, (size_t)32 );
    if (ret){
      ESP.restart(); //If ret = 0, the data generated is invalid
    }

    //Obtain the address associated to the Private Key
    crypto.PrivateKeyToPublic(privateKey_ethAccount, publicKey_ethAccount);
    crypto.PublicKeyToAddress(publicKey_ethAccount,ethAddress);

    //Share the address. Other information can be shared only at the development process
    printf("\nAddress = 0x");
    for(int i = 0 ; i < 20 ; i++){
      printf("%02x",ethAddress[i]);
    }
    printf("\n");
    //End of setup function at the registration process
  }else{
    //The device is registered
    //Helper data are recovered from EEPROM and set up the startup values of CTR-DRBG PRNG
    for(int i = 0 ; i < 51 ; i++){
      helperData[i] = EEPROM.read(i+1);
    }

    //Setup the startup values of CT-DRBG PRNG
    char key_ctr[17]; 

    int i = 0;
  
    for (i = 0; i<16;i++){
      key_ctr[i] = helperData[i+35];
    }

    key_ctr[16] = 0;

    char personalization[17];
    for (i= 0; i<16;i++){
      personalization[i] = helperData[i+19];
    }
    personalization[16] = 0;

    mbedtls_ctr_drbg_init( &ctr_drbg_context);

    int ret = mbedtls_ctr_drbg_seed( &ctr_drbg_context , mbedtls_entropy_func, &entropy_context,(const unsigned char *) personalization,strlen( personalization ) );
  
    char ctr_drbg_context_counter[17];
    for (i= 0; i<16;i++){
      personalization[i] = helperData[i+19];
    }
    personalization[16] = 0;

    for (i = 0 ; i<16 ;i++){
      ctr_drbg_context.counter[i] = helperData[i+3];
    }
  
    ctr_drbg_context.reseed_counter = helperData[2];
    ctr_drbg_context.entropy_len = helperData[1];
    ctr_drbg_context.reseed_interval = helperData[0];

    if( ( ret = mbedtls_aes_setkey_enc( &ctr_drbg_context.aes_ctx, (const unsigned char*)&key_ctr, 256 ) ) != 0 ){
      printf("%d\n",ret);
    }
    
    //The private Key is generated. The ret value is not needed to be checked
    ret = mbedtls_ctr_drbg_random( &ctr_drbg_context, privateKey_ethAccount, (size_t)32 );
    
    //The address associated to the private Key is generated
    crypto.PrivateKeyToPublic(privateKey_ethAccount, publicKey_ethAccount);
    crypto.PublicKeyToAddress(publicKey_ethAccount,ethAddress);

    //The address obtained is shared
    printf("Address = 0x");
    for(int i = 0 ; i < 20 ; i++){
      printf("%02x",ethAddress[i]);
    }
    printf("\n");

    //Setup wifi connection 
    setupWifi();

    //Obtain the state of the SmartNFT and the information about the SmartNFT
    tokenState = queryTokenStatus();
    //Setup of values needed on each execution state
    switch (tokenState)
    {
    case waitingForOwner:
      //The owner address must be saved to be verified
      printf("Waiting for owner...\n Address to verify = 0x");
      for(int i = 0 ; i < 20 ; i++){
        ADDRESS_V[i] = HexTo4Bits(OwnerAddress[2*i+2])*16+HexTo4Bits(OwnerAddress[2*i+3]);
        printf("%02x",ADDRESS_V[i]);
      }
      printf("\n");
      break;
    case engagedWithOwner:
      printf("Engaged with owner!\n");
      break;
    case waitingForUser:
      //The user address must be saved to be verified
      printf("Waiting for user...\n Address to verify = 0x");
      for(int i = 0 ; i < 20 ; i++){
        ADDRESS_V[i] = HexTo4Bits(UserAddress[2*i+2])*16+HexTo4Bits(UserAddress[2*i+3]);
        printf("%02x",ADDRESS_V[i]);
      }
      printf("\n");
      break;
    case engagedWithUser:
      printf("Engaged with user\n");
      break;
    default:
      break;
    }
  }
  //This message is the same whenever the device is registered or not
  printf("Ready\n");
}

void loop() {
  //This function is executed in a loop 
  //The device checks if the SmartNFT is registered
  if(HDState){
    //If the SmartNFT is not registered then the device waits for the manufacturer to register it 
    uint8_t value = 0;
    scanf("%c",&value);
    if(value == '1'){
      //If character '1' is received, then the device shares its Ethereum address
      printf("{\n");
      printf("\"BCA_Address\" : \"0x");
      for(int i = 0 ; i < 20 ; i++){
        printf("%02x",ethAddress[i]);
      }
      printf("\"\n}\n");
    }
    if(value == '0'){
      //This condition is to check the device status
      printf("ready\n");
    }
    if(value == 'S'){
      //If 'S' is received, the device saves the helper data and changes the EEPROM to be registered
      //and the stated is saved
      EEPROM.write(0,0);
      EEPROM.commit();
      //Save the helper data
      for(int i = 0; i<51; i++){
        EEPROM.write(i+1, helperData[i]);
        EEPROM.commit();
      }
      //After that, the device restarts to change its state
      printf("\nRegistered");
      ESP.restart();

      //This is the end of the loop function if the device is no registered.
    }
  }else{
    //The device is registered 
    setupWifi(); //The Wifi connection is checked
    //The device is waiting for a message from the owner or the user
    uint8_t value;
    scanf("%c",&value);
    if(value == 'Z'){
      //If 'Z' is received, the device starts the verification process
      value = 0;
      while(value == 0){
          scanf("%c",&value);
      }        
      if(value == '0'){
        value = 0;
        while(value == 0){
          scanf("%c",&value);
        }if(value == 'x'){
          for(int i = 0; i <20;i++){
            value = 0;
            while(value == 0){
               scanf("%c",&value);
            }
            ADDRESS_E[i] = HexTo4Bits(value)*16;
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            ADDRESS_E[i] = ADDRESS_E[i] + HexTo4Bits(value);
          }
        } 
        //The device checks if the address received coincides
        bool sameAddress = true;
        for(int i = 0 ; i < 20 ; i++){
          if(ADDRESS_E[i] != ADDRESS_V[i]){
            sameAddress = false;
          }
        }
        if(sameAddress){
          //If the address received coincides, this process continues
          printf("{ \"nonceDevice\" : \"0x");
          for(int i = 0 ; i < 32 ; i++){
            nonceDevice[i]=esp_random();
            printf("%02x",nonceDevice[i]);
          }
          printf("\",");
          printf(" \"type\" : 0}\n");
        }
      }
    }if(value == 'Y'){
      //The new nonce and the signed old nonce are received
      value = 0;
      while(value == 0){
        scanf("%c",&value);
      }
      if(value == '0'){
        value = 0;
        while(value == 0){
          scanf("%c",&value);
        }
        if(value == 'x'){
          for(int i = 0; i <65;i++){
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            SIGNATURE_E[i] = HexTo4Bits(value)*16;
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            SIGNATURE_E[i] = SIGNATURE_E[i] + HexTo4Bits(value);
          }
          
          value = 0;
          while(value == 0){
            scanf("%c",&value);
          }
          if(value == '#'){
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            if(value == '0'){
              value = 0;
              while(value == 0){
                scanf("%c",&value);
              }
              if(value == 'x'){
                for(int i = 0; i <32;i++){
                  value = 0;
                  while(value == 0){
                    scanf("%c",&value);
                  }
                  NONCE_E[i] = HexTo4Bits(value)*16;
                  value = 0;
                  while(value == 0){
                    scanf("%c",&value);
                  }
                  NONCE_E[i] = NONCE_E[i] + HexTo4Bits(value);
                }
              }
            }
          }
          //The signature is checked with the nonce generated by the device
          crypto.ECRecover(SIGNATURE_E,PUBLIC_KEY_E,nonceDevice);
          crypto.PublicKeyToAddress(PUBLIC_KEY_E,ADDRESS_E_C);
          //The address obtained from the signature is checked
          bool sameAddressSig = true;
          for(int i = 0 ; i < 20 ; i++){
            if(ADDRESS_E[i] != ADDRESS_E_C[i]){
              sameAddressSig = false;
            }
          }
          if(crypto.Verify(PUBLIC_KEY_E,nonceDevice,SIGNATURE_E) && sameAddressSig){
            //The signature is verified
            const ecdsa_curve *curve = &secp256k1;
            uint8_t pby;
            //The device signs the nonce received and shares a JSON with this signature
            ecdsa_sign_digest(curve, privateKey_ethAccount, NONCE_E, signedDevice, &pby, NULL);
            printf("{ \"signature\" : \"0x");
            for(int i = 0 ; i < 64 ; i++){
              printf("%02x",signedDevice[i]);
            }
            printf("\", ");
            printf(" \"recNum\" : %d , \"type\" : 1}\n",pby);
          }else{
            //Error
            printf("ERROR");
          }
        }
      }
    }if (value == 'K'){
      //The public Key is received to generate a shared key
	  //The public key allows checking if the message is from the owner or from the user
      value = 0;
      while(value == 0){
        scanf("%c",&value);
      }
      if(value == '0'){
        value = 0;
        while(value == 0){
          scanf("%c",&value);
        }
        if(value == 'x'){
          for(int i = 0; i <65;i++){
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            SIGNATURE_E[i] = HexTo4Bits(value)*16;
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            SIGNATURE_E[i] = SIGNATURE_E[i] + HexTo4Bits(value);
          }
          
          value = 0;
          while(value == 0){
            scanf("%c",&value);
          }
          if(value == '#'){
            value = 0;
            while(value == 0){
              scanf("%c",&value);
            }
            if(value == '0'){
              value = 0;
              while(value == 0){
                scanf("%c",&value);
              }
              if(value == 'x'){
                for(int i = 0; i <64;i++){
                  value = 0;
                  while(value == 0){
                    scanf("%c",&value);
                  }
                  PK_XD[i] = HexTo4Bits(value)*16;
                  value = 0;
                  while(value == 0){
                    scanf("%c",&value);
                  }
                  PK_XD[i] = PK_XD[i] + HexTo4Bits(value);
                }
              }
            }
          }
          //Check signature
          if(crypto.Verify(PUBLIC_KEY_E,PK_XD,SIGNATURE_E)){
            //Verified
            const ecdsa_curve *curve = &secp256k1;
            uint8_t pby;
            ecdsa_sign_digest(curve, privateKey_ethAccount, NONCE_E, signedDevice, &pby, NULL);
            ecdh_multiply(curve, privateKey_ethAccount, PK_XD, K_XD);
            sendEngage();
            //Generate a JSON
            printf("{ \"Shared Key\" : \"0x");
            for(int i = 0 ; i < 32 ; i++){
              printf("%02x",K_XD[i]);
            }
            printf("\", ");
            printf(" \"recNum\" : %d , \"type\" : 3}\n",pby);

          }else{
            //Error
            printf("ERROR");
          }
        }
      }
    }if(value == 'C'){
    <  //Clear EEPROM. This command is only for debug process
      EEPROM.write(0,0xFF);
      EEPROM.commit();
      printf("RESTARTING DEVICE...");
      ESP.restart();
    }if(value == 'R'){
      //Restart device. This command is only for debug process
      printf("RESTARTING DEVICE...");
      ESP.restart();
    }
    //This is the end of the loop function if device is registered
  }
}

void setupWifi(){
  //Same function of ESP32 wifi Example
  if (WiFi.status() == WL_CONNECTED)
    {
        return;
    }

    printf("\n");
    printf("Connecting to ");
    printf(ssid);
    printf("\n");

    if (WiFi.status() != WL_CONNECTED)
    {
        WiFi.persistent(false);
        WiFi.mode(WIFI_OFF);
        WiFi.mode(WIFI_STA);

        WiFi.begin(ssid, password);
    }

    wificounter = 0;
    while (WiFi.status() != WL_CONNECTED && wificounter < 10)
    {
        for (int i = 0; i < 500; i++)
        {
            delay(1);
        }
        printf(".");
        wificounter++;
    }

    if (wificounter >= 10)
    {
        printf("Restarting ...\n");
        ESP.restart(); //Targeting 8266 & ESP32. You may need to replace this

    }

    delay(10);

    printf("\n");
    printf("WiFi connected.\n");
    printf("IP address: %d.%d.%d.%d\n",WiFi.localIP()[0],WiFi.localIP()[1],WiFi.localIP()[2],WiFi.localIP()[3]);
}

STATES queryTokenStatus(){
  Contract contract(&web3, EIP4519CONTRACT);
  //Generate the JSON to check the status  
  deviceAddress[0] = '0';
  deviceAddress[1] = 'x';
  for(int i = 0 ; i < 20 ; i++){
    hex2Char(&ethAddress[i], &deviceAddress[2*i+2], &deviceAddress[2*i+3]);
  }

  String tokenAddress;

  printf("Query Status from token : ");
  for(int i = 0 ; i < 40 ; i++){
    printf("%c",deviceAddress[i+2]);
    tokenDevice[i+2] = deviceAddress[i+2];
  }
  printf("\n");
  

  //Call the function to get SmartNFT information
  String func = "getInfoTokenFromBCA(address)";
  string param = contract.SetupContractData(func.c_str(), &tokenDevice);
  string result = contract.ViewCall(&param);
  
  //When data from blockchain are received, the SmartNFT information is saved
  printf("%s\n",result.c_str());
  int i = 0;
  while(result[i] != 'x'){
    i++;
  }
  i++;
  OwnerAddress[0] = UserAddress[0] = '0';
  OwnerAddress[1] = UserAddress[1] = 'x';
  //Owner address
  for(int j =24;j<64;j++){
    OwnerAddress[j-22] = result[i+j];
  }
  i +=64;
  //User address
  for(int j =24;j<64;j++){
    UserAddress[j-22] = result[i+j];
  }

  printf("\nOwner of token : 0x");
  for(int j = 0 ; j < 40 ; j++){
      printf("%c",OwnerAddress[j+2]);
  }
  printf("\nUser of token : 0x");
  for(int j = 0 ; j < 40 ; j++){
      printf("%c",UserAddress[j+2]);
  }
  i +=64;
  
  //tokenID
  tokenID = HexTo4Bits(result[i+56])*268435456+HexTo4Bits(result[i+57])*16777216+HexTo4Bits(result[i+58])*1048576+HexTo4Bits(result[i+59])*65536+HexTo4Bits(result[i+60])*4096+HexTo4Bits(result[i+61])*256+HexTo4Bits(result[i+62])*16+HexTo4Bits(result[i+63]);
  printf("\nToken ID = %d\n",tokenID);

  //SmartNFT state
  switch ((result[i+63])
  {
  case '1':
    return engagedWithOwner;
    break;
  case '2':
    return waitingForUser;
    break;
  case '3':
    return engagedWithUser;
    break;
  default:
    return waitingForOwner
    break;
  }
}


uint8_t HexTo4Bits(uint8_t Hex){
  if (Hex>='0'&&Hex<='9'){
    return (Hex-'0');
  }else if(Hex>='a'&&Hex<='f'){
    return Hex-'a'+10;
  }else if(Hex>='A'&&Hex<='F'){
    return Hex-'A'+10;
  }
}

void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL){
  uint8_t charIN[2];
  charIN[0] = (inHex[0] / 16);
  charIN[1] = (inHex[0] % 16);
  if (charIN[0] < 10){
    charH[0] = charIN[0] + '0';
  }else{
    charH[0] = charIN[0] + 'a' - 10;
  }
  if (charIN[1] < 10){
    charL[0] = charIN[1] + '0';
  }else{
    charL[0] = charIN[1] + 'a' - 10;
  }
}

void sendEngage(){
  Contract contract(&web3, EIP4519CONTRACT);
  //Define the function to call the blockchain. This function depends on the SmartNFT state
  string func;  
  if(tokenState == waitingForOwner){
    func = "ownerEngagement(uint256)";  
  }else if(tokenState == waitingForUser){
    func = "userEngagement(uint256)";  
  }

  //Generate the hash of the shared key
  keccak_256(K_XD,32,hash_K_XD);

  //Put this hash as a uint256 variable
  uint256_t hash_K_XD_uin256 = 0;  
  for(int i =0 ; i < 32 ; i++){
    hash_K_XD_uin256 += (uint256_t)hash_K_XD[i]<<8*(7-i); 
  }

  //Call the function
  string param = contract.SetupContractData(func.c_str(), &hash_K_XD_uin256);
  string result = contract.Call(&param);

}
