diff --git a/fossa/100_config.ino b/fossa/100_config.ino index e6ed7c0..6fc592c 100644 --- a/fossa/100_config.ino +++ b/fossa/100_config.ino @@ -97,6 +97,8 @@ void setDefaultValues() { Serial.println("Max amount: " + String(maxamount)); maxBeforeReset = MAX_BEFORE_RESET; Serial.println("Max before reset: " + String(maxBeforeReset)); + convertStringToFloatArray(COIN_AMOUNTS, coinAmountFloat); + Serial.println("Coin amounts: " + String(COIN_AMOUNTS)); } void readFiles() { diff --git a/fossa/102_helpers.ino b/fossa/102_helpers.ino index 7fc88aa..9716a23 100644 --- a/fossa/102_helpers.ino +++ b/fossa/102_helpers.ino @@ -36,12 +36,20 @@ void splitSettings(String str) { int firstComma = str.indexOf(','); int secondComma = str.indexOf(',', firstComma + 1); baseURLATM = str.substring(0, firstComma); + Serial.println("baseURLATM: " + baseURLATM); + // remove /api/v1.... and add /atm?lightning= + int apiPos = baseURLATM.indexOf("api"); + baseUrlAtmPage = baseURLATM.substring(0, apiPos); + baseUrlAtmPage += "atm?lightning="; + Serial.println("baseUrlAtmPage: " + baseUrlAtmPage); secretATM = str.substring(firstComma + 1, secondComma); + Serial.println("secretATM: " + secretATM); currencyATM = str.substring(secondComma + 1); + Serial.println("currencyATM: " + currencyATM); } void convertStringToFloatArray(const char* str, float* floatArray) { - char buffer[20]; // Temporary buffer to hold each substring + char buffer[30]; // Temporary buffer to hold each substring int index = 0; // Index for the float array int bufferIndex = 0; // Index for the buffer @@ -55,7 +63,7 @@ void convertStringToFloatArray(const char* str, float* floatArray) { buffer[bufferIndex++] = str[i]; // Copy characters to buffer } } - + // Don't forget to convert the last number in the string buffer[bufferIndex] = '\0'; // Null-terminate the buffer floatArray[index] = atof(buffer); // Convert buffer to float diff --git a/fossa/103_lnurl.ino b/fossa/103_lnurl.ino index dde6ffb..042312b 100644 --- a/fossa/103_lnurl.ino +++ b/fossa/103_lnurl.ino @@ -1,23 +1,84 @@ -//////////////////////////////////////////// -///////////////LNURL STUFF////////////////// -////USING STEPAN SNIGREVS GREAT CRYTPO////// -////////////THANK YOU STEPAN//////////////// -//////////////////////////////////////////// +void encrypt(unsigned char* key, unsigned char* iv, int length, const char* plainText, unsigned char* outputBuffer){ + mbedtls_aes_context aes; + mbedtls_aes_init(&aes); + mbedtls_aes_setkey_enc(&aes, key, 256); // AES-256 requires a 32-byte key + mbedtls_aes_crypt_cbc(&aes, MBEDTLS_AES_ENCRYPT, length, iv, (const unsigned char*)plainText, outputBuffer); + mbedtls_aes_free(&aes); +} + +void deriveKeyAndIV(const char* secret, unsigned char* salt, unsigned char* outputBuffer) { + mbedtls_md5_context md5_ctx; + unsigned char data[24]; // 16 bytes key + 8 bytes salt + unsigned char md5Output[16]; // 16 bytes for MD5 output + + memcpy(data, secret, 16); + memcpy(data + 16, salt, 8); + + // first iteration + mbedtls_md5_init(&md5_ctx); + mbedtls_md5_update(&md5_ctx, data, sizeof(data)); + mbedtls_md5_finish(&md5_ctx, md5Output); + + // Copy the first 16 bytes to the output buffer for the key + memcpy(outputBuffer, md5Output, 16); + + unsigned char data_md5[16 + 16 + 8]; // 16 bytes md5 output + 16 bytes key + 8 bytes salt + + for (int i = 16; i <= 48; i+=16) { + memcpy(data_md5, md5Output, 16); + memcpy(data_md5 + 16, data, 24); + mbedtls_md5_init(&md5_ctx); + mbedtls_md5_update(&md5_ctx, data_md5, sizeof(data_md5)); + mbedtls_md5_finish(&md5_ctx, md5Output); + // Copy the next 16 bytes to the output buffer + memcpy(outputBuffer + i, md5Output, 16); + } + + mbedtls_md5_free(&md5_ctx); +} + + +bool makeLNURL() { + int salt_length = 8; + unsigned char salt[salt_length]; + for (int i = 0; i < salt_length; i++) { + salt[i] = random(0, 255); + } + unsigned char keyIV[32 + 16] = {0}; + deriveKeyAndIV(secretATM.c_str(), salt, keyIV); + + unsigned char key[32] = {0}; + unsigned char iv[16] = {0}; -void makeLNURL() { int randomPin = random(1000, 9999); - byte nonce[8]; - for (int i = 0; i < 8; i++) { - nonce[i] = random(256); + + String payload = String(randomPin) + String(":") + String(total); + size_t payload_len = payload.length(); + int padding = 16 - (payload_len % 16); + payload_len += padding; + for (int i = 0; i < padding; i++) { + payload += String((char)padding); } - byte payload[51]; // 51 bytes is max one can get with xor-encryption - size_t payload_len = xor_encrypt(payload, sizeof(payload), (uint8_t *)secretATM.c_str(), secretATM.length(), nonce, sizeof(nonce), randomPin, float(total)); - String preparedURL = baseURLATM + "?atm=1&p="; - preparedURL += toBase64(payload, payload_len, BASE64_URLSAFE | BASE64_NOPADDING); + unsigned char encrypted[payload_len] = {0}; + encrypt(key, iv, payload_len, payload.c_str(), encrypted); + + const unsigned char *saltedChars = (const unsigned char *)"Salted__"; + unsigned char salted[payload_len + 16]; + memcpy(salted, saltedChars, 8); + memcpy(salted + 8, salt, salt_length); + memcpy(salted + 16, encrypted, payload_len); + + String preparedURL = baseURLATM + "?p="; + preparedURL += toBase64(salted, payload_len+16, BASE64_URLSAFE); Serial.println(preparedURL); + lnurl_encode(preparedURL); + return true; +} + +void lnurl_encode(String preparedURL) { char Buf[200]; preparedURL.toCharArray(Buf, 200); char *url = Buf; @@ -27,54 +88,6 @@ void makeLNURL() { char *charLnurl = (char *)calloc(strlen(url) * 2, sizeof(byte)); bech32_encode(charLnurl, "lnurl", data, len); to_upper(charLnurl); - qrData = baseURLATM.substring(0, baseURLATM.length() - 18) + "atm" + "?lightning=" + charLnurl; + qrData = baseUrlAtmPage + charLnurl; + Serial.println(qrData); } - -int xor_encrypt(uint8_t *output, size_t outlen, uint8_t *key, size_t keylen, uint8_t *nonce, size_t nonce_len, uint64_t pin, uint64_t amount_in_cents) { - // check we have space for all the data: - // - if (outlen < 2 + nonce_len + 1 + lenVarInt(pin) + 1 + lenVarInt(amount_in_cents) + 8) { - return 0; - } - - int cur = 0; - output[cur] = 1; // variant: XOR encryption - cur++; - - // nonce_len | nonce - output[cur] = nonce_len; - cur++; - memcpy(output + cur, nonce, nonce_len); - cur += nonce_len; - - // payload, unxored first - - int payload_len = lenVarInt(pin) + 1 + lenVarInt(amount_in_cents); - output[cur] = (uint8_t)payload_len; - cur++; - uint8_t *payload = output + cur; // pointer to the start of the payload - cur += writeVarInt(pin, output + cur, outlen - cur); // pin code - cur += writeVarInt(amount_in_cents, output + cur, outlen - cur); // amount - cur++; - - // xor it with round key - uint8_t hmacresult[32]; - SHA256 h; - h.beginHMAC(key, keylen); - h.write((uint8_t *)"Round secret:", 13); - h.write(nonce, nonce_len); - h.endHMAC(hmacresult); - for (int i = 0; i < payload_len; i++) { - payload[i] = payload[i] ^ hmacresult[i]; - } - - // add hmac to authenticate - h.beginHMAC(key, keylen); - h.write((uint8_t *)"Data:", 5); - h.write(output, cur); - h.endHMAC(hmacresult); - memcpy(output + cur, hmacresult, 8); - cur += 8; - - // return number of bytes written to the output - return cur; -} \ No newline at end of file diff --git a/fossa/105_display.ino b/fossa/105_display.ino index ad0f7cc..4b9c939 100644 --- a/fossa/105_display.ino +++ b/fossa/105_display.ino @@ -33,7 +33,7 @@ void feedmefiatloop() { delay(100); } -void qrShowCodeLNURL(String message) { +void qrShowCodeLNURL() { #ifdef PRINTER printMessage(printingT, waitT, "", TFT_WHITE, TFT_BLACK); delay(3000); @@ -59,7 +59,7 @@ void qrShowCodeLNURL(String message) { tft.setCursor(40, 290); tft.setTextSize(2); tft.setTextColor(TFT_BLACK, TFT_WHITE); - tft.println(message); + tft.println(scanMeT); delay(2000); waitForTap = true; while (waitForTap) { diff --git a/fossa/fossa.ino b/fossa/fossa.ino index cf44db8..732dd38 100644 --- a/fossa/fossa.ino +++ b/fossa/fossa.ino @@ -4,28 +4,31 @@ #include #include #include -#include #include #include "qrcoded.h" #include "Bitcoin.h" -#include -#include +#include +#include "mbedtls/aes.h" +#include "mbedtls/md5.h" + #define FORMAT_ON_FAIL true #define PARAM_FILE "/elements.json" -#include +#define VERSION "1.0.0" + +//#define BILL_ACCEPTOR +#define BILL_RX 32 // RX Bill acceptor +#define BILL_TX 33 // TX Bill acceptor -//#define PRINTER -#define BILL_ACCEPTOR #define COIN_ACCEPTOR +#define COIN_TX 34 // TX Coinmech +#define COIN_INHIBIT 35 // Coinmech interrupt -#define BTN1 39 // Screen tap button -#define BILL_RX 32 // RX Bill acceptor -#define BILL_TX 33 // TX Bill acceptor -#define COIN_TX 4 // TX Coinmech -#define COIN_INHIBIT 2 // Coinmech -#define PRINTER_RX 22 // RX of the thermal printer -#define PRINTER_TX 23 // TX of the thermal printer +//#define PRINTER +#define PRINTER_RX 22 // RX of the thermal printer +#define PRINTER_TX 23 // TX of the thermal printer + +#define BTN1 21 // Screen tap button // uncomment to use always hardcoded default settings #define HARDCODED @@ -35,7 +38,10 @@ #define CHARGE 10 // % you will charge people for service, set in LNbits extension #define MAX_AMOUNT 30 // max amount per withdraw #define MAX_BEFORE_RESET 300 // max amount you want to sell in the atm before having to reset power -#define DEVICE_STRING "https://XXXX.lnbits.com/fossa/api/v1/lnurl/XXXXX,XXXXXXXXXXXXXXXXXXXXXX,USD" +//#define DEVICE_STRING "https://XXXX.lnbits.com/fossa/api/v1/lnurl/XXXXX,XXXXXXXXXXXXXXXX,EUR" +#define DEVICE_STRING "https://test.b1tco1n.org/fossa/api/v1/lnurl/atm/T7HxgedvRJCZVHTm,9rtVvLAsx22hk9W6,USD" +#define COIN_AMOUNTS "0.05,1.0,0.25,0.5,0.1,2.0" +//#define BILL_AMOUNTS "0.01,0.05,0.1,0.25,0.5,1" int billAmountInt[16]; float coinAmountFloat[6]; @@ -46,6 +52,7 @@ String language; String deviceString; String baseURLATM; +String baseUrlAtmPage; String secretATM; String currencyATM; @@ -86,8 +93,10 @@ Adafruit_Thermal printer(&SerialPrinter); TFT_eSPI tft = TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); Button BTNA(BTN1); + void setup() { Serial.begin(115200); + Serial.println("Welcome to FOSSA, running on version: " + String(VERSION)); Serial.println("TFT: " + String(TFT_WIDTH) + "x" + String(TFT_HEIGHT)); Serial.println("TFT pin MISO: " + String(TFT_MISO)); Serial.println("TFT pin CS: " + String(TFT_CS)); @@ -116,21 +125,30 @@ void setup() { readFiles(); translateAll(language); # endif + + splitSettings(deviceString); + BTNA.begin(); FlashFS.begin(FORMAT_ON_FAIL); + tft.init(); tft.setRotation(1); tft.invertDisplay(false); + + char buf[100]; + sprintf(buf, "TFT (%dx%d) initialized.", TFT_WIDTH, TFT_HEIGHT); + Serial.println(buf); + printMessage("", "Loading..", "", TFT_WHITE, TFT_BLACK); - splitSettings(deviceString); #ifdef BILL_ACCEPTOR - SerialBillAcceptor.begin(300, SERIAL_8N2, BILL_TX, BILL_RX); + SerialBillAcceptor.begin(300, SERIAL_8N2, BILL_RX, BILL_TX); Serial.println("Bill Acceptor serial started."); #endif #ifdef COIN_ACCEPTOR + pinMode(COIN_TX, INPUT); + pinMode(COIN_INHIBIT, INPUT); SerialCoinAcceptor.begin(4800, SERIAL_8N1, COIN_TX); - pinMode(COIN_INHIBIT, OUTPUT); Serial.println("Coin Acceptor serial started."); #endif #ifdef PRINTER @@ -141,24 +159,30 @@ void setup() { void loop() { if (maxBeforeResetTally >= maxBeforeReset) { + Serial.println("Max amount reached, please reset power."); printMessage("", tooMuchFiatT, contactOwnerT, TFT_WHITE, TFT_BLACK); delay(100000000); } else { +#ifdef BILL_ACCEPTOR SerialBillAcceptor.write(184); - digitalWrite(COIN_INHIBIT, HIGH); +#endif +#ifdef COIN_ACCEPTOR + digitalWrite(COIN_INHIBIT, LOW); +#endif tft.fillScreen(TFT_BLACK); BTNA.read(); // needed to clear accidental taps moneyTimerFun(); - Serial.println(total); - Serial.println(maxBeforeResetTally); + Serial.println("Total: " + String(total)); + Serial.println("maxBeforeResetTally: " + String(maxBeforeResetTally)); maxBeforeResetTally = maxBeforeResetTally + (total / 100); - Serial.println(maxBeforeResetTally); + Serial.println("maxBeforeResetTally: " + String(maxBeforeResetTally)); makeLNURL(); - qrShowCodeLNURL(scanMeT); + qrShowCodeLNURL(); } } void moneyTimerFun() { + Serial.println("Waiting for money..."); waitForTap = true; coins = 0; bills = 0; @@ -171,10 +195,10 @@ void moneyTimerFun() { feedmefiat(); feedmefiatloop(); } + #ifdef BILL_ACCEPTOR if (SerialBillAcceptor.available()) { int x = SerialBillAcceptor.read(); - for (int i = 0; i < billAmountSize; i++) { if ((i + 1) == x) { bills = bills + billAmountInt[i]; @@ -183,24 +207,25 @@ void moneyTimerFun() { } } } - // Turn off - SerialBillAcceptor.write(185); #endif + #ifdef COIN_ACCEPTOR if (SerialCoinAcceptor.available()) { int x = SerialCoinAcceptor.read(); - printMessage("", "WARNING: print bool", String(x), TFT_WHITE, TFT_BLACK); - delay(3000); - for (int i = 0; i < coinAmountSize; i++) { - if ((i + 1) == x) { - coins = coins + coinAmountFloat[i]; - total = (coins + bills); - printMessage(coinAmountFloat[i] + currencyATM, totalT + String(total) + currencyATM, tapScreenT, TFT_WHITE, TFT_BLACK); - } + //printMessage("", "WARNING: print bool", String(x), TFT_WHITE, TFT_BLACK); + //delay(3000); + for (int i = 0; i < coinAmountSize; i++) { + if (i + 1 == x) { + coins = coins + coinAmountFloat[i]; + total = (coins + bills); + String printCoin = coinAmountFloat[i] + currencyATM; + Serial.println("Coin inserted: " + String(i) + " -> " + printCoin); + String printTotal = totalT + String(total) + currencyATM; + Serial.println(printTotal); + printMessage(printCoin, printTotal, tapScreenT, TFT_WHITE, TFT_BLACK); } + } } - // Turn off - digitalWrite(COIN_INHIBIT, LOW); #endif BTNA.read(); if (BTNA.wasReleased() || total > maxamount) { @@ -209,4 +234,12 @@ void moneyTimerFun() { homeScreenNumColorCount++; } total = (coins + bills) * 100; +#ifdef BILL_ACCEPTOR + // Turn off + SerialBillAcceptor.write(185); +#endif +#ifdef COIN_ACCEPTOR + // Turn off + digitalWrite(COIN_INHIBIT, LOW); +#endif }