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

Generated by: LCOV version 2.0-1