LCOV - code coverage report
Current view: top level - utils - base58.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 77.3 % 119 92
Test Date: 2025-01-30 01:10:00 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:convert';
       2              : import 'dart:typed_data';
       3              : 
       4              : import 'package:pointycastle/digests/sha256.dart';
       5              : import 'package:walletkit_dart/src/domain/extensions.dart';
       6              : 
       7              : /// Encode and decode bytes to base58 strings.
       8              : ///
       9              : /// An alphabet must be provided.
      10              : ///
      11              : /// In order to comply with Bitcoin and Ripple standard encoding Base58Check,
      12              : /// use [Base58CheckCodec].
      13              : 
      14              : const bitcoinAlphabet =
      15              :     '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
      16              : 
      17              : const rippleAlphabet =
      18              :     'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz';
      19              : 
      20              : const base58BitcoinCodec =
      21              :     Base58Codec(bitcoinAlphabet); // Bitcoin alphabet without checksum
      22              : 
      23              : class Base58Codec extends Codec<List<int>, String> {
      24           37 :   const Base58Codec(this.alphabet);
      25              :   final String alphabet;
      26              : 
      27            8 :   @override
      28           16 :   Converter<List<int>, String> get encoder => Base58Encoder(alphabet);
      29              : 
      30            3 :   @override
      31            6 :   Converter<String, List<int>> get decoder => Base58Decoder(alphabet);
      32              : }
      33              : 
      34              : /// Encode bytes to a base58 string.
      35              : class Base58Encoder extends Converter<List<int>, String> {
      36            9 :   const Base58Encoder(this.alphabet);
      37              :   final String alphabet;
      38              : 
      39            9 :   @override
      40              :   String convert(List<int> input) {
      41            9 :     if (input.isEmpty) return '';
      42              : 
      43              :     // copy bytes because we are going to change it
      44            9 :     input = Uint8List.fromList(input);
      45              : 
      46              :     // count number of leading zeros
      47              :     var leadingZeroes = 0;
      48           36 :     while (leadingZeroes < input.length && input[leadingZeroes] == 0) {
      49            0 :       leadingZeroes++;
      50              :     }
      51              : 
      52              :     var output = '';
      53              :     var startAt = leadingZeroes;
      54           18 :     while (startAt < input.length) {
      55            9 :       var mod = _divmod58(input, startAt);
      56           27 :       if (input[startAt] == 0) startAt++;
      57           27 :       output = alphabet[mod] + output;
      58              :     }
      59              : 
      60            9 :     if (output.isNotEmpty) {
      61           36 :       while (output[0] == alphabet[0]) {
      62            0 :         output = output.substring(1, output.length);
      63              :       }
      64              :     }
      65           18 :     while (leadingZeroes-- > 0) {
      66            0 :       output = alphabet[0] + output;
      67              :     }
      68              : 
      69              :     return output;
      70              :   }
      71              : 
      72              :   /// number -> number / 58
      73              :   /// returns number % 58
      74            9 :   static int _divmod58(List<int> number, int startAt) {
      75              :     var remaining = 0;
      76           27 :     for (var i = startAt; i < number.length; i++) {
      77           36 :       var num = (0xFF & remaining) * 256 + number[i];
      78           18 :       number[i] = num ~/ 58;
      79            9 :       remaining = num % 58;
      80              :     }
      81              :     return remaining;
      82              :   }
      83              : }
      84              : 
      85              : /// Decode base58 strings to bytes.
      86              : class Base58Decoder extends Converter<String, List<int>> {
      87            4 :   const Base58Decoder(this.alphabet);
      88              :   final String alphabet;
      89              : 
      90            4 :   @override
      91              :   List<int> convert(String input) {
      92            4 :     if (input.isEmpty) return Uint8List(0);
      93              : 
      94              :     // generate base 58 index list from input string
      95            8 :     var input58 = List<int>.filled(input.length, 0);
      96           12 :     for (var i = 0; i < input.length; i++) {
      97           12 :       var charint = alphabet.indexOf(input[i]);
      98            4 :       if (charint < 0) {
      99            0 :         throw FormatException('Invalid input formatting for Base58 decoding.');
     100              :       }
     101            4 :       input58[i] = charint;
     102              :     }
     103              : 
     104              :     // count leading zeroes
     105              :     var leadingZeroes = 0;
     106           16 :     while (leadingZeroes < input58.length && input58[leadingZeroes] == 0) {
     107            0 :       leadingZeroes++;
     108              :     }
     109              : 
     110              :     // decode
     111            8 :     var output = Uint8List(input.length);
     112            4 :     var j = output.length;
     113              :     var startAt = leadingZeroes;
     114            8 :     while (startAt < input58.length) {
     115            4 :       var mod = _divmod256(input58, startAt);
     116           12 :       if (input58[startAt] == 0) startAt++;
     117            8 :       output[--j] = mod;
     118              :     }
     119              : 
     120              :     // remove unnecessary leading zeroes
     121           16 :     while (j < output.length && output[j] == 0) {
     122            4 :       j++;
     123              :     }
     124            8 :     return output.sublist(j - leadingZeroes);
     125              :   }
     126              : 
     127              :   /// number -> number / 256
     128              :   /// returns number % 256
     129            4 :   static int _divmod256(List<int> number58, int startAt) {
     130              :     var remaining = 0;
     131           12 :     for (var i = startAt; i < number58.length; i++) {
     132           16 :       var num = 58 * remaining + (number58[i] & 0xFF);
     133            8 :       number58[i] = num ~/ 256;
     134            4 :       remaining = num % 256;
     135              :     }
     136              :     return remaining;
     137              :   }
     138              : }
     139              : 
     140              : class Base58CheckPayload {
     141            2 :   const Base58CheckPayload(this.version, this.payload);
     142              :   final int version;
     143              :   final List<int> payload;
     144            0 :   @override
     145              :   bool operator ==(Object other) =>
     146            0 :       other is Base58CheckPayload &&
     147            0 :       version == other.version &&
     148            0 :       _areEqual(payload, other.payload);
     149            0 :   @override
     150            0 :   int get hashCode => version.hashCode ^ hash(payload);
     151              : }
     152              : 
     153              : /// A codec for Base58Check, a binary-to-string encoding used
     154              : /// in cryptocurrencies like Bitcoin and Ripple.
     155              : ///
     156              : /// The constructor requires the alphabet and a function that
     157              : /// performs a SINGLE-round SHA-256 digest on a [List<int>] and
     158              : /// returns a [List<int>] as result.
     159              : ///
     160              : /// For all details about Base58Check, see the Bitcoin wiki page:
     161              : /// https://en.bitcoin.it/wiki/Base58Check_encoding
     162              : class Base58CheckCodec extends Codec<Base58CheckPayload, String> {
     163            2 :   Base58CheckCodec(this.alphabet)
     164            2 :       : _encoder = Base58CheckEncoder(alphabet),
     165            2 :         _decoder = Base58CheckDecoder(alphabet);
     166              : 
     167              :   /// A codec that works with the Ripple alphabet and the SHA256 hash function.
     168            0 :   Base58CheckCodec.ripple() : this(rippleAlphabet);
     169              : 
     170              :   /// A codec that works with the Bitcoin alphabet and the SHA256 hash function.
     171            4 :   Base58CheckCodec.bitcoin() : this(bitcoinAlphabet);
     172              : 
     173              :   static const bitcoinAlphabet =
     174              :       '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
     175              : 
     176              :   static const rippleAlphabet =
     177              :       'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz';
     178              : 
     179              :   final String alphabet;
     180              : 
     181              :   Base58CheckEncoder _encoder;
     182              :   Base58CheckDecoder _decoder;
     183              : 
     184            1 :   @override
     185            1 :   Converter<Base58CheckPayload, String> get encoder => _encoder;
     186              : 
     187            2 :   @override
     188            2 :   Converter<String, Base58CheckPayload> get decoder => _decoder;
     189              : 
     190            0 :   Base58CheckPayload decodeUnchecked(String encoded) =>
     191            0 :       _decoder.convertUnchecked(encoded);
     192              : }
     193              : 
     194              : class Base58CheckEncoder extends Converter<Base58CheckPayload, String> {
     195            2 :   const Base58CheckEncoder(this.alphabet);
     196              :   final String alphabet;
     197              : 
     198            1 :   @override
     199              :   String convert(Base58CheckPayload input) {
     200            5 :     var bytes = Uint8List(input.payload.length + 1 + 4);
     201            3 :     bytes[0] = 0xFF & input.version;
     202            4 :     bytes.setRange(1, bytes.length - 4, input.payload);
     203            4 :     var checksum = _hash(bytes.sublist(0, bytes.length - 4));
     204            5 :     bytes.setRange(bytes.length - 4, bytes.length, checksum.getRange(0, 4));
     205            3 :     return Base58Encoder(alphabet).convert(bytes);
     206              :   }
     207              : }
     208              : 
     209            2 : List<int> _hash(List<int> b) =>
     210           10 :     SHA256Digest().process(SHA256Digest().process(Uint8List.fromList(b)));
     211              : 
     212              : class Base58CheckDecoder extends Converter<String, Base58CheckPayload> {
     213            2 :   const Base58CheckDecoder(this.alphabet);
     214              :   final String alphabet;
     215              : 
     216            2 :   @override
     217            2 :   Base58CheckPayload convert(String input) => _convert(input, true);
     218              : 
     219            0 :   Base58CheckPayload convertUnchecked(String encoded) =>
     220            0 :       _convert(encoded, false);
     221              : 
     222            2 :   Base58CheckPayload _convert(String encoded, bool validateChecksum) {
     223            6 :     var bytes = Base58Decoder(alphabet).convert(encoded);
     224            4 :     if (bytes.length < 5) {
     225            0 :       throw FormatException(
     226              :           'Invalid Base58Check encoded string: must be at least size 5');
     227              :     }
     228            8 :     var checksum = _hash(bytes.sublist(0, bytes.length - 4));
     229            8 :     var providedChecksum = bytes.sublist(bytes.length - 4, bytes.length);
     230              :     if (validateChecksum &&
     231            4 :         !_areEqual(providedChecksum, checksum.sublist(0, 4))) {
     232            1 :       throw FormatException('Invalid checksum in Base58Check encoding.');
     233              :     }
     234           10 :     return Base58CheckPayload(bytes[0], bytes.sublist(1, bytes.length - 4));
     235              :   }
     236              : }
     237              : 
     238            2 : bool _areEqual(List<int> left, List<int> right) {
     239              :   if (identical(left, right)) {
     240              :     return true;
     241              :   }
     242              : 
     243            6 :   if (left.length != right.length) {
     244              :     return false;
     245              :   }
     246              : 
     247            6 :   for (var i = 0; i < left.length; i++) {
     248            6 :     if (left[i] != right[i]) {
     249              :       return false;
     250              :     }
     251              :   }
     252              : 
     253              :   return true;
     254              : }
     255              : 
     256            0 : int hash(List<int>? list) {
     257              :   const hashMask = 0x7fffffff;
     258              : 
     259            0 :   if (list == null) return null.hashCode;
     260              :   // Jenkins's one-at-a-time hash function.
     261              :   // This code is almost identical to the one in IterableEquality, except
     262              :   // that it uses indexing instead of iterating to get the elements.
     263              :   var hash = 0;
     264            0 :   for (var i = 0; i < list.length; i++) {
     265            0 :     var c = list[i].hashCode;
     266            0 :     hash = (hash + c) & hashMask;
     267            0 :     hash = (hash + (hash << 10)) & hashMask;
     268            0 :     hash ^= (hash >> 6);
     269              :   }
     270            0 :   hash = (hash + (hash << 3)) & hashMask;
     271            0 :   hash ^= (hash >> 11);
     272            0 :   hash = (hash + (hash << 15)) & hashMask;
     273              :   return hash;
     274              : }
     275              : 
     276            1 : String base58CheckEncode(int version, Uint8List payload) {
     277            2 :   return Base58CheckCodec.bitcoin().encode(
     278            1 :     Base58CheckPayload(version, payload),
     279              :   );
     280              : }
     281              : 
     282            2 : Uint8List base58CheckDecode(String input) {
     283            8 :   return Uint8List.fromList(Base58CheckCodec.bitcoin().decode(input).payload);
     284              : }
     285              : 
     286            1 : Uint8List base58CheckDecodeWithVersion(String input) {
     287            2 :   final result = Base58CheckCodec.bitcoin().decode(input);
     288            4 :   return Uint8List.fromList([result.version, ...result.payload]);
     289              : }
     290              : 
     291            3 : Uint8List base58Decode(String input, {bool withChecksum = false}) {
     292            9 :   final decoded = base58BitcoinCodec.decoder.convert(input).toUint8List;
     293              :   if (withChecksum) return decoded;
     294            9 :   return decoded.sublist(0, decoded.length - 4);
     295              : }
     296              : 
     297            8 : String base58Encode(Uint8List input) {
     298           16 :   return base58BitcoinCodec.encoder.convert(input);
     299              : }
        

Generated by: LCOV version 2.0-1