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