diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index bcbe3005..ef174827 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1409,6 +1409,26 @@ "repeater_advancedSettingsSubtitle": "Tuning knobs for experienced operators", "repeater_pathHashMode": "Path hash mode", "repeater_pathHashModeHelper": "Bytes used to encode this repeater's ID in flood path/loop-detect tags. 0=1 byte (256 IDs, up to 64 hops), 1=2 bytes (65K IDs, up to 32 hops), 2=3 bytes (16M IDs, up to 21 hops). v1.13 and older firmware drops multi-byte paths — only raise once your network is on v1.14+.", + "repeater_keySettings": "Change Identity Keys", + "repeater_keySettingsSubtitle": "Change the public/private keypair", + "repeater_prvKey": "Private key", + "repeater_prvKeyHelper": "A new private key for the repeater, a 128-character hex string.", + "repeater_generatePrvKey": "Generate a random keypair", + "repeater_stopGeneratingPrvKey": "Interrupt search for keypair", + "repeater_pubKey": "Public key", + "repeater_pubKeyHelper": "This is the public key that goes with the generated private key. You can't set this directly.", + "repeater_pubKeyPrefix": "Desired prefix", + "repeater_pubKeyPrefixHelper": "Find a public key that starts with these hex digits. Expected tries needed: {tries}.", + "@repeater_pubKeyPrefixHelper": { + "placeholders": { + "tries": { + "type": "int" + } + } + }, + + + "repeater_txDelay": "Flood TX delay", "repeater_txDelayHelper": "Retransmit spacing for flood traffic, as a multiplier of the packet's airtime (0-2, default 0.5). Higher = fewer collisions but slower delivery.", "repeater_directTxDelay": "Direct TX delay", @@ -1589,7 +1609,7 @@ "repeater_cliHelpPowersavingOnOff": "Enables or disables powersaving mode (where supported).", "repeater_cliHelpErase": "(Serial only) Formats the device file system. Wipes all settings and contacts.", "repeater_cliHelpSetDutyCycle": "Sets the maximum allowed transmit duty cycle as a percentage (1-100). Internally adjusts the airtime factor.", - "repeater_cliHelpSetPrvKey": "(Serial only) Replaces the device identity private key. Reboot required to apply. Generates a new public key.", + "repeater_cliHelpSetPrvKey": "Replaces the device identity private key. Reboot required to apply. Generates a new public key.", "repeater_cliHelpSetRadioRxGain": "(SX126x only) Toggles boosted RX gain for improved sensitivity at higher current draw.", "repeater_cliHelpSetOwnerInfo": "Sets the owner contact info string included in adverts. Use '|' for newlines.", "repeater_cliHelpSetPathHashMode": "Sets the path-hash mode. 0 = legacy, 1 = standard, 2 = strict. Affects how routing paths are matched.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 08d7a0b6..92bf8c66 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4748,6 +4748,66 @@ abstract class AppLocalizations { /// **'Bytes used to encode this repeater\'s ID in flood path/loop-detect tags. 0=1 byte (256 IDs, up to 64 hops), 1=2 bytes (65K IDs, up to 32 hops), 2=3 bytes (16M IDs, up to 21 hops). v1.13 and older firmware drops multi-byte paths — only raise once your network is on v1.14+.'** String get repeater_pathHashModeHelper; + /// No description provided for @repeater_keySettings. + /// + /// In en, this message translates to: + /// **'Change Identity Keys'** + String get repeater_keySettings; + + /// No description provided for @repeater_keySettingsSubtitle. + /// + /// In en, this message translates to: + /// **'Change the public/private keypair'** + String get repeater_keySettingsSubtitle; + + /// No description provided for @repeater_prvKey. + /// + /// In en, this message translates to: + /// **'Private key'** + String get repeater_prvKey; + + /// No description provided for @repeater_prvKeyHelper. + /// + /// In en, this message translates to: + /// **'A new private key for the repeater, a 128-character hex string.'** + String get repeater_prvKeyHelper; + + /// No description provided for @repeater_generatePrvKey. + /// + /// In en, this message translates to: + /// **'Generate a random keypair'** + String get repeater_generatePrvKey; + + /// No description provided for @repeater_stopGeneratingPrvKey. + /// + /// In en, this message translates to: + /// **'Interrupt search for keypair'** + String get repeater_stopGeneratingPrvKey; + + /// No description provided for @repeater_pubKey. + /// + /// In en, this message translates to: + /// **'Public key'** + String get repeater_pubKey; + + /// No description provided for @repeater_pubKeyHelper. + /// + /// In en, this message translates to: + /// **'This is the public key that goes with the generated private key. You can\'t set this directly.'** + String get repeater_pubKeyHelper; + + /// No description provided for @repeater_pubKeyPrefix. + /// + /// In en, this message translates to: + /// **'Desired prefix'** + String get repeater_pubKeyPrefix; + + /// No description provided for @repeater_pubKeyPrefixHelper. + /// + /// In en, this message translates to: + /// **'Find a public key that starts with these hex digits. Expected tries needed: {tries}.'** + String repeater_pubKeyPrefixHelper(int tries); + /// No description provided for @repeater_txDelay. /// /// In en, this message translates to: @@ -5519,7 +5579,7 @@ abstract class AppLocalizations { /// No description provided for @repeater_cliHelpSetPrvKey. /// /// In en, this message translates to: - /// **'(Serial only) Replaces the device identity private key. Reboot required to apply. Generates a new public key.'** + /// **'Replaces the device identity private key. Reboot required to apply. Generates a new public key.'** String get repeater_cliHelpSetPrvKey; /// No description provided for @repeater_cliHelpSetRadioRxGain. diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 1404277c..e678b89f 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -2641,6 +2641,41 @@ class AppLocalizationsBg extends AppLocalizations { @override String get repeater_txDelay => 'Забавяне на Flood TX'; + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + + @override @override String get repeater_txDelayHelper => diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 9e061241..c897d64e 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2635,6 +2635,41 @@ class AppLocalizationsDe extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bytes, die zur Kodierung der ID dieses Repeaters in Flood-Pfad-/Schleifen-Erkennung-Tags verwendet werden. 0 = 1 Byte (256 IDs, bis zu 64 Hops), 1 = 2 Bytes (65.000 IDs, bis zu 32 Hops), 2 = 3 Bytes (16 Millionen IDs, bis zu 21 Hops). Firmware-Versionen 1.13 und älter verwenden mehrstellige Pfade – ab Version 1.14+ wird nur ein Pfad erstellt, sobald das Netzwerk aktiv ist.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Flood-TX-Verzögerung'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 08f45442..db221c95 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2589,6 +2589,41 @@ class AppLocalizationsEn extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bytes used to encode this repeater\'s ID in flood path/loop-detect tags. 0=1 byte (256 IDs, up to 64 hops), 1=2 bytes (65K IDs, up to 32 hops), 2=3 bytes (16M IDs, up to 21 hops). v1.13 and older firmware drops multi-byte paths — only raise once your network is on v1.14+.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Flood TX delay'; @@ -3059,7 +3094,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get repeater_cliHelpSetPrvKey => - '(Serial only) Replaces the device identity private key. Reboot required to apply. Generates a new public key.'; + 'Replaces the device identity private key. Reboot required to apply. Generates a new public key.'; @override String get repeater_cliHelpSetRadioRxGain => diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index da9decb1..fd7851aa 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2634,6 +2634,41 @@ class AppLocalizationsEs extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bytes utilizados para codificar el ID de este repetidor en las etiquetas de ruta/detección de bucles. 0=1 byte (256 IDs, hasta 64 saltos), 1=2 bytes (65.000 IDs, hasta 32 saltos), 2=3 bytes (16 millones de IDs, hasta 21 saltos). Las versiones 1.13 y anteriores de firmware eliminan rutas de múltiples bytes; solo se detectan una vez que la red está activa en la versión 1.14 o posterior.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Retraso TX por inundación'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 2f9205cf..3094ecdd 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2645,6 +2645,41 @@ class AppLocalizationsFr extends AppLocalizations { String get repeater_pathHashModeHelper => 'Octets utilisés pour encoder l\'ID de ce routeur dans les balises de détection de flux/boucles. 0 = 1 octet (256 ID, jusqu\'à 64 sauts), 1 = 2 octets (65 000 ID, jusqu\'à 32 sauts), 2 = 3 octets (16 millions d\'ID, jusqu\'à 21 sauts). Les versions 1.13 et antérieures utilisent des chemins multi-octets ; à partir de la version 1.14, cela n\'est plus nécessaire.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Délai de transmission en flood'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index d64268ed..20681a99 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -2628,6 +2628,41 @@ class AppLocalizationsHu extends AppLocalizations { String get repeater_pathHashModeHelper => 'Az átjátszó azonosítójának elárasztási útvonal/hurokészlelési címkékbe való kódolására használt bájtok. 0 = 1 bájt (256 azonosító, legfeljebb 64 ugrás), 1 = 2 bájt (65 000 azonosító, legfeljebb 32 ugrás), 2 = 3 bájt (16 millió azonosító, legfeljebb 21 ugrás). A v1.13 és régebbi firmware eldobja a többbájtos elérési utat – csak akkor emelje meg, ha a hálózat a v1.14+ verziót használja.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Áradási TX késleltetés'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 350e7b78..5bf4aae5 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -2636,6 +2636,41 @@ class AppLocalizationsIt extends AppLocalizations { String get repeater_pathHashModeHelper => 'Byte utilizzati per codificare l\'ID di questo ripetitore nei tag per il rilevamento del percorso/loop. 0=1 byte (256 ID, fino a 64 salti), 1=2 byte (65.000 ID, fino a 32 salti), 2=3 byte (16 milioni di ID, fino a 21 salti). Le versioni 1.13 e precedenti utilizzano percorsi multi-byte: è necessario attivare la rete prima di utilizzare questa funzionalità (a partire dalla versione 1.14).'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Ritardo a Flood, TX'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 53851ba3..4a576363 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -2519,6 +2519,41 @@ class AppLocalizationsJa extends AppLocalizations { String get repeater_pathHashModeHelper => 'このリピータのIDをフローパス/ループ検出タグにエンコードするために使用されるバイト数。 0=1バイト (256個のID、最大64ホップ)、1=2バイト (65,000個のID、最大32ホップ)、2=3バイト (160万個のID、最大21ホップ)。 v1.13およびそれ以前のファームウェアでは、マルチバイトパスがサポートされていません。 v1.14以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'フロイド・TXでの遅延'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 3ddff59a..e20e048b 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -2524,6 +2524,41 @@ class AppLocalizationsKo extends AppLocalizations { String get repeater_pathHashModeHelper => '이 리피터의 ID를 플러드 경로/루프 감지 태그에 인코딩하는 데 사용되는 바이트 수: 0=1 바이트 (256개의 ID, 최대 64개의 홉), 1=2 바이트 (65,000개의 ID, 최대 32개의 홉), 2=3 바이트 (16백만 개의 ID, 최대 21개의 홉). v1.13 및 이전 버전의 펌웨어는 다중 바이트 경로를 지원하지 않으며, 네트워크가 v1.14 이상으로 업그레이드되면 한 번만 감지합니다.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => '플러드 TX 지연'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index ade730a2..0e30d205 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2617,6 +2617,41 @@ class AppLocalizationsNl extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bytes die gebruikt worden om de ID van deze repeater te coderen in flood-pad/lusdetectietags. 0=1 byte (256 ID\'s, tot 64 hops), 1=2 bytes (65.000 ID\'s, tot 32 hops), 2=3 bytes (16 miljoen ID\'s, tot 21 hops). Versies 1.13 en ouder gebruiken multi-byte paden – alleen na het activeren van het netwerk.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Vertraging bij Flood TX'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 627b332b..8a80e003 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -2650,6 +2650,41 @@ class AppLocalizationsPl extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bity wykorzystywane do kodowania identyfikatora tego urządzenia w tagach ścieżek/detekcji pętli. 0=1 bity (256 identyfikatorów, do 64 skoków), 1=2 bity (65 000 identyfikatorów, do 32 skoków), 2=3 bity (1 600 000 identyfikatorów, do 21 skoków). Wersje 1.13 i wcześniejsze nie obsługują ścieżek wielobitowych – wykrywają tylko jedną, gdy sieć jest w wersji 1.14 lub nowszej.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Opóźnienie w Flood, TX'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 0c9f67e2..d19939bc 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2635,6 +2635,41 @@ class AppLocalizationsPt extends AppLocalizations { String get repeater_pathHashModeHelper => 'Bytes utilizados para codificar o ID deste repetidor nas tags de caminho/detecção de loop. 0=1 byte (256 IDs, até 64 saltos), 1=2 bytes (65.000 IDs, até 32 saltos), 2=3 bytes (16 milhões de IDs, até 21 saltos). As versões 1.13 e anteriores do firmware não suportam caminhos multi-byte — apenas funcionam uma vez após a ativação da rede (a partir da versão 1.14+).'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Atraso na entrega em Flood, TX'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 1ddc9c79..2d78dd70 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2640,6 +2640,41 @@ class AppLocalizationsRu extends AppLocalizations { String get repeater_pathHashModeHelper => 'Байты, используемые для кодирования идентификатора этого ретранслятора в тегах для обнаружения потоков/циклов. 0 = 1 байт (256 идентификаторов, до 64 переходов), 1 = 2 байта (65 000 идентификаторов, до 32 переходов), 2 = 3 байта (1 600 000 идентификаторов, до 21 перехода). Версии прошивки v1.13 и более ранние версии не поддерживают многобайтовые пути — они поднимаются только после того, как ваша сеть будет обновлена до версии v1.14 и выше.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Задержка в работе системы Flood TX'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index bd16dc0f..4c32ade5 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -2621,6 +2621,41 @@ class AppLocalizationsSk extends AppLocalizations { String get repeater_pathHashModeHelper => 'Byty použité na zakódovanie ID tohto opakovača v tagoch pre trasu/detekciu slučky. 0 = 1 bytu (256 ID, až 64 skokov), 1 = 2 byty (65 000 ID, až 32 skokov), 2 = 3 byty (16 miliónov ID, až 21 skokov). Verzie 1.13 a staršie nepodporujú viacbytové trasy – fungujú len, keď je sieť aktivovaná.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Zpoždanie v Flood, TX'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 064391b7..c248fea9 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -2617,6 +2617,41 @@ class AppLocalizationsSl extends AppLocalizations { String get repeater_pathHashModeHelper => 'Biti, ki so bila uporabljena za kodiranje ID-ja tega releja v oznakah za zaznavanje pot/kroga, imajo naslednje velikosti: 0=1 bit (256 ID-jev, do 64 skokov), 1=2 biti (65.000 ID-jev, do 32 skokov), 2=3 biti (16 milijonov ID-jev, do 21 skokov). V različicah 1.13 in starejših se ustvarjajo večbitne poti – vendar se to zgodi šele, ko je omrežje vklopljeno v različicah 1.14 in kasnejših.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Zatemnitevanje zaradi poplav v Texasu'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 1d1ec527..faee2719 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -2605,6 +2605,41 @@ class AppLocalizationsSv extends AppLocalizations { String get repeater_pathHashModeHelper => 'Byte används för att koda denna repeaters ID i taggar för att upptäcka loopar/flödesvägar. 0=1 byte (256 ID:n, upp till 64 hopp), 1=2 byte (65 000 ID:n, upp till 32 hopp), 2=3 byte (16 miljoner ID:n, upp till 21 hopp). Versioner 1.13 och äldre har stöd för multi-byte-vägar – endast en gång när nätverket är aktiverat (från och med version 1.14).'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Försening i Flood TX'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index ab02382b..88619ead 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -2637,6 +2637,41 @@ class AppLocalizationsUk extends AppLocalizations { String get repeater_pathHashModeHelper => 'Байти, що використовуються для кодування ідентифікатора цього ретранслятора в тегах для виявлення потоків/петлі. 0=1 байт (256 ідентифікаторів, до 64 перехідів), 1=2 байти (65 000 ідентифікаторів, до 32 перехідів), 2=3 байти (16 мільйонів ідентифікаторів, до 21 переходу). Версії 1.13 та старіші не підтримують багатобайтні шляхи — вони активуються лише після того, як мережа буде оновлена до версії 1.14+.'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => 'Затримка у Flood, штат Техас'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6e0704bb..8dffde15 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2494,6 +2494,41 @@ class AppLocalizationsZh extends AppLocalizations { String get repeater_pathHashModeHelper => '用于编码此复用器的 ID 的字节数,在“洪流路径/环检测”标签中使用。 0=1 字节(256 个 ID,最多 64 个跳跃),1=2 字节(65K 个 ID,最多 32 个跳跃),2=3 字节(16M 个 ID,最多 21 个跳跃)。 v1.13 及更早版本的固件会使用多字节路径——只有在您的网络升级到 v1.14 或更高版本后才会生效。'; + @override + String get repeater_keySettings => 'Change Identity Keys'; + + @override + String get repeater_keySettingsSubtitle => + 'Change the public/private keypair'; + + @override + String get repeater_prvKey => 'Private key'; + + @override + String get repeater_prvKeyHelper => + 'A new private key for the repeater, a 128-character hex string.'; + + @override + String get repeater_generatePrvKey => 'Generate a random keypair'; + + @override + String get repeater_stopGeneratingPrvKey => 'Interrupt search for keypair'; + + @override + String get repeater_pubKey => 'Public key'; + + @override + String get repeater_pubKeyHelper => + 'This is the public key that goes with the generated private key. You can\'t set this directly.'; + + @override + String get repeater_pubKeyPrefix => 'Desired prefix'; + + @override + String repeater_pubKeyPrefixHelper(int tries) { + return 'Find a public key that starts with these hex digits. Expected tries needed: $tries.'; + } + @override String get repeater_txDelay => '洪水(德克萨斯州)延误'; diff --git a/lib/models/community.dart b/lib/models/community.dart index 7261ddf9..c0abf7f0 100644 --- a/lib/models/community.dart +++ b/lib/models/community.dart @@ -1,8 +1,8 @@ import 'dart:convert'; -import 'dart:math'; import 'dart:typed_data'; import 'package:crypto/crypto.dart' as crypto; +import 'package:meshcore_open/utils/keys.dart'; import 'channel.dart'; @@ -37,11 +37,7 @@ class Community { /// Generate a new community with a random 32-byte secret factory Community.create({required String id, required String name}) { - final random = Random.secure(); - final secret = Uint8List(32); - for (int i = 0; i < 32; i++) { - secret[i] = random.nextInt(256); - } + final secret = randomBytes(32); return Community( id: id, name: name, @@ -210,12 +206,7 @@ class Community { /// Create a copy of this community with a regenerated random secret Community withRegeneratedSecret() { - final random = Random.secure(); - final newSecret = Uint8List(32); - for (int i = 0; i < 32; i++) { - newSecret[i] = random.nextInt(256); - } - return withNewSecret(newSecret); + return withNewSecret(randomBytes(32)); } /// Extract secret from QR data (for updating existing community) diff --git a/lib/screens/channels_screen.dart b/lib/screens/channels_screen.dart index 2de232c6..e6bd0885 100644 --- a/lib/screens/channels_screen.dart +++ b/lib/screens/channels_screen.dart @@ -1,9 +1,9 @@ import 'dart:async'; -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:meshcore_open/storage/channel_message_store.dart'; +import 'package:meshcore_open/utils/keys.dart'; import 'package:meshcore_open/utils/platform_info.dart'; import 'package:meshcore_open/widgets/app_bar.dart'; import 'package:provider/provider.dart'; @@ -938,11 +938,7 @@ class _ChannelsScreenState extends State ); return; } - final random = Random.secure(); - final psk = Uint8List(16); - for (int i = 0; i < 16; i++) { - psk[i] = random.nextInt(256); - } + final psk = randomBytes(16); Navigator.pop(sheetContext); await connector.setChannel( nextIndex, @@ -1571,11 +1567,7 @@ class _ChannelsScreenState extends State icon: const Icon(Icons.casino), tooltip: sheetContext.l10n.channels_generateRandomPsk, onPressed: () { - final random = Random.secure(); - final bytes = Uint8List(16); - for (int i = 0; i < 16; i++) { - bytes[i] = random.nextInt(256); - } + final bytes = randomBytes(16); pskController.text = Channel.formatPskHex(bytes); }, ), diff --git a/lib/screens/repeater_settings_screen.dart b/lib/screens/repeater_settings_screen.dart index dca0d7f4..fb95d0ea 100644 --- a/lib/screens/repeater_settings_screen.dart +++ b/lib/screens/repeater_settings_screen.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'dart:typed_data'; +import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../l10n/l10n.dart'; import '../models/contact.dart'; @@ -11,6 +12,7 @@ import '../services/storage_service.dart'; import '../theme/mesh_theme.dart'; import '../widgets/mesh_ui.dart'; import '../widgets/routing_sheet.dart'; +import '../utils/keys.dart'; import '../helpers/snack_bar_builder.dart'; class RepeaterSettingsScreen extends StatefulWidget { @@ -44,6 +46,7 @@ enum _SettingField { advertInterval, floodAdvertInterval, pathHashMode, + prvKey, txDelay, directTxDelay, intThresh, @@ -109,6 +112,8 @@ class _RepeaterSettingsScreenState extends State { bool _refreshingIntThresh = false; bool _refreshingAgcResetInterval = false; bool _runningAction = false; + bool _searchingForKeyPair = false; + bool _stopSearchingForKeyPair = false; StreamSubscription? _frameSubscription; RepeaterCommandService? _commandService; @@ -154,6 +159,11 @@ class _RepeaterSettingsScreenState extends State { // Advanced int _pathHashMode = 0; // 0-2 + final TextEditingController _prvKeyController = TextEditingController(); + final TextEditingController _pubKeyController = TextEditingController(); + final TextEditingController _prvKeyPrefixController = TextEditingController(); + final KeyPairSearcher _keyPairSearcher = KeyPairSearcher(); + final int _searchChunkTime = 30; // time in ms to search before yielding final TextEditingController _txDelayController = TextEditingController(); final TextEditingController _directTxDelayController = TextEditingController(); @@ -860,6 +870,12 @@ class _RepeaterSettingsScreenState extends State { command: 'set path.hash.mode $_pathHashMode', )); } + if (_dirtyFields.contains(_SettingField.prvKey)) { + final v = _prvKeyController.text.trim(); + if (v != "") { + pending.add((field: _SettingField.prvKey, command: 'set prv.key $v')); + } + } if (_dirtyFields.contains(_SettingField.txDelay) && _txDelayController.text.isNotEmpty) { final v = double.tryParse(_txDelayController.text.trim()); @@ -1005,6 +1021,36 @@ class _RepeaterSettingsScreenState extends State { } } + void _startSearchingForKeyPair() async { + if (_searchingForKeyPair) return; + setState(() { + _searchingForKeyPair = true; + _stopSearchingForKeyPair = false; + }); + _searchForKeyPair(); + } + + void _searchForKeyPair() async { + if (_stopSearchingForKeyPair) { + setState(() => _searchingForKeyPair = false); + return; + } + final String prefix = _prvKeyPrefixController.text.trim(); + MCKeyPair? keys = await _keyPairSearcher.findMatchingKeyPair( + prefix, + _searchChunkTime, + ); + if (keys == null) { + // Ran out of time; schedule another attempt. + Timer.run(_searchForKeyPair); + } else { + setState(() => _searchingForKeyPair = false); + _prvKeyController.text = pubKeyToHex(keys.private); + _pubKeyController.text = pubKeyToHex(keys.public); + _markChanged(_SettingField.prvKey); + } + } + Widget _buildInlineRefreshButton({ required bool isRefreshing, required VoidCallback onRefresh, @@ -1069,6 +1115,7 @@ class _RepeaterSettingsScreenState extends State { _buildOwnerInfoCard(), _buildActionsCard(), _buildAdvancedCard(), + _buildKeysCard(), const SizedBox(height: 16), _buildDangerZoneCard(), ], @@ -1984,6 +2031,127 @@ class _RepeaterSettingsScreenState extends State { ); } + Widget _buildKeysCard() { + final l10n = context.l10n; + return MeshCard( + child: ExpansionTile( + leading: const Icon(Icons.vpn_key), + title: Text( + l10n.repeater_keySettings, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600), + ), + subtitle: Text(l10n.repeater_keySettingsSubtitle), + childrenPadding: const EdgeInsets.fromLTRB(0, 8, 0, 4), + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + controller: _prvKeyController, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp("[0-9a-fA-F]")), + ], + decoration: InputDecoration( + labelText: l10n.repeater_prvKey, + helperText: l10n.repeater_prvKeyHelper, + helperMaxLines: 3, + border: const OutlineInputBorder(), + ), + onChanged: (_) { + _pubKeyController.text = ""; + if (_prvKeyController.text.isNotEmpty) { + _markChanged(_SettingField.prvKey); + } + }, + ), + ), + const SizedBox(width: 8), + Padding( + padding: const EdgeInsets.only(top: 8), + child: _searchingForKeyPair + ? IconButton( + icon: const Icon(Icons.cancel, size: 24), + onPressed: (() => _stopSearchingForKeyPair = true), + tooltip: l10n.repeater_stopGeneratingPrvKey, + visualDensity: VisualDensity.compact, + ) + : IconButton( + icon: const Icon(Icons.casino_rounded, size: 24), + onPressed: () { + _startSearchingForKeyPair(); + }, + tooltip: l10n.repeater_generatePrvKey, + visualDensity: VisualDensity.compact, + ), + ), + ], + ), + const SizedBox(height: 16), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + readOnly: true, + controller: _pubKeyController, + style: TextStyle( + color: Theme.of( + context, + ).textTheme.headlineSmall?.color?.withValues(alpha: 0.6), + ), + decoration: InputDecoration( + labelText: l10n.repeater_pubKey, + helperText: l10n.repeater_pubKeyHelper, + helperMaxLines: 3, + border: const OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 16), + Padding( + padding: const EdgeInsets.only(top: 12), + child: SizedBox( + width: 24, + height: 24, + child: _searchingForKeyPair + ? CircularProgressIndicator(strokeWidth: 2) + : null, + ), + ), + const SizedBox(width: 8), + ], + ), + const SizedBox(height: 16), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + controller: _prvKeyPrefixController, + maxLength: 4, + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp("[0-9a-fA-F]")), + ], + onChanged: (_) => setState(() => {}), // updates helper text + decoration: InputDecoration( + labelText: l10n.repeater_pubKeyPrefix, + helperText: l10n.repeater_pubKeyPrefixHelper( + pow(16, _prvKeyPrefixController.text.length).toInt(), + ), + helperMaxLines: 3, + border: const OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 48), + ], + ), + ], + ), + ); + } + Widget _buildDangerZoneCard() { final l10n = context.l10n; return MeshCard( diff --git a/lib/utils/keys.dart b/lib/utils/keys.dart new file mode 100644 index 00000000..a89a5d6c --- /dev/null +++ b/lib/utils/keys.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:typed_data'; +import 'package:convert/convert.dart'; +import 'package:cryptography/cryptography.dart'; + +Uint8List randomBytes(int length) { + final random = Random.secure(); + final bytes = Uint8List(32); + for (int i = 0; i < length; i++) { + bytes[i] = random.nextInt(256); + } + return bytes; +} + +class MCKeyPair { + final Uint8List public; + final Uint8List private; + MCKeyPair(this.public, this.private); +} + +Future generateKeyPair() async { + final algo = Ed25519(); + final seed = randomBytes(32); + final SimpleKeyPair keys = await algo.newKeyPairFromSeed(seed); + final Uint8List pubKeyBytes = Uint8List.fromList( + (await keys.extractPublicKey()).bytes, + ); + // Ed25519 gives 32-byte public and private keys, but MeshCore + // uses an expanded 64-byte private key, so we have to make it + // ourselves. Luckily, we can do it such that it has the same public + // key, which we'd otherwise be unable to compute, since although the + // crypto library implements the elliptic curve computations we need, + // it doesn't make them available. + final Uint8List hash = Uint8List.fromList((await Sha512().hash(seed)).bytes); + hash[0] &= 248; // Clamp the scalar. This keeps the chosen point in the + hash[31] &= 63; // large elliptic curve subgroup, and is demanded both by + hash[31] |= 64; // Ed25519 and by the MeshCore repeater key validation code. + return MCKeyPair(pubKeyBytes, hash); +} + +class _KeyHashPrefix { + Uint8List _bytes = Uint8List(0); + Uint8List _mask = Uint8List(0); + _KeyHashPrefix(String prefix) { + final bool lengthIsOdd = (prefix.length % 2 == 1) ? true : false; + final String decodableString = lengthIsOdd ? "${prefix}0" : prefix; + _bytes = hex.decoder.convert(decodableString); + _mask = Uint8List(_bytes.length); + for (var i = 0; i < _mask.length; i++) { + _mask[i] = 0xff; + } + if (lengthIsOdd) { + _mask[_mask.length - 1] = 0xf0; + } + } + + bool matches(List keyBytes) { + if (keyBytes.length < _bytes.length) { + return false; + } + for (var i = 0; i < _bytes.length; i++) { + if (keyBytes[i] & _mask[i] != _bytes[i]) { + return false; + } + } + return true; + } +} + +class KeyPairSearcher { + Future findMatchingKeyPair( + String prefixStr, + int maxMilliseconds, + ) async { + final int now = DateTime.now().millisecondsSinceEpoch; + final int endTime = now + maxMilliseconds; + final _KeyHashPrefix keyHashPrefix = _KeyHashPrefix(prefixStr); + while (DateTime.now().millisecondsSinceEpoch < endTime) { + final MCKeyPair keys = await generateKeyPair(); + if (keyHashPrefix.matches(keys.public)) { + return keys; + } + } + return null; + } +} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 379e36fa..93e46829 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial + jni ) set(PLUGIN_BUNDLED_LIBRARIES) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ffc8c590..3ba5e9d1 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import cryptography_flutter import flutter_blue_plus_darwin import flutter_local_notifications import mobile_scanner @@ -15,6 +16,7 @@ import sqflite_darwin import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + CryptographyFlutterPlugin.register(with: registry.registrar(forPlugin: "CryptographyFlutterPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) diff --git a/pubspec.yaml b/pubspec.yaml index dcefc016..b9864106 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,9 @@ dependencies: ml_dataframe: ^1.0.0 llamadart: ^0.8.0 flutter_langdetect: ^0.0.1 + cryptography: ^2.9.0 + cryptography_flutter: ^2.3.4 + convert: ^3.1.2 hooks: user_defines: diff --git a/untranslated.json b/untranslated.json index f42fd2c3..e63eed61 100644 --- a/untranslated.json +++ b/untranslated.json @@ -15,13 +15,33 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "de": [ "settings_regionFetchRegions", "settings_regionFetchRegionsFail", - "settings_regionFetchRegionsAlreadyExists" + "settings_regionFetchRegionsAlreadyExists", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "es": [ @@ -40,7 +60,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "fr": [ @@ -59,7 +89,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "hu": [ @@ -78,7 +118,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "it": [ @@ -97,7 +147,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "ja": [ @@ -116,7 +176,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "ko": [ @@ -135,7 +205,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "nl": [ @@ -154,7 +234,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "pl": [ @@ -173,7 +263,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "pt": [ @@ -192,7 +292,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "ru": [ @@ -211,7 +321,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "sk": [ @@ -230,7 +350,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "sl": [ @@ -249,7 +379,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "sv": [ @@ -268,7 +408,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "uk": [ @@ -287,7 +437,17 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ], "zh": [ @@ -306,6 +466,16 @@ "channels_regionSetTo", "channels_regionNotSet", "channels_regionSelect_Title", - "channels_clearRegion" + "channels_clearRegion", + "repeater_keySettings", + "repeater_keySettingsSubtitle", + "repeater_prvKey", + "repeater_prvKeyHelper", + "repeater_generatePrvKey", + "repeater_stopGeneratingPrvKey", + "repeater_pubKey", + "repeater_pubKeyHelper", + "repeater_pubKeyPrefix", + "repeater_pubKeyPrefixHelper" ] } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f02857f4..533a1712 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST flserial flutter_local_notifications_windows + jni ) set(PLUGIN_BUNDLED_LIBRARIES)