Skip to content

IbrahimIjai/rust-rlp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rust-light-rlp

License: MIT Rust Tests

A small, dependency-free Rust implementation of RLP (Recursive Length Prefix) — the serialization format used across Ethereum's execution layer for transactions, block headers, receipts, and trie nodes.

The encoder and decoder are verified line-by-line against the official ethereum/ethereum-rlp Python reference and the ethereum.org RLP specification, including the strict canonical-form checks on decode.


Table of Contents


What is RLP?

RLP encodes arbitrarily nested arrays of binary data. It only knows two things:

  • byte strings (e.g. "dog", an address, a hash)
  • lists of items (which may themselves contain byte strings or lists)

Everything else in Ethereum — integers, booleans, structs — is first reduced to those two primitives and then encoded. RLP says nothing about data types; it only encodes structure and length. The result is a compact, deterministic byte layout.


Features

  • Encoding of byte strings and arbitrarily nested lists.
  • Decoding into a recursive RlpItem tree.
  • Strict, canonical decoding — rejects malformed input exactly as the reference does:
    • non-canonical single bytes (0x000x7f wrapped in a length prefix)
    • non-minimal long-form lengths (leading zero, or long form used for < 56 bytes)
    • truncated input and trailing bytes
  • Encodable trait for ergonomic typed encoding of u8/u64/u128/usize, bool, &str/String, and Vec/slices thereof — with correct big-endian, leading-zero-stripped integer rules (00x80).
  • Zero runtime dependencies (hex is used only in dev/tests).

Installation

This is a Cargo workspace crate. Add it as a path or git dependency:

[dependencies]
rust-light-rlp = { path = "crates/light-rlp" }

The package is named rust-light-rlp; the library is imported as light_rlp:

use light_rlp::{decode, encode, Encodable, RlpItem};

Quick Start

Encode and decode raw items

use light_rlp::{decode, encode, RlpItem};

let item = RlpItem::Bytes(b"dog".to_vec());
let encoded = encode(&item);

assert_eq!(encoded, vec![0x83, 0x64, 0x6f, 0x67]);
assert_eq!(decode(&encoded).unwrap(), item);

Nested lists

use light_rlp::{decode, encode, RlpItem};

let item = RlpItem::List(vec![
    RlpItem::Bytes(b"cat".to_vec()),
    RlpItem::Bytes(b"dog".to_vec()),
]);

let encoded = encode(&item);
assert_eq!(encoded, vec![0xc8, 0x83, 0x63, 0x61, 0x74, 0x83, 0x64, 0x6f, 0x67]);
assert_eq!(decode(&encoded).unwrap(), item);

Typed values via the Encodable trait

use light_rlp::Encodable;

assert_eq!(0u8.rlp_encode(),     vec![0x80]);        // integer 0 -> empty string
assert_eq!(1024u64.rlp_encode(), vec![0x82, 0x04, 0x00]); // big-endian, no leading zeros
assert_eq!(true.rlp_encode(),    vec![0x01]);
assert_eq!("dog".rlp_encode(),   vec![0x83, 0x64, 0x6f, 0x67]);

How It Maps to the Spec

Every rule in the Python reference (ethereum/ethereum-rlp, src/ethereum_rlp/rlp.py) has a direct counterpart in this crate.

Encoding rules

RLP rule Python reference This crate
Single byte [0x00, 0x7f] is its own encoding encode_bytes (len == 1 and b[0] < 0x80) encode/byte_string.rs
String 0–55 bytes → 0x80 + len, then data encode_bytes (len < 0x38) encode/byte_string.rs
String > 55 bytes → 0xb7 + len(len), big-endian len, data encode_bytes (else) encode/byte_string.rs
List payload 0–55 bytes → 0xc0 + len, then payload encode_sequence (len < 0x38) encode/list.rs
List payload > 55 bytes → 0xf7 + len(len), big-endian len, payload encode_sequence (else) encode/list.rs
Integers: big-endian, no leading zeros (0 → empty string) encode (Uintto_be_bytes) traits.rs
true0x01, false → empty string encode (bool) traits.rs

Decoding rules (strict / canonical)

Validation Python reference This crate
Prefix classification by first byte decode / decode_item_length decode/dispatch.rs
Reject single byte < 0x80 wrapped in length prefix decode_to_bytes (len == 1 and raw[0] < 0x80) decode/short.rsNonCanonicalSingleByte
Reject leading-zero long length decode_* (encoded[1] == 0) decode/long.rsNonCanonicalLength
Reject long form used for length < 56 decode_* (len < 0x38) decode/long.rsNonCanonicalLength
Reject truncated / out-of-bounds input decode_* (>= len(...)) LengthOutOfBounds / InputTooShort
Reject trailing bytes after the top-level item decode_* (< len(...)) decode/mod.rsTrailingBytes

The error variants live in types.rs (RlpError).


Project Structure

rust-rlp-ssz/
├── Cargo.toml                  # workspace manifest
├── README.md
├── LICENSE
└── crates/
    └── light-rlp/
        ├── Cargo.toml          # package: rust-light-rlp (lib: light_rlp)
        ├── README.md
        ├── examples/
        │   └── demo.rs         # runnable encode/decode showcase
        ├── src/
        │   ├── lib.rs          # public re-exports
        │   ├── types.rs        # RlpItem, RlpError
        │   ├── traits.rs       # Encodable trait + typed impls
        │   ├── encode/
        │   │   ├── mod.rs      # encode() dispatch
        │   │   ├── byte_string.rs
        │   │   └── list.rs
        │   └── decode/
        │       ├── mod.rs      # decode() entry + trailing-byte check
        │       ├── dispatch.rs # first-byte classification
        │       ├── short.rs    # short string / short list
        │       └── long.rs     # long string / long list + length parsing
        └── tests/
            └── spec_vectors.rs # spec conformance vectors

Testing

The suite covers unit tests in every module plus end-to-end spec vectors (47 tests total).

# Run everything
cargo test

# Just the spec-conformance vectors
cargo test --test spec_vectors

# A single test by name
cargo test encode_list_cat_dog -- --exact

Running the Demo

A self-contained example prints encode/decode roundtrips as hex and shows the strict decoder rejecting non-canonical input:

cargo run --example demo

Sample output:

=== rust-light-rlp :: encoding ===

  string "dog"                 -> 0x83646f67
  list ["cat", "dog"]          -> 0xc88363617483646f67
  u64 1024                     -> 0x820400
  bool true                    -> 0x01

=== strict decode rejects non-canonical input ===

  0x8100  -> Err(NonCanonicalSingleByte)   (0x00 wrapped, should be bare)
  0xb837  -> Err(NonCanonicalLength)       (long form for 55 bytes)
  0x8080  -> Err(TrailingBytes)            (trailing bytes)

License

Released under the MIT License. © 2026 ibrahimijai.

About

Lightweight Rust implementations of Ethereum RLP and SSZ serialization.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages