Skip to main content

Try it Live

Run RLP examples in the interactive playground

RLP Utilities

Helper methods for creating, measuring, flattening, and comparing RLP data structures.

Overview

Utility functions provide convenient ways to work with RLP data:
  • from - Create RLP data from various inputs
  • getEncodedLength - Calculate encoded size without encoding
  • flatten - Extract all bytes from nested structures
  • equals - Deep equality comparison

from

Create RLP data structure from various inputs with automatic type detection.

    Behavior

    The from method normalizes various input types into RLP data structures:
    1. Uint8Array → Creates bytes data
    2. BrandedRlp → Returns unchanged (idempotent)
    3. Array → Creates list data (doesn’t recursively convert items)
    Note: Arrays are not recursively converted:
    import { Rlp } from 'tevm'
    
    // Array items remain as-is (not converted to RLP data)
    const list = Rlp([
      new Uint8Array([1]),  // Stays as Uint8Array
      new Uint8Array([2])   // Stays as Uint8Array
    ])
    
    // To recursively convert, use encode + decode
    const encoded = Rlp.encode([
      new Uint8Array([1]),
      new Uint8Array([2])
    ])
    const decoded = Rlp.decode(encoded)
    // Now fully converted to RLP data structures
    

    Use Cases

    Normalizing Input:
    import { Rlp } from 'tevm'
    
    function processRlpData(input: Uint8Array | BrandedRlp | BrandedRlp[]) {
      // Normalize to RLP data
      const data = Rlp(input)
    
      // Now work with consistent type
      if (data.type === 'bytes') {
        console.log('Bytes length:', data.value.length)
      } else {
        console.log('List items:', data.value.length)
      }
    }
    
    Building Structures:
    import { Rlp } from 'tevm'
    
    // Build transaction structure
    const nonce = Rlp(new Uint8Array([0x00]))
    const gasPrice = Rlp(new Uint8Array([0x04, 0xa8, 0x17, 0xc8]))
    const gas = Rlp(new Uint8Array([0x52, 0x08]))
    
    const transaction = Rlp([nonce, gasPrice, gas, /* ... */])
    

    getEncodedLength

    Calculate the byte length of RLP-encoded data without actually encoding it.

      Algorithm

      Calculates encoded size using RLP rules without allocating buffers: For Uint8Array:
      // Single byte < 0x80: 1 byte (no prefix)
      if (length === 1 && byte < 0x80) return 1
      
      // Short string (< 56 bytes): 1 + length
      if (length < 56) return 1 + length
      
      // Long string: 1 + length_of_length + length
      const lengthBytes = calculateLengthOfLength(length)
      return 1 + lengthBytes + length
      
      For Arrays (lists):
      // Calculate total payload size
      const totalLength = items.reduce((sum, item) =>
        sum + getEncodedLength(item), 0
      )
      
      // Short list (< 56 bytes): 1 + totalLength
      if (totalLength < 56) return 1 + totalLength
      
      // Long list: 1 + length_of_length + totalLength
      const lengthBytes = calculateLengthOfLength(totalLength)
      return 1 + lengthBytes + totalLength
      

      Performance Benefits

      Measuring without encoding saves allocation and copying:
      import { Rlp } from 'tevm'
      
      // Inefficient: encode just to check size
      const encoded = Rlp.encode(data)
      const size = encoded.length
      
      // Efficient: measure directly
      const size = Rlp.getEncodedLength(data)
      
      Use Cases: Pre-sizing Buffers:
      import { Rlp } from 'tevm'
      
      // Calculate total size for multiple items
      const items = [data1, data2, data3]
      const totalSize = items.reduce(
        (sum, item) => sum + Rlp.getEncodedLength(item),
        0
      )
      
      // Allocate single buffer
      const buffer = new Uint8Array(totalSize)
      
      Size Validation:
      import { Rlp } from 'tevm'
      
      // Check if data will fit in block
      const MAX_BLOCK_SIZE = 1000000
      
      const size = Rlp.getEncodedLength(transactionList)
      if (size > MAX_BLOCK_SIZE) {
        throw new Error('Transaction list too large for block')
      }
      
      Choosing Encoding Strategy:
      import { Rlp } from 'tevm'
      
      // Use different strategies based on size
      const size = Rlp.getEncodedLength(data)
      
      if (size < 1000) {
        // Small data: use JS encoder
        const encoded = Rlp.encode(data)
      } else {
        // Large data: use WASM encoder
        const encoded = RlpWasm.encode(data)
      }
      

      flatten

      Recursively extract all bytes data from nested lists (depth-first traversal).

        Algorithm

        Depth-first traversal that collects all bytes data:
        function flatten(data) {
          const result = []
        
          function visit(d) {
            if (d.type === 'bytes') {
              result.push(d)
            } else {
              for (const item of d.value) {
                visit(item)
              }
            }
          }
        
          visit(data)
          return result
        }
        

        Use Cases

        Extract Transaction Fields:
        import { Rlp } from 'tevm'
        
        // Decode transaction
        const txBytes = new Uint8Array([...])
        const decoded = Rlp.decode(txBytes)
        
        // Extract all byte fields
        const fields = Rlp.flatten(decoded.data)
        // => [nonce, gasPrice, gas, to, value, data, v, r, s]
        
        // Access specific fields
        const [nonce, gasPrice, gas] = fields
        console.log('Nonce:', nonce.value)
        
        Count Total Bytes:
        import { Rlp } from 'tevm'
        
        function countTotalBytes(data: BrandedRlp): number {
          const flattened = Rlp.flatten(data)
          return flattened.reduce((sum, item) => sum + item.value.length, 0)
        }
        
        const nested = { /* ... */ }
        const total = countTotalBytes(nested)
        console.log(`Total bytes: ${total}`)
        
        Validate Structure:
        import { Rlp } from 'tevm'
        
        function validateAllBytes(data: BrandedRlp, minLength: number) {
          const flattened = Rlp.flatten(data)
        
          for (const item of flattened) {
            if (item.value.length < minLength) {
              throw new Error(`Bytes too short: ${item.value.length} < ${minLength}`)
            }
          }
        }
        
        Map Over Bytes:
        import { Rlp } from 'tevm'
        
        function hashAllBytes(data: BrandedRlp): Uint8Array[] {
          const flattened = Rlp.flatten(data)
          return flattened.map(item => keccak256(item.value))
        }
        

        equals

        Deep equality comparison for RLP data structures.

          Algorithm

          Recursive comparison with type checking:
          function equals(data, other) {
            // Different types
            if (data.type !== other.type) return false
          
            // Compare bytes
            if (data.type === 'bytes') {
              if (data.value.length !== other.value.length) return false
              for (let i = 0; i < data.value.length; i++) {
                if (data.value[i] !== other.value[i]) return false
              }
              return true
            }
          
            // Compare lists
            if (data.type === 'list') {
              if (data.value.length !== other.value.length) return false
              for (let i = 0; i < data.value.length; i++) {
                if (!equals(data.value[i], other.value[i])) return false
              }
              return true
            }
          
            return false
          }
          

          Use Cases

          Deduplication:
          import { Rlp } from 'tevm'
          
          function deduplicate(items: BrandedRlp[]): BrandedRlp[] {
            const unique: BrandedRlp[] = []
          
            for (const item of items) {
              const isDuplicate = unique.some(u => Rlp.equals(u, item))
              if (!isDuplicate) {
                unique.push(item)
              }
            }
          
            return unique
          }
          
          Testing:
          import { Rlp } from 'tevm'
          
          // Test round-trip encoding
          const original = {
            type: 'list',
            value: [
              { type: 'bytes', value: new Uint8Array([1]) }
            ]
          }
          
          const encoded = Rlp.encode(original)
          const decoded = Rlp.decode(encoded)
          
          console.log(Rlp.equals(original, decoded.data))  // true
          
          Caching:
          import { Rlp } from 'tevm'
          
          const cache = new Map<string, Uint8Array>()
          
          function encodeWithCache(data: BrandedRlp): Uint8Array {
            // Check cache
            for (const [key, cached] of cache.entries()) {
              const cachedData = JSON.parse(key)
              if (Rlp.equals(data, cachedData)) {
                return cached
              }
            }
          
            // Encode and cache
            const encoded = Rlp.encode(data)
            cache.set(JSON.stringify(data), encoded)
            return encoded
          }
          
          Assertions:
          import { Rlp } from 'tevm'
          
          function assertRlpEquals(
            actual: BrandedRlp,
            expected: BrandedRlp,
            message?: string
          ) {
            if (!Rlp.equals(actual, expected)) {
              throw new Error(message || 'RLP data not equal')
            }
          }
          
          // Usage in tests
          const result = processRlpData(input)
          assertRlpEquals(result, expectedOutput)
          

          Utility Combinations

          Combine utilities for powerful operations: Measure Flattened Size:
          import { Rlp } from 'tevm'
          
          function getTotalBytesSize(data: BrandedRlp): number {
            const flattened = Rlp.flatten(data)
            return flattened.reduce((sum, item) => sum + item.value.length, 0)
          }
          
          Compare After Normalization:
          import { Rlp } from 'tevm'
          
          function normalizeAndCompare(a: unknown, b: unknown): boolean {
            const dataA = Rlp(a)
            const dataB = Rlp(b)
            return Rlp.equals(dataA, dataB)
          }
          
          Validate Then Flatten:
          import { Rlp } from 'tevm'
          
          function extractAndValidate(data: BrandedRlp): Uint8Array[] {
            // Validate structure
            const size = Rlp.getEncodedLength(data)
            if (size > MAX_SIZE) {
              throw new Error('Data too large')
            }
          
            // Extract all bytes
            const flattened = Rlp.flatten(data)
          
            // Return raw byte arrays
            return flattened.map(item => item.value)
          }