Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run RLP examples in the interactive playground
String Encoding Rules
RLP string encoding has three cases based on byte length:
1. Single Byte < 0x80
For a single byte with value less than 0x80 (128), the byte encodes as itself with no prefix:
import { Rlp } from 'tevm'
// Byte value 0x7f (127)
const input = new Uint8Array ([ 0x7f ])
const encoded = Rlp . encodeBytes ( input )
// => Uint8Array([0x7f])
// Byte value 0x00
const zero = new Uint8Array ([ 0x00 ])
const encoded = Rlp . encodeBytes ( zero )
// => Uint8Array([0x00])
// Note: Single byte >= 0x80 still needs prefix
const high = new Uint8Array ([ 0x80 ])
const encoded = Rlp . encodeBytes ( high )
// => Uint8Array([0x81, 0x80])
2. Short String (0-55 bytes)
For strings of 0-55 bytes, prefix with 0x80 + length:
import { Rlp } from 'tevm'
// Empty string
const empty = Bytes ()
const encoded = Rlp . encodeBytes ( empty )
// => Uint8Array([0x80])
// 0x80 = 0x80 + 0
// 3 bytes
const bytes = new Uint8Array ([ 1 , 2 , 3 ])
const encoded = Rlp . encodeBytes ( bytes )
// => Uint8Array([0x83, 1, 2, 3])
// 0x83 = 0x80 + 3
// 55 bytes (maximum for short form)
const max = new Uint8Array ( 55 ). fill ( 0x42 )
const encoded = Rlp . encodeBytes ( max )
// => Uint8Array([0xb7, ...max])
// 0xb7 = 0x80 + 55
3. Long String (56+ bytes)
For strings of 56+ bytes, use long form: [0xb7 + length_of_length, ...length_bytes, ...bytes]
import { Rlp } from 'tevm'
// 56 bytes (minimum for long form)
const min = new Uint8Array ( 56 ). fill ( 0x42 )
const encoded = Rlp . encodeBytes ( min )
// => Uint8Array([0xb8, 56, ...min])
// 0xb8 = 0xb7 + 1 (length needs 1 byte)
// 56 = length value
// 256 bytes (length needs 2 bytes)
const large = new Uint8Array ( 256 ). fill ( 0x42 )
const encoded = Rlp . encodeBytes ( large )
// => Uint8Array([0xb9, 0x01, 0x00, ...large])
// 0xb9 = 0xb7 + 2 (length needs 2 bytes)
// [0x01, 0x00] = 256 in big-endian
// 65536 bytes (length needs 3 bytes)
const huge = new Uint8Array ( 65536 ). fill ( 0x42 )
const encoded = Rlp . encodeBytes ( huge )
// => Uint8Array([0xba, 0x01, 0x00, 0x00, ...huge])
// 0xba = 0xb7 + 3
The threshold of 56 bytes is chosen because lengths 0-55 fit in a single prefix byte (0x80-0xb7). Starting at 56, we need an additional byte to encode the length.
Usage Patterns
Transaction Field Encoding
Encode individual transaction fields:
import { Rlp } from 'tevm'
// Encode nonce (typically small number)
const nonce = new Uint8Array ([ 0x00 ])
const encodedNonce = Rlp . encodeBytes ( nonce )
// => Uint8Array([0x00]) (single byte < 0x80)
// Encode address (20 bytes)
const address = new Uint8Array ( 20 ). fill ( 0x01 )
const encodedAddress = Rlp . encodeBytes ( address )
// => Uint8Array([0x94, ...address])
// 0x94 = 0x80 + 20
// Encode signature r (32 bytes)
const r = Bytes32 (). fill ( 0x02 )
const encodedR = Rlp . encodeBytes ( r )
// => Uint8Array([0xa0, ...r])
// 0xa0 = 0x80 + 32
Contract Bytecode
Encode contract bytecode (often > 56 bytes):
import { Rlp } from 'tevm'
// Contract bytecode (example: 500 bytes)
const bytecode = new Uint8Array ( 500 ). fill ( 0x60 )
const encoded = Rlp . encodeBytes ( bytecode )
// => Uint8Array([0xb9, 0x01, 0xf4, ...bytecode])
// 0xb9 = 0xb7 + 2 (length needs 2 bytes)
// [0x01, 0xf4] = 500 in big-endian
Keccak256 Hash
Encode 32-byte hash values:
import { Rlp } from 'tevm'
import { keccak256 } from 'tevm/crypto'
const data = new Uint8Array ([ 1 , 2 , 3 , 4 ])
const hash = keccak256 ( data ) // 32 bytes
const encoded = Rlp . encodeBytes ( hash )
// => Uint8Array([0xa0, ...hash])
// 0xa0 = 0x80 + 32
Empty Data
Encode empty byte arrays:
import { Rlp } from 'tevm'
// Empty transaction data field
const empty = Bytes ()
const encoded = Rlp . encodeBytes ( empty )
// => Uint8Array([0x80])
// Use in transaction encoding
const tx = [
nonce ,
gasPrice ,
gasLimit ,
to ,
value ,
Rlp . encodeBytes ( Bytes ()), // Empty data
v ,
r ,
s
]
When to Use encodeBytes
Use encodeBytes instead of generic encode when you know the input is bytes (not a list), to skip type dispatch overhead.
import { Rlp } from 'tevm'
// Direct encoding
const encoded = Rlp . encodeBytes ( bytes )
// vs generic with dispatch overhead
const encoded = Rlp . encode ( bytes )
Pre-calculate Sizes
For repeated encoding, pre-calculate buffer sizes:
import { Rlp } from 'tevm'
// Calculate size first
function calculateEncodedSize ( bytes : Uint8Array ) : number {
if ( bytes . length === 1 && bytes [ 0 ] ! < 0x80 ) {
return 1
}
if ( bytes . length <= 55 ) {
return 1 + bytes . length
}
const lengthBytes = Math . ceil ( Math . log2 ( bytes . length + 1 ) / 8 )
return 1 + lengthBytes + bytes . length
}
const size = calculateEncodedSize ( data )
// Allocate buffer of exact size
const buffer = new Uint8Array ( size )
Batch Encoding
Encode multiple byte arrays efficiently:
import { Rlp } from 'tevm'
const items = [
new Uint8Array ([ 1 , 2 , 3 ]),
new Uint8Array ([ 4 , 5 , 6 ]),
new Uint8Array ([ 7 , 8 , 9 ])
]
// Encode all at once as list
const encoded = Rlp . encode ( items )
// vs encoding individually (less efficient)
const encoded = items . map ( item => Rlp . encodeBytes ( item ))
See Also