Sreekanth G S
2 min read

RLP Encoding & Decoding in C#

This is a blog post with sample code explaining RLP (Recursive Length Prefix) encoding used by Ethereum while serializing and deserializing transactions and other information.

Please note that this should not be used for production use as is, and must be optimized and all edge cases covered prior to such use.

Refer: https://github.com/ethereum/wiki/wiki/RLP

Encoding of ArrayList

public byte[] Encode(ArrayList data) {
	data.Reverse();
	if (data.Count == 0) return new byte[] { (byte)'\xc0'
	};
	MemoryStream stream = new MemoryStream();
	BinaryWriter writer = new BinaryWriter(stream);
	data.Reverse();
	foreach(byte[] item in data) {
		writer.Write(Encode(item));
	}
	writer.Close();
	var result = stream.ToArray();
	switch (result.Length) {
	case int b when result.Length <= 55 : 
        var prefix = (byte)'\xc0' + (byte) result.Length;
		var prefixBytes = BitConverter.GetBytes(prefix);
		prefixBytes = RemoveNullBytes(prefixBytes)
		result = prefixBytes.Concat(result).ToArray();
		break;
	case int b when result.Length > 55 : 
        var lengthBytes = BitConverter.GetBytes(result.Length);
		lengthBytes = RemoveNullBytes(lengthBytes);
		var prefixBytes = BitConverter.GetBytes((byte)'\xf7' + (byte) lengthBytes.Length);
		prefixBytes = RemoveNullBytes(prefixBytes);
		var prefixAndLengthBytes = prefixBytes.Concat(lengthBytes);
		result = prefixAndLengthBytes.Concat(result).ToArray();
		break;
	}
	return result;
}

Encoding of Byte Array

public byte[] Encode(byte[] data) {
	if (data.Length == 0) 
        return new byte[] { (byte)'\x80' };
	if (data.Length == 1 && data[0] <= (byte)'\x7f') 
        return new byte[] { data[0] };
	if (data.Length >= 1 && data.Length <= 55) {
		var length = data.Length + 128;
		var lengthBytes = BitConverter.GetBytes(length);
		lengthBytes = RemoveNullBytes(lengthBytes)
		return lengthBytes.Concat(data).ToArray();
	}
	else if (data.Length > 55) {
		var length = data.Length;
		var lengthBytes = BitConverter.GetBytes(length);
		lengthBytes = RemoveNullBytes(lengthBytes);
		var prefix = data.Length + 183;
		var prefixBytes = BitConverter.GetBytes(prefix);
		prefixBytes = RemoveNullBytes(prefixBytes)
		var prefixAndLengthBytes = prefixBytes.Concat(lengthBytes);
		return prefixAndLengthBytes.Concat(data).ToArray();
	}
	return new byte[] {};
}

Decoding of Byte Array

public ArrayList Decode(byte[] encodedBytes, ArrayList rlpList) {
	MemoryStream stream = new MemoryStream(encodedBytes);
	BinaryReader reader = new BinaryReader(stream);
	while (reader.BaseStream.Position != reader.BaseStream.Length) {
		byte firstByte = reader.ReadByte();
		if (firstByte <= (byte)'\xbf') {
			switch (firstByte) {
			case byte b when firstByte <= (byte)'\x7f': 
                rlpList.Add(new byte[] { firstByte });
				break;
			case byte b when firstByte >= (byte)'\x80' && firstByte <= (byte)'\xb7': 
                var data = reader.ReadBytes(firstByte - (byte)'\x80');
				rlpList.Add(data);
				break;
			case byte b when firstByte >= (byte)'\xb8' && firstByte <= (byte)'\xbf': 
                var lengthOfLengthBytes = firstByte - (byte)'\xb7';
				var lengthBytes = reader.ReadBytes(lengthOfLengthBytes);
				var paddingSuffix = new byte[4 - lengthBytes.Length];
				var length = (lengthBytes).Concat(paddingSuffix).ToArray();
				var dataLength = BitConverter.ToInt32(length, 0);
				rlpList.Add(reader.ReadBytes(dataLength));
				break;
			};
		}
		else if (firstByte >= (byte)'\xc0' && firstByte <= (byte)'\xff') {
			switch (firstByte) {
			case byte b when firstByte <= (byte)'\xf7': 
                Decode(ReadAllBytes(reader), rlpList);
				break;
			case byte b when firstByte >= (byte)'\xf8' && firstByte <= (byte)'\xff': 
                reader.ReadBytes(firstByte - (byte)'\xf7');
				Decode(ReadAllBytes(reader), rlpList);
				break;
			}
		}
	}
	reader.Close();
	stream.Close();
	return rlpList;
}