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

Generated by: LCOV version 2.0-1