mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-07-11 11:07:04 +10:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user