LCOV - code coverage report
Current view: top level - crypto/utxo/utils - eurocoin_signing.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 11.0 % 82 9
Test Date: 2025-07-02 01:23:33 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:convert';
       2              : import 'dart:typed_data';
       3              : import 'package:bip32/bip32.dart' as bip32;
       4              : import 'package:hex/hex.dart';
       5              : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
       6              : import 'package:walletkit_dart/walletkit_dart.dart';
       7              : import 'package:pointycastle/src/utils.dart' as p_utils;
       8              : import 'package:bip32/src/utils/ecurve.dart' as ecc;
       9              : import 'package:convert/convert.dart' as convert;
      10              : 
      11            0 : List<int> nomoIdDerivationPath(int hostId) {
      12            0 :   return [0, 100, 102, hostId];
      13              : }
      14              : 
      15            0 : String ec8Recover({required String message, required String sig}) {
      16            0 :   final messageHash = createEurocoinMessageHash(message);
      17            0 :   final parsedSig = _parseEc8Signature(sig);
      18            0 :   final pubKeyUncompressed = recoverPublicKey(messageHash, parsedSig);
      19              : 
      20            0 :   final uncompressedPrefix = [0x04];
      21              :   final pubKeyCompressed =
      22            0 :       compressPublicKey(Uint8List.fromList(uncompressedPrefix + pubKeyUncompressed));
      23            0 :   final pubKeyHex = convert.hex.encode(pubKeyCompressed);
      24              :   return pubKeyHex;
      25              : }
      26              : 
      27            0 : Signature _parseEc8Signature(String signature) {
      28            0 :   if (signature.startsWith("0x")) {
      29            0 :     throw WKFailure("expected to not begin with 0x");
      30              :   }
      31            0 :   if (signature.length != 130) {
      32            0 :     throw WKFailure("Unexpected signature length");
      33              :   }
      34              : 
      35              :   // Extract the r and s values
      36            0 :   BigInt r = BigInt.parse(signature.substring(2, 66), radix: 16);
      37            0 :   BigInt s = BigInt.parse(signature.substring(66, 130), radix: 16);
      38              : 
      39              :   // Extract the recovery identifier (v) and make it backwards-compat to the following C-code:
      40              :   // int expectedRecId = (((uint8_t *)compactSig)[0] - 27) % 4;
      41            0 :   BigInt v = BigInt.parse(signature.substring(0, 2), radix: 16);
      42            0 :   final expectedRecId = (v - BigInt.from(27)) % BigInt.from(4);
      43            0 :   v = expectedRecId + BigInt.from(27);
      44              : 
      45            0 :   return Signature.fromRSV(r, s, v.toInt());
      46              : }
      47              : 
      48            0 : Uint8List createEurocoinMessageHash(String message) {
      49              :   List<int> messageBytes;
      50            0 :   if (message.startsWith("0x")) {
      51            0 :     messageBytes = HEX.decode(message.substring(2));
      52              :   } else {
      53            0 :     messageBytes = utf8.encode(message);
      54              :   }
      55            0 :   final varIntLength = encodeVarint(messageBytes.length);
      56            0 :   final prefix = utf8.encode('\u0019Eurocoin Signed Message:\n') + varIntLength;
      57            0 :   final hashInput = Uint8List.fromList(prefix + messageBytes);
      58              : 
      59            0 :   return sha256Sha256Hash(hashInput);
      60              : }
      61              : 
      62            0 : Uint8List ec8SignMessage(String message, NodeWithAddress node) {
      63            0 :   final privateKey = node.bip32Node!.privateKey!;
      64              :   // message test vector: https://zeniq.id/safir.com/backend/qrl?n=2f24bc32c0752e835e21&r=/backend/qrll
      65            0 :   final messageHash = createEurocoinMessageHash(message);
      66              :   // messageHash test vector: u8        uint8_t[32]     "\xf9\x9b\xf9~\U00000015j\xf1\xd1K\U0000001a\U00000002[\U00000011\x83\U0000001ae\xeb\nH\xa0zⴞ\xa8k\xc4Tn~\x80\xe3"
      67            0 :   final signature = Signature.createSignature(messageHash, privateKey, hashPayload: false);
      68              : 
      69            0 :   final r = padUint8ListTo32(unsignedIntToBytes(signature.r));
      70            0 :   final s = padUint8ListTo32(unsignedIntToBytes(signature.s));
      71            0 :   final v = unsignedIntToBytes(BigInt.from(signature.v));
      72              : 
      73              :   // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
      74            0 :   return uint8ListFromList(v + r + s);
      75              : }
      76              : 
      77            0 : String _derivationPathToString(List<int> derivationPath) {
      78              :   String path = "m";
      79            0 :   for (final rawInt in derivationPath) {
      80              :     int i = rawInt;
      81              :     bool hardened = false;
      82            0 :     if (rawInt < 0) {
      83            0 :       i += 0x80000000;
      84              :       hardened = true;
      85            0 :     } else if (rawInt >= 0x80000000) {
      86            0 :       i -= 0x80000000;
      87              :       hardened = true;
      88              :     }
      89            0 :     path += "/" + i.toString();
      90              :     if (hardened) {
      91            0 :       path += "'";
      92              :     }
      93              :   }
      94              :   return path;
      95              : }
      96              : 
      97            0 : Future<NodeWithAddress> deriveBIP32Ec8({
      98              :   required List<int> derivationPath,
      99              :   required Uint8List seed,
     100              : }) async {
     101            0 :   final String derivationPathString = _derivationPathToString(derivationPath);
     102              : 
     103            0 :   bip32.BIP32 seedNode = bip32.BIP32.fromSeed(seed);
     104            0 :   final bip32.BIP32 childNode = seedNode.derivePath(derivationPathString);
     105              : 
     106              :   const compressed = false; // needed for address backwards compat
     107            0 :   final publicKey = ecc.pointFromScalar(childNode.privateKey!, compressed)!;
     108            0 :   final address = pubKeyToAddress(
     109              :     publicKey,
     110              :     AddressType.legacy,
     111              :     EurocoinNetwork,
     112              :   );
     113              : 
     114            0 :   return ChangeNode(
     115              :     bip32Node: childNode,
     116              :     address: address,
     117              :     derivationPath: "", //
     118            0 :     addresses: {AddressType.legacy: address},
     119              :     walletPurpose: HDWalletPurpose.BIP44,
     120            0 :     publicKey: childNode.publicKey.toHex,
     121              :   );
     122              : }
     123              : 
     124            9 : Uint8List uint8ListFromList(List<int> data) {
     125            9 :   if (data is Uint8List) return data;
     126              : 
     127            1 :   return Uint8List.fromList(data);
     128              : }
     129              : 
     130            4 : Uint8List padUint8ListTo32(Uint8List data) {
     131           12 :   assert(data.length <= 32);
     132            8 :   if (data.length == 32) return data;
     133              : 
     134            0 :   return Uint8List(32)..setRange(32 - data.length, 32, data);
     135              : }
     136              : 
     137            2 : Uint8List unsignedIntToBytes(BigInt number) {
     138            4 :   assert(!number.isNegative);
     139            2 :   return p_utils.encodeBigIntAsUnsigned(number);
     140              : }
     141              : 
     142            0 : Uint8List encodeVarint(int value) {
     143            0 :   final List<int> result = [];
     144              : 
     145            0 :   if (value < 0xFD) {
     146            0 :     result.add(value);
     147            0 :   } else if (value <= 0xFFFF) {
     148            0 :     result.addAll([0xFD, value & 0xFF, (value >> 8) & 0xFF]);
     149            0 :   } else if (value <= 0xFFFFFFFF) {
     150            0 :     result.addAll(
     151            0 :         [0xFE, value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]);
     152              :   } else {
     153            0 :     result.addAll([
     154              :       0xFF,
     155            0 :       value & 0xFF,
     156            0 :       (value >> 8) & 0xFF,
     157            0 :       (value >> 16) & 0xFF,
     158            0 :       (value >> 24) & 0xFF,
     159            0 :       (value >> 32) & 0xFF,
     160            0 :       (value >> 40) & 0xFF,
     161            0 :       (value >> 48) & 0xFF,
     162            0 :       (value >> 56) & 0xFF,
     163              :     ]);
     164              :   }
     165            0 :   return Uint8List.fromList(result);
     166              : }
        

Generated by: LCOV version 2.0-1