LCOV - code coverage report
Current view: top level - crypto/evm/utils - rlp.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.3 % 150 143
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:walletkit_dart/src/domain/extensions.dart';
       5              : 
       6              : final class RLPException implements Exception {
       7              :   final String message;
       8            1 :   const RLPException(this.message);
       9              : 
      10            1 :   @override
      11              :   String toString() {
      12            2 :     return "RLPException: $message";
      13              :   }
      14              : }
      15              : 
      16              : sealed class RLPItem<T> {
      17              :   final T value;
      18              : 
      19            6 :   RLPItem(this.value);
      20              : 
      21              :   Uint8List get buffer;
      22              : 
      23           12 :   String get hex => buffer.toHex;
      24              : 
      25            1 :   @override
      26            1 :   String toString() => hex;
      27              : 
      28            1 :   static RLPItem<dynamic> fromValue(dynamic value) {
      29            1 :     if (value is int) {
      30            1 :       return RLPInt(value);
      31            1 :     } else if (value is String) {
      32            1 :       if (value.startsWith("#")) {
      33            3 :         return RLPBigInt(BigInt.parse(value.substring(1)));
      34              :       }
      35              : 
      36            1 :       return RLPString(value);
      37            1 :     } else if (value is BigInt) {
      38            1 :       return RLPBigInt(value);
      39            1 :     } else if (value is Uint8List) {
      40            1 :       return RLPBytes(value);
      41            1 :     } else if (value is List) {
      42            4 :       final items = value.map((item) => RLPItem.fromValue(item)).toList();
      43            1 :       return RLPList(items);
      44              :     }
      45            0 :     throw ArgumentError("Unsupported type: ${value.runtimeType}");
      46              :   }
      47              : }
      48              : 
      49              : final class RLPInt extends RLPItem<int> {
      50            4 :   RLPInt(super.value);
      51              : 
      52            4 :   @override
      53            8 :   Uint8List get buffer => arrayifyInteger(value);
      54              : }
      55              : 
      56              : final class RLPString extends RLPItem<String> {
      57              :   final bool? isHex;
      58              : 
      59            4 :   RLPString(super.value, {this.isHex});
      60              : 
      61            4 :   @override
      62              :   Uint8List get buffer {
      63            8 :     if (isHex == true) {
      64            2 :       if (value.startsWith("0x")) {
      65            0 :         return value.substring(2).hexToBytes;
      66              :       }
      67            2 :       return value.hexToBytes;
      68              :     }
      69              : 
      70            8 :     if (value.startsWith("0x")) {
      71            9 :       return value.substring(2).hexToBytes;
      72              :     }
      73              : 
      74            2 :     return utf8.encode(value);
      75              :   }
      76              : }
      77              : 
      78              : final class RLPBigInt extends RLPItem<BigInt> {
      79            4 :   RLPBigInt(super.value);
      80              : 
      81            4 :   @override
      82              :   Uint8List get buffer {
      83           12 :     if (value == BigInt.zero) {
      84            4 :       return Uint8List.fromList([0x80]);
      85              :     }
      86            8 :     return this.value.toBytesUnsigned;
      87              :   }
      88              : }
      89              : 
      90              : final class RLPNull extends RLPItem<Null> {
      91            8 :   RLPNull() : super(null);
      92              : 
      93            4 :   @override
      94            4 :   Uint8List get buffer => Uint8List(0);
      95              : }
      96              : 
      97              : final class RLPBytes extends RLPItem<Uint8List> {
      98            6 :   RLPBytes(super.value);
      99              : 
     100            4 :   @override
     101            4 :   Uint8List get buffer => value;
     102              : }
     103              : 
     104              : final class RLPList extends RLPItem<List<RLPItem>> {
     105            6 :   RLPList(super.value);
     106              : 
     107            4 :   @override
     108              :   Uint8List get buffer =>
     109           16 :       Uint8List.fromList([for (final item in value) ...encodeRLP(item)]);
     110              : 
     111           12 :   int get length => value.length;
     112              : 
     113            6 :   RLPItem operator [](int index) {
     114           12 :     return value[index];
     115              :   }
     116              : 
     117            6 :   List<String> get hexValues => value.map((e) => e.hex).toList();
     118              : 
     119            1 :   @override
     120            4 :   String get hex => "[" + hexValues.join(", ") + "]";
     121              : 
     122            6 :   factory RLPList.fromBuffer(
     123              :     Uint8List buffer,
     124              :     int offset,
     125              :     int childOffset,
     126              :     int length,
     127              :   ) {
     128            6 :     final items = <RLPItem>[];
     129           18 :     while (childOffset < offset + 1 + length) {
     130            6 :       final (item, consumed) = decodeRLP(buffer, offset: childOffset);
     131            6 :       items.add(item);
     132            6 :       childOffset += consumed;
     133              :     }
     134            6 :     return RLPList(items);
     135              :   }
     136              : }
     137              : 
     138            4 : Uint8List encodeRLP(RLPItem input) {
     139            4 :   final buffer = input.buffer;
     140              : 
     141              :   /// Check for int = 0
     142           16 :   if (input is RLPInt && buffer.length == 1 && input.value == 0) {
     143              :     return buffer;
     144              :   }
     145              : 
     146              :   /// Check for bigint = 0
     147           21 :   if (input is RLPBigInt && buffer.length == 1 && input.value == BigInt.zero) {
     148              :     return buffer;
     149              :   }
     150           18 :   if (input is! RLPList && buffer.length == 1 && buffer[0] < 128) {
     151              :     return buffer;
     152              :   }
     153              : 
     154            8 :   if (buffer.length < 56) {
     155              :     final flag = switch (input) {
     156            4 :       RLPList _ => 0xc0,
     157              :       _ => 0x80,
     158              :     };
     159              : 
     160           20 :     return Uint8List.fromList([flag + buffer.length, ...buffer]);
     161              :   }
     162              : 
     163            6 :   final lengthBuffer = arrayifyInteger(buffer.length);
     164              : 
     165              :   final flag = switch (input) {
     166            3 :     RLPList _ => 0xf7,
     167              :     _ => 0xb7,
     168              :   };
     169              : 
     170            3 :   return Uint8List.fromList(
     171            3 :     [
     172            6 :       flag + lengthBuffer.length,
     173            3 :       ...lengthBuffer,
     174            3 :       ...buffer,
     175              :     ],
     176              :   );
     177              : }
     178              : 
     179            1 : RLPItem decodeRLPCheck(Uint8List input) {
     180            1 :   final (item, consumed) = decodeRLP(input);
     181            0 :   if (consumed != input.length) {
     182            0 :     throw RLPException("Invalid RLP: input is longer than specified length");
     183              :   }
     184              :   return item;
     185              : }
     186              : 
     187            6 : (RLPItem, int) decodeRLP(
     188              :   Uint8List input, {
     189              :   int offset = 0,
     190              : }) {
     191           12 :   final bytes = input.buffer.asByteData();
     192           12 :   if (offset >= input.length) {
     193            0 :     throw RLPException("offset out of bounds");
     194              :   }
     195            6 :   final firstByte = bytes.getUint8(offset);
     196              : 
     197              :   switch (firstByte) {
     198            6 :     case >= 0xf8:
     199            4 :       final lengthLength = firstByte - 0xf7;
     200           16 :       if (offset + 1 + lengthLength > input.length) {
     201            0 :         throw RLPException("insufficient data for length bytes");
     202              :       }
     203              : 
     204            8 :       final length = decodeLength(input, lengthLength, offset + 1);
     205              : 
     206            4 :       if (length < 56) {
     207            1 :         throw RLPException("encoded list too short");
     208              :       }
     209              : 
     210            8 :       final totalLength = 1 + lengthLength + length;
     211           12 :       if (offset + totalLength > input.length) {
     212            1 :         throw RLPException("insufficient data length");
     213              :       }
     214              : 
     215              :       // For Root Lists we can enforce a stric length check
     216            4 :       if (offset == 0) {
     217           12 :         if (offset + totalLength != input.length) {
     218            0 :           throw RLPException("Given input is longer than specified length");
     219              :         }
     220              :       }
     221              : 
     222              :       return (
     223            4 :         RLPList.fromBuffer(
     224              :           input,
     225              :           offset,
     226            8 :           offset + 1 + lengthLength,
     227            4 :           lengthLength + length,
     228              :         ),
     229            8 :         1 + lengthLength + length
     230              :       );
     231            6 :     case >= 0xc0:
     232            4 :       final length = firstByte - 0xc0;
     233              : 
     234           16 :       if (offset + 1 + length > input.length) {
     235            1 :         throw RLPException("insufficient data length");
     236              :       }
     237              : 
     238              :       // For Root Lists we can enforce a stric length check
     239            4 :       if (offset == 0) {
     240            9 :         if (1 + length != input.length) {
     241            1 :           throw RLPException("Given input is longer than specified length");
     242              :         }
     243              :       }
     244              : 
     245              :       return (
     246            4 :         RLPList.fromBuffer(
     247              :           input,
     248              :           offset,
     249            4 :           offset + 1,
     250              :           length,
     251              :         ),
     252            4 :         1 + length
     253              :       );
     254            6 :     case >= 0xb8:
     255            4 :       final lengthLength = firstByte - 0xb7;
     256           16 :       if (offset + 1 + lengthLength > input.length) {
     257            1 :         throw RLPException("insufficient data for length bytes");
     258              :       }
     259            8 :       final length = decodeLength(input, lengthLength, offset + 1);
     260              : 
     261            4 :       if (length < 56) {
     262            1 :         throw RLPException("Invalid RLP: length is too short");
     263              :       }
     264           20 :       if (offset + 1 + lengthLength + length > input.length) {
     265            1 :         throw RLPException("insufficient data length");
     266              :       }
     267            4 :       final result = input.sublist(
     268            8 :         offset + 1 + lengthLength,
     269           12 :         offset + 1 + lengthLength + length,
     270              :       );
     271           12 :       return (RLPBytes(result), 1 + lengthLength + length);
     272            6 :     case >= 0x80:
     273            6 :       final length = firstByte - 0x80;
     274           24 :       if (offset + 1 + length > input.length) {
     275            1 :         throw RLPException("insufficient data length");
     276              :       }
     277            6 :       final result = input.sublist(
     278            6 :         offset + 1,
     279           12 :         offset + 1 + length,
     280              :       );
     281            8 :       if (length == 1 && result[0] < 0x80) {
     282            1 :         throw RLPException(
     283              :           "invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed",
     284              :         );
     285              :       }
     286           12 :       return (RLPBytes(result), 1 + length);
     287              :     default:
     288            6 :       final result = input.sublist(offset, offset + 1);
     289            3 :       return (RLPBytes(result), 1);
     290              :   }
     291              : }
     292              : 
     293            4 : Uint8List arrayifyInteger(int value) {
     294            4 :   if (value == 0) {
     295            4 :     return Uint8List.fromList([0x80]);
     296              :   }
     297            4 :   List<int> result = [];
     298            4 :   while (value > 0) {
     299            8 :     result.insert(0, value & 0xff);
     300            4 :     value >>= 8;
     301              :   }
     302              : 
     303            4 :   return Uint8List.fromList(result);
     304              : }
     305              : 
     306            4 : int decodeLength(
     307              :   Uint8List input,
     308              :   int lengthLength,
     309              :   int offset,
     310              : ) {
     311            8 :   final buffer = input.sublist(offset, offset + lengthLength);
     312           14 :   if (buffer.length > 1 && buffer[0] == 0) {
     313            1 :     throw RLPException('Leading zeros are not allowed');
     314              :   }
     315            4 :   return buffer.toUInt;
     316              : }
        

Generated by: LCOV version 2.0-1