Skip to content
Snippets Groups Projects
g1_helper.dart 7.31 KiB
Newer Older
vjrj's avatar
vjrj committed
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

vjrj's avatar
vjrj committed
import 'package:crypto/crypto.dart';
vjrj's avatar
vjrj committed
import 'package:durt/durt.dart';
import 'package:encrypt/encrypt.dart' as encrypt;
import 'package:encrypt/encrypt.dart';
vjrj's avatar
vjrj committed
import 'package:fast_base58/fast_base58.dart';
vjrj's avatar
vjrj committed
import 'package:sentry_flutter/sentry_flutter.dart';
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
import '../data/models/contact.dart';
vjrj's avatar
vjrj committed
import '../data/models/payment_state.dart';
vjrj's avatar
vjrj committed
import '../ui/ui_helpers.dart';
vjrj's avatar
vjrj committed
Random createRandom() {
  try {
    return Random.secure();
  } catch (e) {
    return Random();
  }
}

Uint8List generateUintSeed() {
  final Random random = createRandom();
  return Uint8List.fromList(List<int>.generate(32, (_) => random.nextInt(256)));
}

String seedToString(Uint8List seed) {
  final Uint8List seedsBytes = Uint8List.fromList(seed);
  final String encoded = json.encode(seedsBytes.toList());
  return encoded;
}

CesiumWallet generateCesiumWallet(Uint8List seed) {
  return CesiumWallet.fromSeed(seed);
}

Uint8List seedFromString(String sString) {
  final List<dynamic> list = json.decode(sString) as List<dynamic>;
  final Uint8List bytes =
      Uint8List.fromList(list.map((dynamic e) => e as int).toList());
  return bytes;
}

String generateSalt(int length) {
  final Random random = createRandom();
  const String charset =
      '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  return List<String>.generate(
      length, (int index) => charset[random.nextInt(charset.length)]).join();
}

vjrj's avatar
vjrj committed
String? parseHost(String endpointUnParsed) {
vjrj's avatar
vjrj committed
  endpointUnParsed = endpointUnParsed.replaceFirst('GVA S', 'GVA_S');
vjrj's avatar
vjrj committed
  try {
    final List<String> parts = endpointUnParsed.split(' ');
    // FIXME (vjrj): figure out if exists a way to detect http or https
    const String protocol = 'https';
    final String lastPart = parts.removeLast();
    String path =
        RegExp(r'^\/[a-zA-Z0-9\-\/]+$').hasMatch(lastPart) ? lastPart : '';
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
    final String nextToLast = parts[parts.length - 1];
vjrj's avatar
vjrj committed
    /* print(lastPart);
    print(path);
    print(nextToLast); */
    String port = path == ''
vjrj's avatar
vjrj committed
        ? (RegExp(r'^[0-9]+$').hasMatch(lastPart) ? lastPart : '443')
        : RegExp(r'^[0-9]+$').hasMatch(nextToLast)
vjrj's avatar
vjrj committed
            ? nextToLast
            : '443';
    final List<String> hostSplited = parts[1].split('/');
    // Process hosts like monnaie-libre.ortie.org/bma/
    final String host = hostSplited[0];
    path = path.isEmpty
        ? ((hostSplited.length > 1 && hostSplited[1].isNotEmpty
                    ? hostSplited[1]
                    : '')
                .isNotEmpty
            ? hostSplited.length > 1 && hostSplited[1].isNotEmpty
                ? '/${hostSplited[1]}'
                : ''
            : path)
        : path;
vjrj's avatar
vjrj committed
    if (endpointUnParsed.endsWith('gva')) {
      path = '/gva';
    }
    if (port == '443') {
      port = '';
    } else {
      port = ':$port';
    }
    final String endpoint = '$protocol://$host$port$path'.trim();
vjrj's avatar
vjrj committed
    return endpoint;
vjrj's avatar
vjrj committed
  } catch (e, stacktrace) {
    Sentry.captureMessage("Error $e trying to parse '$endpointUnParsed'");
vjrj's avatar
vjrj committed
    Sentry.captureException(e, stackTrace: stacktrace);
vjrj's avatar
vjrj committed
    return null;
  }
vjrj's avatar
vjrj committed
}
vjrj's avatar
vjrj committed

vjrj's avatar
vjrj committed
bool validateKeyOld(String pubKey) {
vjrj's avatar
vjrj committed
  return RegExp(
vjrj's avatar
vjrj committed
          r'^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}$')
vjrj's avatar
vjrj committed
      .hasMatch(pubKey);
}

vjrj's avatar
vjrj committed
bool validateKey(String pubKey) {
  final RegExp regex = RegExp(
    r'^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}(:([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{3}))?$',
  );

  if (!regex.hasMatch(pubKey)) {
    return false;
  }

  final List<String> parts = pubKey.split(':');
  final String publicKeyPart = parts[0];

  if (parts.length == 2) {
    final String checksumPart = parts[1];

    if (pkChecksum(publicKeyPart) != checksumPart) {
      return false;
    }
  }

  return true;
}

String pkChecksum(String pubkey) {
  List<int> signpkInt8;

  // Remove leading '1'
  if (pubkey.length == 44 && pubkey.startsWith('1')) {
    signpkInt8 = Base58Decode(pubkey.substring(1));
  } else {
    signpkInt8 = Base58Decode(pubkey);
  }

  // Double SHA256 hash
  final Digest firstHash = sha256.convert(signpkInt8);
  final Digest secondHash = sha256.convert(firstHash.bytes);

  // Base58 encode and take the first 3 characters
  final String checksum = Base58Encode(secondHash.bytes).substring(0, 3);

  return checksum;
}

vjrj's avatar
vjrj committed
String getQrUri(
    {required String pubKey, String locale = 'en', String amount = '0'}) {
  double amountD;
  try {
    amountD = parseToDoubleLocalized(locale: locale, number: amount);
  } catch (e) {
    amountD = 0;
  }
vjrj's avatar
vjrj committed

  String uri;
vjrj's avatar
vjrj committed
  if (amountD > 0) {
vjrj's avatar
vjrj committed
    // there is something like this in other clients?
    uri = 'june://$pubKey?amount=$amountD';
vjrj's avatar
vjrj committed
  } else {
vjrj's avatar
vjrj committed
    uri = pubKey;
vjrj's avatar
vjrj committed
  }
  return uri;
}

PaymentState? parseScannedUri(String qr) {
  final RegExp regexKeyCommentAmount = RegExp(
      r'(duniter\:key|june\:\/)/(\w+)\?(comment=([^&]+))&amount=([\d.]+)');
  final RegExpMatch? matchKeyCommentAmount =
      regexKeyCommentAmount.firstMatch(qr);

  if (matchKeyCommentAmount != null) {
    final String publicKey = matchKeyCommentAmount.group(2)!;
    final String? comment = matchKeyCommentAmount.group(4);
    final double amount = double.parse(matchKeyCommentAmount.group(5)!);
    return PaymentState(
        contact: Contact(pubKey: publicKey),
        amount: amount,
        comment: comment ?? '');
  }

  final RegExp regexKeyAmount =
      RegExp(r'(duniter\:key|june\:\/)/(\w+)\?amount=([\d.]+)');
vjrj's avatar
vjrj committed
  final RegExpMatch? matchKeyAmount = regexKeyAmount.firstMatch(qr);

  if (matchKeyAmount != null) {
    final String publicKey = matchKeyAmount.group(2)!;
    final double amount = double.parse(matchKeyAmount.group(3)!);
vjrj's avatar
vjrj committed
    return PaymentState(contact: Contact(pubKey: publicKey), amount: amount);
vjrj's avatar
vjrj committed
  }

  // Match no amount
  final RegExp regexKey = RegExp(r'(duniter\:key|june\:\/)/(\w+)');
vjrj's avatar
vjrj committed
  final RegExpMatch? matchKey = regexKey.firstMatch(qr);
  if (matchKey != null) {
    final String publicKey = matchKey.group(2)!;
vjrj's avatar
vjrj committed
    return PaymentState(contact: Contact(pubKey: publicKey));
vjrj's avatar
vjrj committed
  }

  // Match key only
  if (validateKey(qr)) {
vjrj's avatar
vjrj committed
    return PaymentState(contact: Contact(pubKey: qr));
vjrj's avatar
vjrj committed
  }

  return null;
}

final IV _iv = encrypt.IV.fromLength(16);

Map<String, String> encryptJsonForExport(String jsonString, String password) {
  final Uint8List plainText = Uint8List.fromList(utf8.encode(jsonString));
  final encrypt.Encrypted encrypted = encrypt.Encrypter(
          encrypt.AES(encrypt.Key.fromUtf8(password.padRight(32))))
      .encryptBytes(plainText, iv: _iv);
  final Map<String, String> jsonData = <String, String>{
    'key': base64Encode(encrypted.bytes)
  };
  return jsonData;
}

Map<String, dynamic> decryptJsonForImport(
    String keyEncrypted, String password) {
  final String decrypted = encrypt.Encrypter(
          encrypt.AES(encrypt.Key.fromUtf8(password.padRight(32))))
      .decrypt64(keyEncrypted, iv: _iv);
  return jsonDecode(decrypted) as Map<String, dynamic>;
}
vjrj's avatar
vjrj committed

const Duration wrongNodeDuration = Duration(days: 2);
vjrj's avatar
vjrj committed

bool areDatesClose(DateTime date1, DateTime date2, Duration threshold) {
  return date1.difference(date2).abs() <= threshold;
}

double toG1(double amount, bool isG1, double currentUd) {
  return isG1 ? amount : amount * currentUd;
}
vjrj's avatar
vjrj committed

int toCG1(double amount) => (amount.toPrecision(2) * 100).toInt();

// From durt
extension Ex on double {
  double toPrecision(int n) => double.parse(toStringAsFixed(n));
}