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 : }
|