Add UI for setting repeater identity keys

This adds a simple UI in repeater management settings for remotely
setting a repeater's private key. A key can be pasted from an external
key generator, or a random one can be generated here, optionally with a
specified prefix.

If a prefix is specified, searching is done in 30ms batches in order
to keep the UI responsive and show the progress indicator animation
smoothly. Searches can be interrupted. An estimation of search length is
given as expected number of generated key pairs. (A future improvement
would be to observe keys per second performance and give time estimates
based on that.)

When it generates a new private key on your behalf, it can tell you
what the corresponding public key will be. Unfortunately, if you paste
one in from another source, it can't compute the corresponding public
key. This is because the standard Dart crypto library doesn't provide
that capability, nor does it give access to the elliptic curve functions
it implements so we could do it ourselves. Our only recourse would be to
copy the source for all those things, and I didn't think it was worth
it.

This will not check the validity of a pasted key, but the remote
repeater will. It does limit it to the correct length and hexadecimal
charset.

Prefixes are limited to 4 characters. There's no technical reason for
this, but users are unlikely to want more, and I think it would just be
a bad experience to allow more and spin for a very long time. If someone
really wants a longer one, they can use an external generator like they
do now.

Possible TODO: rewrite background processing using isolates.

Closes #142.
This commit is contained in:
Seth Golub
2026-05-28 10:57:50 -07:00
parent 5670ab0067
commit e1a4900dc0
29 changed files with 1169 additions and 44 deletions
+21 -1
View File
@@ -1409,6 +1409,26 @@
"repeater_advancedSettingsSubtitle": "Tuning knobs for experienced operators", "repeater_advancedSettingsSubtitle": "Tuning knobs for experienced operators",
"repeater_pathHashMode": "Path hash mode", "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_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_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_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", "repeater_directTxDelay": "Direct TX delay",
@@ -1589,7 +1609,7 @@
"repeater_cliHelpPowersavingOnOff": "Enables or disables powersaving mode (where supported).", "repeater_cliHelpPowersavingOnOff": "Enables or disables powersaving mode (where supported).",
"repeater_cliHelpErase": "(Serial only) Formats the device file system. Wipes all settings and contacts.", "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_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_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_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.", "repeater_cliHelpSetPathHashMode": "Sets the path-hash mode. 0 = legacy, 1 = standard, 2 = strict. Affects how routing paths are matched.",
+61 -1
View File
@@ -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+.'** /// **'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; 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. /// No description provided for @repeater_txDelay.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -5519,7 +5579,7 @@ abstract class AppLocalizations {
/// No description provided for @repeater_cliHelpSetPrvKey. /// No description provided for @repeater_cliHelpSetPrvKey.
/// ///
/// In en, this message translates to: /// 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; String get repeater_cliHelpSetPrvKey;
/// No description provided for @repeater_cliHelpSetRadioRxGain. /// No description provided for @repeater_cliHelpSetRadioRxGain.
+35
View File
@@ -2641,6 +2641,41 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get repeater_txDelay => 'Забавяне на Flood TX'; 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 @override
String get repeater_txDelayHelper => String get repeater_txDelayHelper =>
+35
View File
@@ -2635,6 +2635,41 @@ class AppLocalizationsDe extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Flood-TX-Verzögerung'; String get repeater_txDelay => 'Flood-TX-Verzögerung';
+36 -1
View File
@@ -2589,6 +2589,41 @@ class AppLocalizationsEn extends AppLocalizations {
String get repeater_pathHashModeHelper => 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+.'; '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 @override
String get repeater_txDelay => 'Flood TX delay'; String get repeater_txDelay => 'Flood TX delay';
@@ -3059,7 +3094,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get repeater_cliHelpSetPrvKey => 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 @override
String get repeater_cliHelpSetRadioRxGain => String get repeater_cliHelpSetRadioRxGain =>
+35
View File
@@ -2634,6 +2634,41 @@ class AppLocalizationsEs extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Retraso TX por inundación'; String get repeater_txDelay => 'Retraso TX por inundación';
+35
View File
@@ -2645,6 +2645,41 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Délai de transmission en flood'; String get repeater_txDelay => 'Délai de transmission en flood';
+35
View File
@@ -2628,6 +2628,41 @@ class AppLocalizationsHu extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Áradási TX késleltetés'; String get repeater_txDelay => 'Áradási TX késleltetés';
+35
View File
@@ -2636,6 +2636,41 @@ class AppLocalizationsIt extends AppLocalizations {
String get repeater_pathHashModeHelper => 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).'; '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 @override
String get repeater_txDelay => 'Ritardo a Flood, TX'; String get repeater_txDelay => 'Ritardo a Flood, TX';
+35
View File
@@ -2519,6 +2519,41 @@ class AppLocalizationsJa extends AppLocalizations {
String get repeater_pathHashModeHelper => 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以降のバージョンでは、一度ネットワークが起動されると、パスが一度だけ検出されます。'; 'このリピータの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 @override
String get repeater_txDelay => 'フロイド・TXでの遅延'; String get repeater_txDelay => 'フロイド・TXでの遅延';
+35
View File
@@ -2524,6 +2524,41 @@ class AppLocalizationsKo extends AppLocalizations {
String get repeater_pathHashModeHelper => 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 이상으로 업그레이드되면 한 번만 감지합니다.'; '이 리피터의 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 @override
String get repeater_txDelay => '플러드 TX 지연'; String get repeater_txDelay => '플러드 TX 지연';
+35
View File
@@ -2617,6 +2617,41 @@ class AppLocalizationsNl extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Vertraging bij Flood TX'; String get repeater_txDelay => 'Vertraging bij Flood TX';
+35
View File
@@ -2650,6 +2650,41 @@ class AppLocalizationsPl extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Opóźnienie w Flood, TX'; String get repeater_txDelay => 'Opóźnienie w Flood, TX';
+35
View File
@@ -2635,6 +2635,41 @@ class AppLocalizationsPt extends AppLocalizations {
String get repeater_pathHashModeHelper => 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+).'; '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 @override
String get repeater_txDelay => 'Atraso na entrega em Flood, TX'; String get repeater_txDelay => 'Atraso na entrega em Flood, TX';
+35
View File
@@ -2640,6 +2640,41 @@ class AppLocalizationsRu extends AppLocalizations {
String get repeater_pathHashModeHelper => String get repeater_pathHashModeHelper =>
'Байты, используемые для кодирования идентификатора этого ретранслятора в тегах для обнаружения потоков/циклов. 0 = 1 байт (256 идентификаторов, до 64 переходов), 1 = 2 байта (65 000 идентификаторов, до 32 переходов), 2 = 3 байта (1 600 000 идентификаторов, до 21 перехода). Версии прошивки v1.13 и более ранние версии не поддерживают многобайтовые пути — они поднимаются только после того, как ваша сеть будет обновлена до версии v1.14 и выше.'; 'Байты, используемые для кодирования идентификатора этого ретранслятора в тегах для обнаружения потоков/циклов. 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 @override
String get repeater_txDelay => 'Задержка в работе системы Flood TX'; String get repeater_txDelay => 'Задержка в работе системы Flood TX';
+35
View File
@@ -2621,6 +2621,41 @@ class AppLocalizationsSk extends AppLocalizations {
String get repeater_pathHashModeHelper => 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á.'; '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 @override
String get repeater_txDelay => 'Zpoždanie v Flood, TX'; String get repeater_txDelay => 'Zpoždanie v Flood, TX';
+35
View File
@@ -2617,6 +2617,41 @@ class AppLocalizationsSl extends AppLocalizations {
String get repeater_pathHashModeHelper => 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.'; '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 @override
String get repeater_txDelay => 'Zatemnitevanje zaradi poplav v Texasu'; String get repeater_txDelay => 'Zatemnitevanje zaradi poplav v Texasu';
+35
View File
@@ -2605,6 +2605,41 @@ class AppLocalizationsSv extends AppLocalizations {
String get repeater_pathHashModeHelper => 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).'; '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 @override
String get repeater_txDelay => 'Försening i Flood TX'; String get repeater_txDelay => 'Försening i Flood TX';
+35
View File
@@ -2637,6 +2637,41 @@ class AppLocalizationsUk extends AppLocalizations {
String get repeater_pathHashModeHelper => String get repeater_pathHashModeHelper =>
'Байти, що використовуються для кодування ідентифікатора цього ретранслятора в тегах для виявлення потоків/петлі. 0=1 байт (256 ідентифікаторів, до 64 перехідів), 1=2 байти (65 000 ідентифікаторів, до 32 перехідів), 2=3 байти (16 мільйонів ідентифікаторів, до 21 переходу). Версії 1.13 та старіші не підтримують багатобайтні шляхи — вони активуються лише після того, як мережа буде оновлена до версії 1.14+.'; 'Байти, що використовуються для кодування ідентифікатора цього ретранслятора в тегах для виявлення потоків/петлі. 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 @override
String get repeater_txDelay => 'Затримка у Flood, штат Техас'; String get repeater_txDelay => 'Затримка у Flood, штат Техас';
+35
View File
@@ -2494,6 +2494,41 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_pathHashModeHelper => String get repeater_pathHashModeHelper =>
'用于编码此复用器的 ID 的字节数,在“洪流路径/环检测”标签中使用。 0=1 字节(256 个 ID,最多 64 个跳跃),1=2 字节(65K 个 ID,最多 32 个跳跃),2=3 字节(16M 个 ID,最多 21 个跳跃)。 v1.13 及更早版本的固件会使用多字节路径——只有在您的网络升级到 v1.14 或更高版本后才会生效。'; '用于编码此复用器的 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 @override
String get repeater_txDelay => '洪水(德克萨斯州)延误'; String get repeater_txDelay => '洪水(德克萨斯州)延误';
+3 -12
View File
@@ -1,8 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:meshcore_open/utils/keys.dart';
import 'channel.dart'; import 'channel.dart';
@@ -37,11 +37,7 @@ class Community {
/// Generate a new community with a random 32-byte secret /// Generate a new community with a random 32-byte secret
factory Community.create({required String id, required String name}) { factory Community.create({required String id, required String name}) {
final random = Random.secure(); final secret = randomBytes(32);
final secret = Uint8List(32);
for (int i = 0; i < 32; i++) {
secret[i] = random.nextInt(256);
}
return Community( return Community(
id: id, id: id,
name: name, name: name,
@@ -210,12 +206,7 @@ class Community {
/// Create a copy of this community with a regenerated random secret /// Create a copy of this community with a regenerated random secret
Community withRegeneratedSecret() { Community withRegeneratedSecret() {
final random = Random.secure(); return withNewSecret(randomBytes(32));
final newSecret = Uint8List(32);
for (int i = 0; i < 32; i++) {
newSecret[i] = random.nextInt(256);
}
return withNewSecret(newSecret);
} }
/// Extract secret from QR data (for updating existing community) /// Extract secret from QR data (for updating existing community)
+3 -11
View File
@@ -1,9 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:meshcore_open/storage/channel_message_store.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/utils/platform_info.dart';
import 'package:meshcore_open/widgets/app_bar.dart'; import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -938,11 +938,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
); );
return; return;
} }
final random = Random.secure(); final psk = randomBytes(16);
final psk = Uint8List(16);
for (int i = 0; i < 16; i++) {
psk[i] = random.nextInt(256);
}
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
await connector.setChannel( await connector.setChannel(
nextIndex, nextIndex,
@@ -1571,11 +1567,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
icon: const Icon(Icons.casino), icon: const Icon(Icons.casino),
tooltip: sheetContext.l10n.channels_generateRandomPsk, tooltip: sheetContext.l10n.channels_generateRandomPsk,
onPressed: () { onPressed: () {
final random = Random.secure(); final bytes = randomBytes(16);
final bytes = Uint8List(16);
for (int i = 0; i < 16; i++) {
bytes[i] = random.nextInt(256);
}
pskController.text = Channel.formatPskHex(bytes); pskController.text = Channel.formatPskHex(bytes);
}, },
), ),
+169 -1
View File
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
@@ -11,6 +12,7 @@ import '../services/storage_service.dart';
import '../theme/mesh_theme.dart'; import '../theme/mesh_theme.dart';
import '../widgets/mesh_ui.dart'; import '../widgets/mesh_ui.dart';
import '../widgets/routing_sheet.dart'; import '../widgets/routing_sheet.dart';
import '../utils/keys.dart';
import '../helpers/snack_bar_builder.dart'; import '../helpers/snack_bar_builder.dart';
class RepeaterSettingsScreen extends StatefulWidget { class RepeaterSettingsScreen extends StatefulWidget {
@@ -44,6 +46,7 @@ enum _SettingField {
advertInterval, advertInterval,
floodAdvertInterval, floodAdvertInterval,
pathHashMode, pathHashMode,
prvKey,
txDelay, txDelay,
directTxDelay, directTxDelay,
intThresh, intThresh,
@@ -109,6 +112,8 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
bool _refreshingIntThresh = false; bool _refreshingIntThresh = false;
bool _refreshingAgcResetInterval = false; bool _refreshingAgcResetInterval = false;
bool _runningAction = false; bool _runningAction = false;
bool _searchingForKeyPair = false;
bool _stopSearchingForKeyPair = false;
StreamSubscription<Uint8List>? _frameSubscription; StreamSubscription<Uint8List>? _frameSubscription;
RepeaterCommandService? _commandService; RepeaterCommandService? _commandService;
@@ -154,6 +159,11 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
// Advanced // Advanced
int _pathHashMode = 0; // 0-2 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 _txDelayController = TextEditingController();
final TextEditingController _directTxDelayController = final TextEditingController _directTxDelayController =
TextEditingController(); TextEditingController();
@@ -860,6 +870,12 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
command: 'set path.hash.mode $_pathHashMode', 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) && if (_dirtyFields.contains(_SettingField.txDelay) &&
_txDelayController.text.isNotEmpty) { _txDelayController.text.isNotEmpty) {
final v = double.tryParse(_txDelayController.text.trim()); final v = double.tryParse(_txDelayController.text.trim());
@@ -1005,6 +1021,36 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
} }
} }
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({ Widget _buildInlineRefreshButton({
required bool isRefreshing, required bool isRefreshing,
required VoidCallback onRefresh, required VoidCallback onRefresh,
@@ -1069,6 +1115,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
_buildOwnerInfoCard(), _buildOwnerInfoCard(),
_buildActionsCard(), _buildActionsCard(),
_buildAdvancedCard(), _buildAdvancedCard(),
_buildKeysCard(),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildDangerZoneCard(), _buildDangerZoneCard(),
], ],
@@ -1984,6 +2031,127 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
); );
} }
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() { Widget _buildDangerZoneCard() {
final l10n = context.l10n; final l10n = context.l10n;
return MeshCard( return MeshCard(
+87
View File
@@ -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<MCKeyPair> 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<int> 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<MCKeyPair?> 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;
}
}
+1
View File
@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flserial flserial
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)
@@ -5,6 +5,7 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import cryptography_flutter
import flutter_blue_plus_darwin import flutter_blue_plus_darwin
import flutter_local_notifications import flutter_local_notifications
import mobile_scanner import mobile_scanner
@@ -15,6 +16,7 @@ import sqflite_darwin
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
CryptographyFlutterPlugin.register(with: registry.registrar(forPlugin: "CryptographyFlutterPlugin"))
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
+3
View File
@@ -73,6 +73,9 @@ dependencies:
ml_dataframe: ^1.0.0 ml_dataframe: ^1.0.0
llamadart: ^0.8.0 llamadart: ^0.8.0
flutter_langdetect: ^0.0.1 flutter_langdetect: ^0.0.1
cryptography: ^2.9.0
cryptography_flutter: ^2.3.4
convert: ^3.1.2
hooks: hooks:
user_defines: user_defines:
+187 -17
View File
@@ -15,13 +15,33 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "de": [
"settings_regionFetchRegions", "settings_regionFetchRegions",
"settings_regionFetchRegionsFail", "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": [ "es": [
@@ -40,7 +60,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "fr": [
@@ -59,7 +89,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "hu": [
@@ -78,7 +118,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "it": [
@@ -97,7 +147,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "ja": [
@@ -116,7 +176,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "ko": [
@@ -135,7 +205,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "nl": [
@@ -154,7 +234,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "pl": [
@@ -173,7 +263,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "pt": [
@@ -192,7 +292,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "ru": [
@@ -211,7 +321,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "sk": [
@@ -230,7 +350,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "sl": [
@@ -249,7 +379,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "sv": [
@@ -268,7 +408,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "uk": [
@@ -287,7 +437,17 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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": [ "zh": [
@@ -306,6 +466,16 @@
"channels_regionSetTo", "channels_regionSetTo",
"channels_regionNotSet", "channels_regionNotSet",
"channels_regionSelect_Title", "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"
] ]
} }
+1
View File
@@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flserial flserial
flutter_local_notifications_windows flutter_local_notifications_windows
jni
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)