Skip to content

Latest commit

 

History

History
473 lines (344 loc) · 12.6 KB

File metadata and controls

473 lines (344 loc) · 12.6 KB

Contributing to Voltaire

Thank you for your interest in contributing to Voltaire (Ethereum primitives and cryptography library)!

We welcome contributions! This document provides guidelines and instructions for contributing to the project.

AI-Assisted Contributions

We welcome and encourage AI-assisted contributions. Using Claude, Copilot, ChatGPT, or other AI tools is perfectly fine - we care about quality, not how the code was written.

⚠️ MANDATORY REQUIREMENT: You MUST include your prompts in the PR.

PRs without prompts will be rejected - no exceptions - unless:

  • The change is trivial (typo fix, single-line change)
  • You are a known trusted contributor

Required Format

  1. Include ALL prompts used to generate the code in your PR description
  2. Provide a human-written summary explaining the "why"
  3. Review and test all AI-generated code before submitting

Example PR description:

## AI Prompts Used

### Prompt 1:
"Add validation for RLP encoded data to prevent buffer overflows"

### Prompt 2:
"Write tests for the new validation logic covering edge cases"

## Summary
Adds input validation for RLP decoding. Fixes #123.

## Testing
- ✅ zig build test passes
- ✅ Added new test cases for edge conditions

If your contribution is large, please open a discussion to chat about the change before doing the work.

Critical Safety Requirements

⚠️ WARNING: This is mission-critical cryptographic infrastructure. ANY bug can compromise security or cause fund loss. Please follow these requirements:

Zero Tolerance Policy

  • NO broken builds or failing tests
  • NO stub implementations (error.NotImplemented)
  • NO commented-out code (use Git for history)
  • NO test failures - all tests must pass
  • NO swallowing errors with empty catch blocks
  • NO std.debug.print in library code (this is a library, not an app)
  • NO std.debug.assert (use proper error handling)

If in doubt, STOP and ask for guidance rather than stubbing or commenting out code.

Before You Start

  1. Read CLAUDE.md - Critical development guidelines
  2. Understand the architecture - Read README.md and see docs/
  3. Check existing issues - Your change may already be discussed
  4. Run tests - Ensure everything works before making changes

Prerequisites

Install the following dependencies:

Development Workflow

1. Setup

git clone <repository-url>
cd voltaire
git submodule update --init --recursive
bun install
zig build
zig build test

2. Make Changes

  • Follow the coding standards in CLAUDE.md
  • Write tests for all new functionality
  • Ensure memory safety (explicit ownership, defer/errDefer)
  • Use meaningful variable names
  • Add inline documentation for complex logic

3. Test Thoroughly

# Run all Zig tests
zig build test

# Run specific test filter
zig build -Dtest-filter=address

# Run all TypeScript tests (from repo root)
bun run test

# Run TypeScript tests with coverage
bun run test:coverage

# Optional umbrella step (if configured)
zig build test-all

# Quick validation
zig build check  # Format + lint + typecheck

CRITICAL: ALL tests must pass before submitting (both Zig and TypeScript if applicable).

4. Submit Pull Request

  • Write a clear PR description
  • Reference related issues
  • Disclose AI usage if applicable
  • Ensure CI passes

Coding Standards

Memory Management

// ✅ CORRECT - Explicit cleanup
const thing = try allocator.create(Thing);
defer allocator.destroy(thing);

// ❌ WRONG - Memory leak
const thing = try allocator.create(Thing);
// Missing defer!

Error Handling

// ✅ CORRECT - Explicit error propagation
pub fn doSomething() !void {
    const result = try riskyOperation();
    if (result == null) return error.InvalidResult;
}

// ❌ WRONG - Swallowing errors
pub fn doSomething() void {
    const result = riskyOperation() catch return;  // NEVER DO THIS
}

Testing

// ✅ CORRECT - Self-contained test
test "uint256 addition overflow" {
    const a = primitives.uint256.max();
    const b = primitives.uint256.fromInt(1);
    const result = a.addWithOverflow(b);
    try std.testing.expect(result.overflow);
}

// ❌ WRONG - No test for new functionality
pub fn newFeature() void {
    // ... implementation without tests
}

Cryptographic Code

Extra scrutiny required for crypto changes:

  1. Constant-time operations - Prevent timing attacks
  2. Input validation - Check all inputs before processing
  3. Test vectors - Use known test vectors from specifications
  4. Reference implementations - Cross-validate against known-good implementations
  5. Security review - Tag maintainers for review

Example:

// ✅ CORRECT - Constant time comparison
pub fn constantTimeCompare(a: []const u8, b: []const u8) bool {
    if (a.len != b.len) return false;
    var result: u8 = 0;
    for (a, b) |byte_a, byte_b| {
        result |= byte_a ^ byte_b;
    }
    return result == 0;
}

// ❌ WRONG - Leaks timing information
pub fn timingUnsafeCompare(a: []const u8, b: []const u8) bool {
    for (a, b) |byte_a, byte_b| {
        if (byte_a != byte_b) return false;  // Early exit leaks info!
    }
    return true;
}

TypeScript/JavaScript Contributions

This library provides TypeScript bindings for JavaScript environments. When contributing TypeScript code:

Pure TypeScript vs FFI

The TypeScript layer has two types of implementations:

  1. Pure TypeScript (src/primitives/, ts/src/primitives/)

    • No native dependencies
    • Works in any JavaScript environment
    • Examples: ABI encoding, numeric conversions, bytecode analysis
  2. FFI Wrappers (src/crypto/)

    • Uses Bun FFI to call native Zig library
    • Requires compiled native library
    • Examples: Keccak-256, EIP-191

TypeScript Development Workflow

# Build Zig library first (required for FFI modules)
zig build

# Install TypeScript dependencies
cd src
bun install

# Run TypeScript tests
bun test

# Run specific test file
bun test crypto/keccak.test.ts

Adding New FFI Bindings

To add a new FFI wrapper for a Zig crypto function:

  1. Add C API binding in src/c_api.zig:

    export fn primitives_my_function(input: [*]const u8, len: usize, output: [*]u8) callconv(.C) void {
        const data = input[0..len];
        const result = crypto.myFunction(data);
        @memcpy(output, &result);
    }
  2. Create TypeScript wrapper in src/crypto/my-function.ts:

    import { getCApiPath } from './utils';
    import { dlopen, FFIType } from 'bun:ffi';
    
    const lib = dlopen(getCApiPath(), {
      primitives_my_function: {
        args: [FFIType.ptr, FFIType.u64, FFIType.ptr],
        returns: FFIType.void
      }
    });
    
    export function myFunction(data: Uint8Array): Uint8Array {
      const output = new Uint8Array(32);
      lib.symbols.primitives_my_function(data, data.length, output);
      return output;
    }
  3. Add tests in src/crypto/my-function.test.ts using known test vectors

  4. Update documentation in TYPESCRIPT_API.md and README.md

TypeScript Code Standards

  • Use TypeScript strict mode (enabled in tsconfig.json)
  • Prefer Uint8Array for byte data
  • Use bigint for large integers
  • Write comprehensive tests with known test vectors
  • Document all public APIs with JSDoc comments
  • Handle errors gracefully (throw descriptive errors)

Testing TypeScript Code

// ✅ CORRECT - Use known test vectors
import { describe, test, expect } from 'bun:test';

describe('keccak256', () => {
  test('matches NIST test vector', () => {
    const input = 'abc';
    const expected = new Uint8Array([/* known hash */]);
    const result = keccak256(input);
    expect(result).toEqual(expected);
  });
});

// ❌ WRONG - No test vectors
test('keccak256 works', () => {
  const result = keccak256('hello');
  expect(result.length).toBe(32);  // Too weak!
});

Common Commands

Building

zig build                    # Full build (Zig + TS typecheck + C libs)
zig build build-ts-native    # Native FFI (.dylib/.so) - ReleaseFast
zig build build-ts-wasm      # WASM - ReleaseSmall (size-optimized)
zig build build-ts-full      # Complete TypeScript build (TS + native + WASM)
zig build crypto-wasm        # Individual crypto WASM modules (tree-shaking)

Testing

zig build test               # All Zig tests
zig build test-ts            # All TypeScript tests
zig build test-all           # All tests (Zig + TypeScript + Go)
zig build test-integration   # Integration tests
zig build test-security      # Security tests

Quality

zig build format             # Format Zig + TypeScript
zig build format-check       # Check formatting
zig build lint               # Lint TypeScript (auto-fix)
zig build lint-check         # Check linting
zig build check              # Quick validation (format + lint + typecheck)
zig build ci                 # Complete CI pipeline

Benchmarks

zig build bench              # zbench Zig benchmarks
bun run bench                # TS comparison benchmarks + generate BENCHMARKING.md

Examples

zig build example-keccak256
zig build example-abi
zig build example-secp256k1
zig build example-address
zig build example-eip712
zig build example-transaction

Documentation

bun run docs:dev             # Mintlify dev (localhost:3000)

Cleaning

zig build clean              # Clean artifacts (keep node_modules)
zig build clean-all          # Deep clean + node_modules

Fuzz Testing

Fuzz tests use Zig's built-in fuzzer (requires Linux). On macOS, use Docker.

Running Fuzz Tests

macOS (Docker Required)

# Run fuzzing for 5 minutes
docker run --rm -it -v $(pwd):/workspace -w /workspace \
  ziglang/zig:0.15.1 \
  zig build test --fuzz=300s

# With web UI
docker run --rm -it -v $(pwd):/workspace -w /workspace \
  -p 6971:6971 \
  ziglang/zig:0.15.1 \
  zig build test --fuzz --port=6971

Visit http://localhost:6971 for live coverage visualization.

Linux (Native)

zig build test --fuzz=300s       # Run for 5 minutes
zig build test --fuzz            # Run indefinitely

Fuzz Test Organization

Fuzz tests are colocated with implementations using .fuzz.zig extension:

src/primitives/Address/address.fuzz.zig
src/crypto/hash.fuzz.zig
src/precompiles/ecrecover.fuzz.zig

See .claude/commands/fuzz.md for comprehensive fuzzing documentation.

MCP Evaluation Tests

Voltaire includes an Advent-of-Code style evaluation suite that tests Claude's ability to use the Voltaire MCP server for solving Ethereum challenges.

Running MCP Evals

# Set up RPC endpoint
export ETHEREUM_RPC_URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"

# Run all MCP evaluation tests
bun run test:mcp

# Run specific test
bun run test:mcp -t "CryptoPunk"

Example Challenges

  • Find block hash where first CryptoPunk minted
  • Calculate total ETH burned via EIP-1559 in block range
  • Verify secp256k1 signatures using Voltaire primitives
  • Query NFT ownership at specific blocks

See tests/mcp-evals/README.md for detailed documentation.

Areas Looking for Contributors

We especially welcome contributions in:

  • Test Coverage - More comprehensive unit tests (both Zig and TypeScript)
  • Fuzz Testing - Additional fuzz test coverage
  • Documentation - Improving code documentation
  • Performance - Optimization opportunities (with benchmarks)
  • Bug Fixes - Addressing any issues
  • Platform Support - Testing on different platforms
  • TypeScript FFI Bindings - Adding C API wrappers for remaining crypto functions (EIP-712, secp256k1, hash algorithms)
  • TypeScript Primitives - Pure TypeScript implementations of additional Ethereum primitives

Questions?

  • Open a discussion for architecture questions
  • Comment on relevant issues for feature discussions
  • Tag maintainers for security-sensitive changes

Code Review Process

  1. Automated checks - CI must pass
  2. Manual review - Maintainer review required
  3. Testing verification - Confirm tests are comprehensive
  4. Security review - Extra scrutiny for crypto code
  5. Merge - Squash and merge with clear commit message

License

By contributing, you agree that your contributions will be licensed under the same license as the project.


Thank you for helping make Voltaire more robust and secure! 🙏