| 1 | import 'dart:typed_data'; |
| 2 | |
| 3 | import 'package:web3dart/crypto.dart'; |
| 4 | |
| 5 | /// RLP Decode |
| 6 | /// |
| 7 | /// Adapted from https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/rlp |
| 8 | |
| 9 | class _Decoded { |
| 10 | Uint8List remainder; |
| 11 | List data; |
| 12 | |
| 13 | _Decoded(this.data, this.remainder); |
| 14 | } |
| 15 | |
| 16 | int _decodeLength(Uint8List v) { |
| 17 | if (v[0] == 0) throw Exception('invalid RLP: extra zeros'); |
| 18 | return int.parse(bytesToHex(v), radix: 16); |
| 19 | } |
| 20 | |
| 21 | _Decoded _decode(Uint8List input) { |
| 22 | final firstByte = input[0]; |
| 23 | |
| 24 | if (firstByte <= 0x7f) { |
| 25 | // a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. |
| 26 | return _Decoded(input.sublist(0, 1), input.sublist(1)); |
| 27 | } else if (firstByte <= 0xb7) { |
| 28 | // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string |
| 29 | // The range of the first byte is [0x80, 0xb7] |
| 30 | final length = firstByte - 0x7f; |
| 31 | |
| 32 | // set 0x80 null to 0 |
| 33 | final data = firstByte == 0x80 ? Uint8List(0) : input.sublist(1, length); |
| 34 | |
| 35 | if (length == 2 && data[0] < 0x80) { |
| 36 | throw Exception('invalid RLP encoding: invalid prefix, single byte < 0x80 are not prefixed'); |
| 37 | } |
| 38 | |
| 39 | return _Decoded(data, input.sublist(length)); |
| 40 | } else if (firstByte <= 0xbf) { |
| 41 | // string is greater than 55 bytes long. A single byte with the value (0xb7 plus the length of the length), |
| 42 | // followed by the length, followed by the string |
| 43 | final lLength = firstByte - 0xb6; |
| 44 | if (input.length - 1 < lLength) { |
| 45 | throw Exception('invalid RLP: not enough bytes for string length'); |
| 46 | } |
| 47 | |
| 48 | final length = _decodeLength(input.sublist(1, lLength)); |
| 49 | if (length <= 55) { |
| 50 | throw Exception('invalid RLP: expected string length to be greater than 55'); |
| 51 | } |
| 52 | |
| 53 | final data = input.sublist(lLength, length + lLength); |
| 54 | return _Decoded(data, input.sublist(length + lLength)); |
| 55 | } else if (firstByte <= 0xf7) { |
| 56 | // a list between 0-55 bytes long |
| 57 | final length = firstByte - 0xbf; |
| 58 | var innerRemainder = input.sublist(1, length); |
| 59 | |
| 60 | final decoded = []; |
| 61 | while (innerRemainder.isNotEmpty) { |
| 62 | final d = _decode(innerRemainder); |
| 63 | decoded.add(d.data); |
| 64 | innerRemainder = d.remainder; |
| 65 | } |
| 66 | |
| 67 | return _Decoded(decoded, input.sublist(length)); |
| 68 | } else { |
| 69 | // a list over 55 bytes long |
| 70 | final lLength = firstByte - 0xf6; |
| 71 | |
| 72 | final length = _decodeLength(input.sublist(1, lLength)); |
| 73 | if (length < 56) { |
| 74 | throw Exception('invalid RLP: encoded list too short'); |
| 75 | } |
| 76 | |
| 77 | final totalLength = lLength + length; |
| 78 | if (totalLength > input.length) { |
| 79 | throw Exception('invalid RLP: total length is larger than the data'); |
| 80 | } |
| 81 | |
| 82 | var innerRemainder = input.sublist(lLength, totalLength); |
| 83 | final decoded = []; |
| 84 | while (innerRemainder.isNotEmpty) { |
| 85 | final d = _decode(innerRemainder); |
| 86 | decoded.add(d.data); |
| 87 | innerRemainder = d.remainder; |
| 88 | } |
| 89 | |
| 90 | return _Decoded(decoded, input.sublist(totalLength)); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | /// RLP Decoding based on https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ |
| 95 | List decode(Uint8List input) { |
| 96 | final decoded = _decode(input); |
| 97 | |
| 98 | if (decoded.remainder.isNotEmpty) throw Exception('invalid RLP: remainder must be zero'); |
| 99 | |
| 100 | return decoded.data; |
| 101 | } |