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