diff --git a/debug.sh b/debug.sh old mode 100644 new mode 100755 index 817a8a0..8ac6958 --- a/debug.sh +++ b/debug.sh @@ -6,7 +6,6 @@ echo "User TFT Config: " cat tft_config.txt tft_config=$(sh ./tft_config_build_flags.sh) arduino-cli compile \ - --library ./libraries/QRCode \ --build-property "build.partitions=min_spiffs" \ --build-property "upload.maximum_size=1966080" \ --build-property "build.extra_flags.esp32=${tft_config}" \ diff --git a/fossa-tests/testprint/testprint.ino b/fossa-tests/testprint/testprint.ino index 193a05f..02ea8e3 100644 --- a/fossa-tests/testprint/testprint.ino +++ b/fossa-tests/testprint/testprint.ino @@ -3,11 +3,11 @@ #include #define RXP 22 // TX of the thermal printer #define TXP 23 // RX of the thermal printer -SoftwareSerial printerSerial(RXP, TXP); -Adafruit_Thermal printer(&printerSerial); +SoftwareSerial SerialPrinter(RXP, TXP); +Adafruit_Thermal printer(&SerialPrinter); void setup() { - printerSerial.begin(9600); + SerialPrinter.begin(9600); printer.begin(); printer.wake(); printer.setDefault(); 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/104_printer.ino b/fossa/104_printer.ino index cba237b..cf4970d 100644 --- a/fossa/104_printer.ino +++ b/fossa/104_printer.ino @@ -1,4 +1,3 @@ -#ifdef PRINTER // define a list of quote Strings that can be used to print on the receipt const char* quotes[13] = { @@ -34,8 +33,8 @@ void printQRcode(String qrData, byte size = 2, bool isMainQR = true) { const byte eccCommand[] = { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, eccLevel }; const byte printCommand[] = { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 }; - printerSerial.write(modelCommand, sizeof(modelCommand)); - printerSerial.write(eccCommand, sizeof(eccCommand)); + SerialPrinter.write(modelCommand, sizeof(modelCommand)); + SerialPrinter.write(eccCommand, sizeof(eccCommand)); int len = qrData.length() + 3; if (len > 255) { @@ -44,9 +43,9 @@ void printQRcode(String qrData, byte size = 2, bool isMainQR = true) { return; } byte dataCommand[] = { 0x1D, 0x28, 0x6B, (byte)len, 0x00, 0x31, 0x50, 0x30 }; - printerSerial.write(dataCommand, sizeof(dataCommand)); - printerSerial.print(qrData); - printerSerial.write(printCommand, sizeof(printCommand)); + SerialPrinter.write(dataCommand, sizeof(dataCommand)); + SerialPrinter.print(qrData); + SerialPrinter.write(printCommand, sizeof(printCommand)); } void printReceipt() { @@ -73,4 +72,3 @@ void printReceipt() { printer.feed(3); printer.sleep(); } -#endif diff --git a/fossa/105_display.ino b/fossa/105_display.ino index ad0f7cc..e6966e4 100644 --- a/fossa/105_display.ino +++ b/fossa/105_display.ino @@ -12,6 +12,7 @@ void printMessage(String text1, String text2, String text3, int ftcolor, int bgc } void feedmefiat() { + tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE); tft.setCursor((480 - textWidth(fossaT, 2)) / 2, 40); tft.setTextSize(2); @@ -22,6 +23,9 @@ void feedmefiat() { } void feedmefiatloop() { + if (homeScreenNumColorCount == homeScreenNumColors) { + homeScreenNumColorCount = 0; + } tft.setTextColor(homeScreenColors[homeScreenNumColorCount]); tft.setTextSize(8); tft.setCursor((480 - textWidth(satsT, 8)) / 2, 80); @@ -30,11 +34,12 @@ void feedmefiatloop() { tft.println(forT); tft.setCursor((480 - textWidth(fiatT, 8)) / 2, 200); tft.println(fiatT); + homeScreenNumColorCount++; delay(100); } -void qrShowCodeLNURL(String message) { - #ifdef PRINTER +void qrShowCodeLNURL() { + #ifdef RECEIPT_PRINTER printMessage(printingT, waitT, "", TFT_WHITE, TFT_BLACK); delay(3000); printReceipt(); @@ -59,7 +64,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 5abaa43..bc58771 100644 --- a/fossa/fossa.ino +++ b/fossa/fossa.ino @@ -3,23 +3,37 @@ #include #include #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 PRINTER +// Comment out to disable #define BILL_ACCEPTOR #define COIN_ACCEPTOR +#define RECEIPT_PRINTER + +#define BILL_RX 32 // RX Bill acceptor +#define BILL_TX 33 // TX Bill acceptor + + +#define COIN_TX 34 // TX Coinmech +#define COIN_INHIBIT 35 // Coinmech interrupt -#define BTN1 39 // Screen tap button + +#define PRINTER_RX 22 // RX of the thermal printer +#define PRINTER_TX 23 // TX of the thermal printer + +#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 @@ -28,14 +42,17 @@ #define PRINTER_TX 23 // TX of the thermal printer // uncomment to use always hardcoded default settings -#define HARDCODED +// #define HARDCODED // default settings #define LANGUAGE "en" // Supports en, es, fr, de, it, pt, pl, hu, tr, ro, fi, sv #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 +63,7 @@ String language; String deviceString; String baseURLATM; +String baseUrlAtmPage; String secretATM; String currencyATM; @@ -80,7 +98,7 @@ HardwareSerial SerialBillAcceptor(1); #ifdef COIN_ACCEPTOR HardwareSerial SerialCoinAcceptor(2); #endif -#ifdef PRINTER +#ifdef RECEIPT_PRINTER SoftwareSerial SerialPrinter(PRINTER_RX, PRINTER_TX); Adafruit_Thermal printer(&SerialPrinter); #endif @@ -88,8 +106,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)); @@ -120,24 +140,32 @@ void setup() { 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 +#ifdef RECEIPT_PRINTER SerialPrinter.begin(9600); printer.begin(); #endif @@ -145,25 +173,31 @@ 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(); // clear accidental taps moneyTimerFun(); - Serial.println(total); - Serial.println(maxBeforeResetTally); - maxBeforeResetTally += (total / 100); - Serial.println(maxBeforeResetTally); + Serial.println("Total: " + String(total)); + Serial.println("maxBeforeResetTally: " + String(maxBeforeResetTally)); + maxBeforeResetTally += total / 100; + Serial.println("maxBeforeResetTally: " + String(maxBeforeResetTally)); makeLNURL(); - qrShowCodeLNURL(scanMeT); + qrShowCodeLNURL(); } } // FIXED: only honor tap *after* first money input void moneyTimerFun() { + Serial.println("Waiting for money..."); waitForTap = true; coins = 0; bills = 0; @@ -215,12 +249,11 @@ void moneyTimerFun() { if ((i + 1) == x) { coins += coinAmountFloat[i]; total = coins + bills; - printMessage( - coinAmountFloat[i] + currencyATM, - totalT + String(total) + currencyATM, - tapScreenT, - TFT_WHITE, TFT_BLACK - ); + 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); firstInputDone = true; } } @@ -242,4 +275,12 @@ void moneyTimerFun() { // finalize total (in cents) total = (coins + bills) * 100; -} +#ifdef BILL_ACCEPTOR + // Turn off + SerialBillAcceptor.write(185); +#endif +#ifdef COIN_ACCEPTOR + // Turn off + digitalWrite(COIN_INHIBIT, LOW); +#endif +} \ No newline at end of file