Skip to main content
Compares two CallData instances for bytewise equality. Uses constant-time comparison to prevent timing attacks.

Signature

function equals(a: CallDataType, b: CallDataType): boolean

Parameters

  • a - First CallData instance
  • b - Second CallData instance

Returns

boolean - true if instances are bytewise identical, false otherwise

Examples

import { CallData } from '@tevm/voltaire';

const calldata1 = CallData("0xa9059cbb...");
const calldata2 = CallData("0xa9059cbb...");
const calldata3 = CallData("0x095ea7b3...");

console.log(CallData.equals(calldata1, calldata2)); // true
console.log(CallData.equals(calldata1, calldata3)); // false

Constant-Time Comparison

Uses constant-time comparison to prevent timing attacks:
// Pseudocode (actual implementation)
function equals(a: CallDataType, b: CallDataType): boolean {
  // Early length check (not timing-sensitive)
  if (a.length !== b.length) {
    return false;
  }

  // Constant-time: Always checks all bytes
  let result = 0;
  for (let i = 0; i < a.length; i++) {
    result |= a[i] ^ b[i];
  }

  return result === 0;
}
Why constant-time?
  • Prevents timing side-channel attacks
  • Attacker can’t learn where bytes differ
  • Standard practice for cryptographic comparisons
Note: Length check is not constant-time (not security-sensitive).

Use Cases

Transaction Matching

import { CallData, type CallDataType } from '@tevm/voltaire';

function findTransaction(
  target: CallDataType,
  transactions: Transaction[]
): Transaction | undefined {
  return transactions.find(tx =>
    CallData.equals(CallData(tx.data), target)
  );
}

Replay Detection

import { CallData, type CallDataType } from '@tevm/voltaire';

class ReplayDetector {
  private seen = new Set<string>();

  hasBeenSeen(calldata: CallDataType): boolean {
    const key = CallData.toHex(calldata);

    if (this.seen.has(key)) {
      return true;
    }

    this.seen.add(key);
    return false;
  }

  // Alternative: Direct comparison (no string conversion)
  hasBeenSeenDirect(calldata: CallDataType, history: CallDataType[]): boolean {
    return history.some(seen => CallData.equals(seen, calldata));
  }
}

Cache Management

import { CallData, type CallDataType } from '@tevm/voltaire';

interface CacheEntry {
  calldata: CallDataType;
  result: unknown;
  timestamp: number;
}

class CallDataCache {
  private entries: CacheEntry[] = [];

  get(calldata: CallDataType): unknown | undefined {
    const entry = this.entries.find(e =>
      CallData.equals(e.calldata, calldata)
    );

    if (entry) {
      console.log("Cache hit");
      return entry.result;
    }

    console.log("Cache miss");
    return undefined;
  }

  set(calldata: CallDataType, result: unknown): void {
    this.entries.push({
      calldata,
      result,
      timestamp: Date.now(),
    });
  }
}

Unit Testing

import { CallData, Abi } from '@tevm/voltaire';
import { describe, it, expect } from 'vitest';

describe('CallData encoding', () => {
  it('produces consistent output', () => {
    const calldata1 = abi.transfer.encode(recipient, amount);
    const calldata2 = abi.transfer.encode(recipient, amount);

    // Use equals for comparison
    expect(CallData.equals(calldata1, calldata2)).toBe(true);
  });

  it('detects differences', () => {
    const calldata1 = abi.transfer.encode(recipient1, amount);
    const calldata2 = abi.transfer.encode(recipient2, amount);

    expect(CallData.equals(calldata1, calldata2)).toBe(false);
  });
});

Performance

Constant-time comparison is fast:
// Benchmark: 1M iterations
const calldata1 = CallData("0xa9059cbb...");  // 68 bytes
const calldata2 = CallData("0xa9059cbb...");  // Identical

console.time("equals (same)");
for (let i = 0; i < 1_000_000; i++) {
  CallData.equals(calldata1, calldata2);
}
console.timeEnd("equals (same)");
// ~85ms

// Different calldata
const calldata3 = CallData("0x095ea7b3...");

console.time("equals (different)");
for (let i = 0; i < 1_000_000; i++) {
  CallData.equals(calldata1, calldata3);
}
console.timeEnd("equals (different)");
// ~85ms (same time - constant-time!)
Comparison time independent of where bytes differ.

Comparison Methods

import { CallData } from '@tevm/voltaire';

// Direct byte comparison
const same = CallData.equals(calldata1, calldata2);
Advantages:
  • Constant-time (secure)
  • No allocation
  • Fast
Use for:
  • Direct comparison
  • Security-sensitive code
  • Performance-critical paths

Edge Cases

import { CallData } from '@tevm/voltaire';

const short = CallData("0xa9059cbb");
const long = CallData("0xa9059cbb00000000");

console.log(CallData.equals(short, long)); // false
Different lengths always return false (fast path).

Type Safety

Requires CallData instances (not plain Uint8Array):
import { CallData, type CallDataType } from '@tevm/voltaire';

const bytes1 = new Uint8Array([0xa9, 0x05, 0x9c, 0xbb]);
const bytes2 = new Uint8Array([0xa9, 0x05, 0x9c, 0xbb]);

// Type error: Uint8Array not assignable to CallDataType
CallData.equals(bytes1, bytes2); // ❌

// Correct: Convert to CallData first
const calldata1 = CallData.fromBytes(bytes1);
const calldata2 = CallData.fromBytes(bytes2);
CallData.equals(calldata1, calldata2); // ✅
  • hasSelector - Compare selectors only
  • is - Type guard check
  • toHex - Convert for string comparison
  • toBytes - Access raw bytes