|
| 1 | +use alexandria_data_structures::array_ext::ArrayTraitExt; |
| 2 | +use alexandria_numeric::integers::UIntBytes; |
| 3 | + |
| 4 | +// Possible RLP errors |
| 5 | +#[derive(Drop, Copy, PartialEq)] |
| 6 | +enum RLPError { |
| 7 | + EmptyInput, |
| 8 | + InputTooShort, |
| 9 | + PayloadTooLong |
| 10 | +} |
| 11 | + |
| 12 | +// Possible RLP types |
| 13 | +#[derive(Drop, PartialEq)] |
| 14 | +enum RLPType { |
| 15 | + String, |
| 16 | + List |
| 17 | +} |
| 18 | + |
| 19 | +#[derive(Drop, Copy, PartialEq)] |
| 20 | +enum RLPItem { |
| 21 | + String: Span<u8>, |
| 22 | + List: Span<RLPItem> |
| 23 | +} |
| 24 | + |
| 25 | +#[generate_trait] |
| 26 | +impl RLPImpl of RLPTrait { |
| 27 | + /// Returns RLPType from the leading byte with |
| 28 | + /// its offset in the array as well as its size. |
| 29 | + /// |
| 30 | + /// # Arguments |
| 31 | + /// * `input` - Array of byte to decode |
| 32 | + /// # Returns |
| 33 | + /// * `(RLPType, offset, size)` - A tuple containing the RLPType |
| 34 | + /// the offset and the size of the RLPItem to decode |
| 35 | + /// # Errors |
| 36 | + /// * empty input - if the input is empty |
| 37 | + /// * input too short - if the input is too short for a given |
| 38 | + fn decode_type(input: Span<u8>) -> Result<(RLPType, u32, u32), RLPError> { |
| 39 | + let input_len = input.len(); |
| 40 | + if input_len == 0 { |
| 41 | + return Result::Err(RLPError::EmptyInput); |
| 42 | + } |
| 43 | + |
| 44 | + let prefix_byte = *input[0]; |
| 45 | + |
| 46 | + if prefix_byte < 0x80 { // Char |
| 47 | + return Result::Ok((RLPType::String, 0, 1)); |
| 48 | + } else if prefix_byte < 0xb8 { // Short String |
| 49 | + return Result::Ok((RLPType::String, 1, prefix_byte.into() - 0x80)); |
| 50 | + } else if prefix_byte < 0xc0 { // Long String |
| 51 | + let len_bytes_count: u32 = (prefix_byte - 0xb7).into(); |
| 52 | + if input_len <= len_bytes_count { |
| 53 | + return Result::Err(RLPError::InputTooShort); |
| 54 | + } |
| 55 | + let string_len_bytes = input.slice(1, len_bytes_count); |
| 56 | + let string_len: u32 = UIntBytes::from_bytes(string_len_bytes) |
| 57 | + .ok_or(RLPError::PayloadTooLong)?; |
| 58 | + |
| 59 | + return Result::Ok((RLPType::String, 1 + len_bytes_count, string_len)); |
| 60 | + } else if prefix_byte < 0xf8 { // Short List |
| 61 | + return Result::Ok((RLPType::List, 1, prefix_byte.into() - 0xc0)); |
| 62 | + } else { // Long List |
| 63 | + let len_bytes_count = prefix_byte.into() - 0xf7; |
| 64 | + if input.len() <= len_bytes_count { |
| 65 | + return Result::Err(RLPError::InputTooShort); |
| 66 | + } |
| 67 | + let list_len_bytes = input.slice(1, len_bytes_count); |
| 68 | + let list_len: u32 = UIntBytes::from_bytes(list_len_bytes) |
| 69 | + .ok_or(RLPError::PayloadTooLong)?; |
| 70 | + return Result::Ok((RLPType::List, 1 + len_bytes_count, list_len)); |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + /// Recursively encodes multiple a list of RLPItems |
| 75 | + /// # Arguments |
| 76 | + /// * `input` - Span of RLPItem to encode |
| 77 | + /// # Returns |
| 78 | + /// * `Span<u8> - RLP encoded items as a span of bytes |
| 79 | + /// # Errors |
| 80 | + /// * empty input - if the input is empty |
| 81 | + fn encode(mut input: Span<RLPItem>) -> Result<Span<u8>, RLPError> { |
| 82 | + if input.len() == 0 { |
| 83 | + return Result::Err(RLPError::EmptyInput); |
| 84 | + } |
| 85 | + |
| 86 | + let mut output: Array<u8> = Default::default(); |
| 87 | + // Safe to unwrap because input length is not 0 |
| 88 | + let item = input.pop_front().unwrap(); |
| 89 | + |
| 90 | + match item { |
| 91 | + RLPItem::String(string) => { output.concat_span(RLPTrait::encode_string(*string)?); }, |
| 92 | + RLPItem::List(list) => { |
| 93 | + if (*list).len() == 0 { |
| 94 | + output.append(0xc0); |
| 95 | + } else { |
| 96 | + let payload = RLPTrait::encode(*list)?; |
| 97 | + let payload_len = payload.len(); |
| 98 | + if payload_len > 55 { |
| 99 | + let len_in_bytes = payload_len.to_bytes(); |
| 100 | + // The payload length being a u32, the length in bytes |
| 101 | + // will maximum be equal to 4, making the unwrap safe |
| 102 | + output.append(0xf7 + len_in_bytes.len().try_into().unwrap()); |
| 103 | + output.concat_span(len_in_bytes); |
| 104 | + } else { |
| 105 | + // Safe to unwrap because payload_len<55 |
| 106 | + output.append(0xc0 + payload_len.try_into().unwrap()); |
| 107 | + } |
| 108 | + output.concat_span(payload); |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + if input.len() > 0 { |
| 114 | + output.concat_span(RLPTrait::encode(input)?); |
| 115 | + } |
| 116 | + |
| 117 | + Result::Ok(output.span()) |
| 118 | + } |
| 119 | + |
| 120 | + /// RLP encodes a Array of bytes representing a RLP String. |
| 121 | + /// # Arguments |
| 122 | + /// * `input` - Array of bytes representing a RLP String to encode |
| 123 | + /// # Returns |
| 124 | + /// * `Span<u8> - RLP encoded items as a span of bytes |
| 125 | + fn encode_string(input: Span<u8>) -> Result<Span<u8>, RLPError> { |
| 126 | + let len = input.len(); |
| 127 | + if len == 0 { |
| 128 | + return Result::Ok(array![0x80].span()); |
| 129 | + } else if len == 1 && *input[0] < 0x80 { |
| 130 | + return Result::Ok(input); |
| 131 | + } else if len < 56 { |
| 132 | + let mut encoding: Array<u8> = Default::default(); |
| 133 | + // Safe to unwrap because len<56 |
| 134 | + encoding.append(0x80 + len.try_into().unwrap()); |
| 135 | + encoding.concat_span(input); |
| 136 | + return Result::Ok(encoding.span()); |
| 137 | + } else { |
| 138 | + let mut encoding: Array<u8> = Default::default(); |
| 139 | + let len_as_bytes = len.to_bytes(); |
| 140 | + let len_bytes_count = len_as_bytes.len(); |
| 141 | + // The payload length being a u32, the length in bytes |
| 142 | + // will maximum be equal to 4, making the unwrap safe |
| 143 | + let prefix = 0xb7 + len_bytes_count.try_into().unwrap(); |
| 144 | + encoding.append(prefix); |
| 145 | + encoding.concat_span(len_as_bytes); |
| 146 | + encoding.concat_span(input); |
| 147 | + return Result::Ok(encoding.span()); |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + /// Recursively decodes a rlp encoded byte array |
| 152 | + /// as described in https://ethereum.org/en/developers/docs/data-structures-and-encoding/rlp/ |
| 153 | + /// |
| 154 | + /// # Arguments |
| 155 | + /// * `input` - Array of bytes to decode |
| 156 | + /// # Returns |
| 157 | + /// * `Span<RLPItem>` - Span of RLPItem |
| 158 | + /// # Errors |
| 159 | + /// * input too short - if the input is too short for a given |
| 160 | + fn decode(input: Span<u8>) -> Result<Span<RLPItem>, RLPError> { |
| 161 | + let mut output: Array<RLPItem> = Default::default(); |
| 162 | + let input_len = input.len(); |
| 163 | + |
| 164 | + let (rlp_type, offset, len) = RLPTrait::decode_type(input)?; |
| 165 | + |
| 166 | + if input_len < offset + len { |
| 167 | + return Result::Err(RLPError::InputTooShort); |
| 168 | + } |
| 169 | + |
| 170 | + match rlp_type { |
| 171 | + RLPType::String => { |
| 172 | + if (len == 0) { |
| 173 | + output.append(RLPItem::String(array![].span())); |
| 174 | + } else { |
| 175 | + output.append(RLPItem::String(input.slice(offset, len))); |
| 176 | + } |
| 177 | + }, |
| 178 | + RLPType::List => { |
| 179 | + if len > 0 { |
| 180 | + let res = RLPTrait::decode(input.slice(offset, len))?; |
| 181 | + output.append(RLPItem::List(res)); |
| 182 | + } else { |
| 183 | + output.append(RLPItem::List(array![].span())); |
| 184 | + } |
| 185 | + } |
| 186 | + }; |
| 187 | + |
| 188 | + let total_item_len = len + offset; |
| 189 | + if total_item_len < input_len { |
| 190 | + output |
| 191 | + .concat_span( |
| 192 | + RLPTrait::decode(input.slice(total_item_len, input_len - total_item_len))? |
| 193 | + ); |
| 194 | + } |
| 195 | + |
| 196 | + Result::Ok(output.span()) |
| 197 | + } |
| 198 | +} |
0 commit comments