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-07-02 01:23:33 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 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           16 :   Uint8List get buffer => Uint8List.fromList([for (final item in value) ...encodeRLP(item)]);
     113              : 
     114           12 :   int get length => value.length;
     115              : 
     116            6 :   RLPItem operator [](int index) {
     117           12 :     return value[index];
     118              :   }
     119              : 
     120            6 :   List<String> get hexValues => value.map((e) => e.hex).toList();
     121              : 
     122            1 :   @override
     123            4 :   String get hex => "[" + hexValues.join(", ") + "]";
     124              : 
     125            6 :   factory RLPList.fromBuffer(
     126              :     Uint8List buffer,
     127              :     int offset,
     128              :     int childOffset,
     129              :     int length,
     130              :   ) {
     131            6 :     final items = <RLPItem>[];
     132           18 :     while (childOffset < offset + 1 + length) {
     133            6 :       final (item, consumed) = decodeRLP(buffer, offset: childOffset);
     134            6 :       items.add(item);
     135            6 :       childOffset += consumed;
     136              :     }
     137            6 :     return RLPList(items);
     138              :   }
     139              : }
     140              : 
     141            4 : Uint8List encodeRLP(RLPItem input) {
     142            4 :   final buffer = input.buffer;
     143              : 
     144              :   /// Check for int = 0
     145           16 :   if (input is RLPInt && buffer.length == 1 && input.value == 0) {
     146              :     return buffer;
     147              :   }
     148              : 
     149              :   /// Check for bigint = 0
     150           21 :   if (input is RLPBigInt && buffer.length == 1 && input.value == BigInt.zero) {
     151              :     return buffer;
     152              :   }
     153           18 :   if (input is! RLPList && buffer.length == 1 && buffer[0] < 128) {
     154              :     return buffer;
     155              :   }
     156              : 
     157            8 :   if (buffer.length < 56) {
     158              :     final flag = switch (input) {
     159            4 :       RLPList _ => 0xc0,
     160              :       _ => 0x80,
     161              :     };
     162              : 
     163           20 :     return Uint8List.fromList([flag + buffer.length, ...buffer]);
     164              :   }
     165              : 
     166            6 :   final lengthBuffer = arrayifyInteger(buffer.length);
     167              : 
     168              :   final flag = switch (input) {
     169            3 :     RLPList _ => 0xf7,
     170              :     _ => 0xb7,
     171              :   };
     172              : 
     173            3 :   return Uint8List.fromList(
     174            3 :     [
     175            6 :       flag + lengthBuffer.length,
     176            3 :       ...lengthBuffer,
     177            3 :       ...buffer,
     178              :     ],
     179              :   );
     180              : }
     181              : 
     182            1 : RLPItem decodeRLPCheck(Uint8List input) {
     183            1 :   final (item, consumed) = decodeRLP(input);
     184            0 :   if (consumed != input.length) {
     185            0 :     throw RLPException("Invalid RLP: input is longer than specified length");
     186              :   }
     187              :   return item;
     188              : }
     189              : 
     190            6 : (RLPItem, int) decodeRLP(
     191              :   Uint8List input, {
     192              :   int offset = 0,
     193              : }) {
     194           12 :   final bytes = input.buffer.asByteData();
     195           12 :   if (offset >= input.length) {
     196            0 :     throw RLPException("offset out of bounds");
     197              :   }
     198            6 :   final firstByte = bytes.getUint8(offset);
     199              : 
     200              :   switch (firstByte) {
     201            6 :     case >= 0xf8:
     202            5 :       final lengthLength = firstByte - 0xf7;
     203           20 :       if (offset + 1 + lengthLength > input.length) {
     204            0 :         throw RLPException("insufficient data for length bytes");
     205              :       }
     206              : 
     207           10 :       final length = decodeLength(input, lengthLength, offset + 1);
     208              : 
     209            5 :       if (length < 56) {
     210            1 :         throw RLPException("encoded list too short");
     211              :       }
     212              : 
     213           10 :       final totalLength = 1 + lengthLength + length;
     214           15 :       if (offset + totalLength > input.length) {
     215            1 :         throw RLPException("insufficient data length");
     216              :       }
     217              : 
     218              :       // For Root Lists we can enforce a stric length check
     219            5 :       if (offset == 0) {
     220           15 :         if (offset + totalLength != input.length) {
     221            0 :           throw RLPException("Given input is longer than specified length");
     222              :         }
     223              :       }
     224              : 
     225              :       return (
     226            5 :         RLPList.fromBuffer(
     227              :           input,
     228              :           offset,
     229           10 :           offset + 1 + lengthLength,
     230            5 :           lengthLength + length,
     231              :         ),
     232           10 :         1 + lengthLength + length
     233              :       );
     234            6 :     case >= 0xc0:
     235            4 :       final length = firstByte - 0xc0;
     236              : 
     237           16 :       if (offset + 1 + length > input.length) {
     238            1 :         throw RLPException("insufficient data length");
     239              :       }
     240              : 
     241              :       // For Root Lists we can enforce a stric length check
     242            4 :       if (offset == 0) {
     243            9 :         if (1 + length != input.length) {
     244            1 :           throw RLPException("Given input is longer than specified length");
     245              :         }
     246              :       }
     247              : 
     248              :       return (
     249            4 :         RLPList.fromBuffer(
     250              :           input,
     251              :           offset,
     252            4 :           offset + 1,
     253              :           length,
     254              :         ),
     255            4 :         1 + length
     256              :       );
     257            6 :     case >= 0xb8:
     258            5 :       final lengthLength = firstByte - 0xb7;
     259           20 :       if (offset + 1 + lengthLength > input.length) {
     260            1 :         throw RLPException("insufficient data for length bytes");
     261              :       }
     262           10 :       final length = decodeLength(input, lengthLength, offset + 1);
     263              : 
     264            5 :       if (length < 56) {
     265            1 :         throw RLPException("Invalid RLP: length is too short");
     266              :       }
     267           25 :       if (offset + 1 + lengthLength + length > input.length) {
     268            1 :         throw RLPException("insufficient data length");
     269              :       }
     270            5 :       final result = input.sublist(
     271           10 :         offset + 1 + lengthLength,
     272           15 :         offset + 1 + lengthLength + length,
     273              :       );
     274           15 :       return (RLPBytes(result), 1 + lengthLength + length);
     275            6 :     case >= 0x80:
     276            6 :       final length = firstByte - 0x80;
     277           24 :       if (offset + 1 + length > input.length) {
     278            1 :         throw RLPException("insufficient data length");
     279              :       }
     280            6 :       final result = input.sublist(
     281            6 :         offset + 1,
     282           12 :         offset + 1 + length,
     283              :       );
     284           10 :       if (STRICT_RLP_DECODE && length == 1 && result[0] < 0x80) {
     285            2 :         throw RLPException(
     286              :           "invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed",
     287              :         );
     288              :       }
     289           12 :       return (RLPBytes(result), 1 + length);
     290              :     default:
     291            6 :       final result = input.sublist(offset, offset + 1);
     292            3 :       return (RLPBytes(result), 1);
     293              :   }
     294              : }
     295              : 
     296            4 : Uint8List arrayifyInteger(int value) {
     297            4 :   if (value == 0) {
     298            4 :     return Uint8List.fromList([0x80]);
     299              :   }
     300            4 :   List<int> result = [];
     301            4 :   while (value > 0) {
     302            8 :     result.insert(0, value & 0xff);
     303            4 :     value >>= 8;
     304              :   }
     305              : 
     306            4 :   return Uint8List.fromList(result);
     307              : }
     308              : 
     309            5 : int decodeLength(
     310              :   Uint8List input,
     311              :   int lengthLength,
     312              :   int offset,
     313              : ) {
     314           10 :   final buffer = input.sublist(offset, offset + lengthLength);
     315           16 :   if (buffer.length > 1 && buffer[0] == 0) {
     316            1 :     throw RLPException('Leading zeros are not allowed');
     317              :   }
     318            5 :   return buffer.toUInt;
     319              : }
        

Generated by: LCOV version 2.0-1