Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github: meshcore-dev
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,4 @@ There are a number of fairly major features in the pipeline, with no particular

- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community.
14 changes: 3 additions & 11 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ Anyone is able to build anything they like on top of MeshCore without paying any
- MeshCore Firmware on GitHub: [https://github.com/meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore)
- MeshCore Companion Web App: [https://app.meshcore.nz](https://app.meshcore.nz)
- MeshCore Map: [https://map.meshcore.io](https://map.meshcore.io)
- Andy Kirby's [MeshCore Intro Video](https://www.youtube.com/watch?v=t1qne8uJBAc)
- Liam Cottle's [MeshCore Technical Presentation](https://www.youtube.com/watch?v=OwmkVkZQTf4)

You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
Expand Down Expand Up @@ -194,7 +193,7 @@ Recently, as of October 2025, many regions have moved to the "narrow" setting, a

After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.

If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know.
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know.



Expand Down Expand Up @@ -402,10 +401,7 @@ Another way to download map tiles is to use this Python script to get the tiles
<https://github.com/fistulareffigy/MTD-Script>

There is also a modified script that adds additional error handling and parallel downloads:
<https://discord.com/channels/826570251612323860/1330643963501351004/1338775811548905572>

UK map tiles are available separately from Andy Kirby on his discord server:
<https://discord.com/channels/826570251612323860/1330643963501351004/1331346597367386224>
<https://github.com/TheBestJohn/MTD-Script>

### 4.8. Q: Where do the map tiles go?
Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card.
Expand Down Expand Up @@ -526,7 +522,7 @@ The third character is the capital letter 'O', not zero `0`
- Firmware repo: https://github.com/meshcore-dev/MeshCore

### 5.8. Q: How can I support MeshCore?
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://meshcore.gg). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.

Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase

Expand Down Expand Up @@ -563,10 +559,6 @@ pio run -e RAK_4631_Repeater
```
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`

Andy also has a video on how to build using VS Code:
*How to build and flash Meshcore repeater firmware | Heltec V3*
<https://www.youtube.com/watch?v=WJvg6dt13hk> *(Link referenced in the Discord post)*

### 5.10. Q: Are there other MeshCore related open source projects?

**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript library are open source under MIT license.
Expand Down
36 changes: 26 additions & 10 deletions src/Identity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ LocalIdentity::LocalIdentity(const char* prv_hex, const char* pub_hex) : Identit
}

LocalIdentity::LocalIdentity(RNG* rng) {
uint8_t seed[SEED_SIZE];
rng->random(seed, SEED_SIZE);
ed25519_create_keypair(pub_key, prv_key, seed);
}
Expand Down Expand Up @@ -95,12 +94,17 @@ bool LocalIdentity::validatePrivateKey(const uint8_t prv[64]) {
bool LocalIdentity::readFrom(Stream& s) {
bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
memset(seed, 0, SEED_SIZE);
if (success) {
s.readBytes(seed, SEED_SIZE);
}
return success;
}

bool LocalIdentity::writeTo(Stream& s) const {
bool success = (s.write(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
success = success && (s.write(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
success = success && (s.write(seed, SEED_SIZE) == SEED_SIZE);
return success;
}

Expand All @@ -109,26 +113,38 @@ void LocalIdentity::printTo(Stream& s) const {
s.print("prv_key: "); Utils::printHex(s, prv_key, PRV_KEY_SIZE); s.println();
}

size_t LocalIdentity::writeTo(uint8_t* dest, size_t max_len) {
size_t LocalIdentity::writePubkeyTo(uint8_t* dest, size_t max_len) {
if (max_len < PUB_KEY_SIZE) return 0; // not big enough
memcpy(dest, pub_key, PUB_KEY_SIZE);
return PUB_KEY_SIZE;
}

size_t LocalIdentity::writePrvkeyTo(uint8_t* dest, size_t max_len) {
if (max_len < PRV_KEY_SIZE) return 0; // not big enough
memcpy(dest, prv_key, PRV_KEY_SIZE);
return PRV_KEY_SIZE;
}

if (max_len < PRV_KEY_SIZE + PUB_KEY_SIZE) { // only room for prv_key
memcpy(dest, prv_key, PRV_KEY_SIZE);
return PRV_KEY_SIZE;
}
memcpy(dest, prv_key, PRV_KEY_SIZE); // otherwise can fit prv + pub keys
memcpy(&dest[PRV_KEY_SIZE], pub_key, PUB_KEY_SIZE);
return PRV_KEY_SIZE + PUB_KEY_SIZE;
size_t LocalIdentity::writeSeedTo(uint8_t* dest, size_t max_len) {
if (max_len < SEED_SIZE) return 0; // not big enough
memcpy(dest, seed, SEED_SIZE);
return SEED_SIZE;
}

void LocalIdentity::readFrom(const uint8_t* src, size_t len) {
if (len == PRV_KEY_SIZE + PUB_KEY_SIZE) { // has prv + pub keys
memcpy(prv_key, src, PRV_KEY_SIZE);
memcpy(pub_key, &src[PRV_KEY_SIZE], PUB_KEY_SIZE);
memset(seed, 0, SEED_SIZE);
} else if (len == PRV_KEY_SIZE) {
memcpy(prv_key, src, PRV_KEY_SIZE);
// now need to re-calculate the pub_key
ed25519_derive_pub(pub_key, prv_key);
memset(seed, 0, SEED_SIZE);
} else if (len == SEED_SIZE) {
memcpy(seed, src, SEED_SIZE);
// re-generate the keypair from the given seed
ed25519_create_keypair(pub_key, prv_key, seed);
}
}

Expand All @@ -140,4 +156,4 @@ void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_k
ed25519_key_exchange(secret, other_pub_key, prv_key);
}

}
}
15 changes: 14 additions & 1 deletion src/Identity.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Identity {
*/
class LocalIdentity : public Identity {
uint8_t prv_key[PRV_KEY_SIZE];
uint8_t seed[SEED_SIZE];
public:
LocalIdentity();
LocalIdentity(const char* prv_hex, const char* pub_hex);
Expand Down Expand Up @@ -90,7 +91,19 @@ class LocalIdentity : public Identity {
bool readFrom(Stream& s);
bool writeTo(Stream& s) const;
void printTo(Stream& s) const;
size_t writeTo(uint8_t* dest, size_t max_len);
size_t writePubkeyTo(uint8_t* dest, size_t max_len);
size_t writePrvkeyTo(uint8_t* dest, size_t max_len);
size_t writeSeedTo(uint8_t* dest, size_t max_len);
/**
* \brief Set the Ed25519 keypair.
* \param src IN - the source for the key(s) or seed
* \param len IN - length of the input; if equal to SEED_SIZE, src is
* assumed to be a new seed, from which new private and public keys
* will be generated. If equal to PRV_KEY_SIZE, the corresponding
* public key will be re-generated. If equal to PRV_KEY_SIZE+
* PUB_KEY_SIZE, no key regen is needed. The seed can only later
* be obtained via the `get prv.seed` CLI if SEED_SIZE is used.
*/
void readFrom(const uint8_t* src, size_t len);
};

Expand Down
18 changes: 17 additions & 1 deletion src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,17 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
strcpy(reply, "Error, bad key");
}
} else if (memcmp(config, "prv.seed ", 9) == 0) {
uint8_t seed[SEED_SIZE];
bool success = mesh::Utils::fromHex(seed, SEED_SIZE, &config[9]);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there's no length validation for the provided seed, is there any issue with providing a seed that's too short or too long? I guess the remainder would just be all zeros...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utils::fromHex() does a check that strlen(src_hex)==dest_size*2, so it should give an error with a seed that's too short or too long (I'd test but I'm across an ocean from my nearest spare repeater until the weekend.)

if (success) {
mesh::LocalIdentity new_id;
new_id.readFrom(seed, SEED_SIZE);
_callbacks->saveIdentity(new_id);
strcpy(reply, "OK");
} else {
strcpy(reply, "Error, invalid seed");
}
} else if (memcmp(config, "name ", 5) == 0) {
if (isValidName(&config[5])) {
StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
Expand Down Expand Up @@ -758,9 +769,14 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", _prefs->guest_password);
} else if (sender_timestamp == 0 && memcmp(config, "prv.key", 7) == 0) { // from serial command line only
uint8_t prv_key[PRV_KEY_SIZE];
int len = _callbacks->getSelfId().writeTo(prv_key, PRV_KEY_SIZE);
auto len = _callbacks->getSelfId().writePrvkeyTo(prv_key, PRV_KEY_SIZE);
mesh::Utils::toHex(tmp, prv_key, len);
sprintf(reply, "> %s", tmp);
} else if (sender_timestamp == 0 && memcmp(config, "prv.seed", 8) == 0) { // from serial command line only
uint8_t seed[SEED_SIZE];
auto len = _callbacks->getSelfId().writeSeedTo(seed, SEED_SIZE);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for devices that update firmware, but keep their existing identity, I'd assume this will just provide an output of full zeros. Probably fine, as you could check for full zeros to know there's no seed available. But wondering if it should return an error message instead...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming you mean here devices that were seeded before this code: yes, LocalIdentity::readFrom() zeroes the seed and then attempts to read over it if the seed is in the identity store, so if the identity were stored from an older revision it'll stay zeroed and this will output a zero string. I suppose returning an error would be more helpful but we should have something in the docs to say more than the firmware itself can.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liamcottle lmk if you have a preference here. If #1065 gets merged before this I'll add unit tests to prove the above assertion (and this one) and do a zero-check on get prv.seed. I looked at adding interim tests in this PR but with the limited Stream mock available from #925 I'd have to add code code that would create conflicts, so it's easier to do #1065 and then add tests for #1055.

mesh::Utils::toHex(tmp, seed, len);
sprintf(reply, "> %s", tmp);
} else if (memcmp(config, "name", 4) == 0) {
sprintf(reply, "> %s", _prefs->node_name);
} else if (memcmp(config, "repeat", 6) == 0) {
Expand Down
10 changes: 10 additions & 0 deletions variants/lilygo_teth_elite/TETHEliteBoard.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <helpers/ESP32Board.h>

class TETHEliteBoard : public ESP32Board {
public:
const char* getManufacturerName() const override {
return "LilyGO T-ETH Elite";
}
};
99 changes: 99 additions & 0 deletions variants/lilygo_teth_elite/platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
[LilyGo_TETH_Elite_sx1262]
extends = esp32_base
board = esp32s3box
board_build.partitions = default_16MB.csv
board_upload.flash_size = 16MB
build_flags =
${esp32_base.build_flags}
-I variants/lilygo_teth_elite
-D BOARD_HAS_PSRAM
-D LILYGO_TETH_ELITE
-D LILYGO_T_ETH_ELITE_ESP32S3
-D ARDUINO_USB_CDC_ON_BOOT=1
-D P_LORA_DIO_1=8
-D P_LORA_NSS=40
-D P_LORA_RESET=46
-D P_LORA_BUSY=16
-D P_LORA_SCLK=10
-D P_LORA_MISO=9
-D P_LORA_MOSI=11
-D P_LORA_TX_LED=38
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D USE_SX1262
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=8
-D SX126X_RX_BOOSTED_GAIN=1
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/lilygo_teth_elite>
lib_deps =
${esp32_base.lib_deps}

[env:LilyGo_TETH_Elite_sx1262_repeater]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D ADVERT_NAME='"T-ETH Elite Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
${esp32_ota.lib_deps}

[env:LilyGo_TETH_Elite_sx1262_room_server]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D ADVERT_NAME='"T-ETH Elite Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/simple_room_server>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
${esp32_ota.lib_deps}

[env:LilyGo_TETH_Elite_sx1262_companion_radio_usb]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/companion_radio/*.cpp>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
densaugeo/base64 @ ~1.4.0

[env:LilyGo_TETH_Elite_sx1262_companion_radio_ble]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
densaugeo/base64 @ ~1.4.0
43 changes: 43 additions & 0 deletions variants/lilygo_teth_elite/target.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <Arduino.h>
#include "target.h"

TETHEliteBoard board;

static SPIClass spi(HSPI);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
WRAPPER_CLASS radio_driver(radio, board);

ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;

#ifndef LORA_CR
#define LORA_CR 5
#endif

bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);

return radio.std_init(&spi);
}

uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}

void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}

void radio_set_tx_power(int8_t dbm) {
radio.setOutputPower(dbm);
}

mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng);
}
20 changes: 20 additions & 0 deletions variants/lilygo_teth_elite/target.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#include "TETHEliteBoard.h"

extern TETHEliteBoard board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;

bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(int8_t dbm);
mesh::LocalIdentity radio_new_identity();