Skip to main content

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 Authorization examples in the interactive playground

Validation

Authorization structure and signature validation.

validate

Validate authorization structure and signature parameters.
Authorization.validate.call(auth: Authorization.Item): void
Parameters:
  • auth: Authorization to validate
Throws: Authorization.ValidationError if invalid Validation Checks:
  1. Chain ID must be non-zero
  2. Address cannot be zero address
  3. yParity must be 0 or 1
  4. Signature r must be non-zero
  5. Signature s must be non-zero
  6. r must be < SECP256K1_N (curve order)
  7. s must be ≤ SECP256K1_HALF_N (prevents malleable signatures)

Basic Usage

import { Authorization } from 'tevm';

const auth: Authorization.Item = {
  chainId: 1n,
  address: contractAddress,
  nonce: 0n,
  yParity: 0,
  r: 0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefn,
  s: 0x0edcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210n
};

try {
  Authorization.validate.call(auth);
  console.log('Authorization is valid');
} catch (e) {
  if (e instanceof Authorization.ValidationError) {
    console.error(`Invalid: ${e.message}`);
  }
}

Validation Errors

ValidationError

class ValidationError extends Error {
  name: 'ValidationError';
}
Error Messages:
  • "Chain ID must be non-zero" - chainId is 0
  • "Address cannot be zero address" - address is 0x0000…0000
  • "yParity must be 0 or 1" - yParity not 0 or 1
  • "Signature r cannot be zero" - r is 0
  • "Signature s cannot be zero" - s is 0
  • "Signature r must be less than curve order" - r >= SECP256K1_N
  • "Signature s too high (malleable signature)" - s > SECP256K1_HALF_N

Validation Rules

Chain ID

Chain ID must be non-zero to indicate which chain authorization is valid on.
// Valid
const auth = { chainId: 1n, ... };  // Mainnet
const auth = { chainId: 137n, ... };  // Polygon

// Invalid
const auth = { chainId: 0n, ... };  // Throws ValidationError
Rationale: Zero chain ID is reserved and indicates invalid authorization.

Zero Address

Address cannot be zero address (0x0000…0000).
// Valid
const auth = {
  address: Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2'),
  ...
};

// Invalid
const auth = {
  address: Address.zero(),  // Throws ValidationError
  ...
};
Rationale: Zero address has no code and delegation would be meaningless.

yParity

yParity must be 0 or 1 (ECDSA signature parity bit).
// Valid
const auth = { yParity: 0, ... };
const auth = { yParity: 1, ... };

// Invalid
const auth = { yParity: 2, ... };  // Throws ValidationError
const auth = { yParity: 27, ... };  // Throws ValidationError (use 0/1, not 27/28)
Rationale: ECDSA signatures have two possible y-coordinates (even/odd). yParity indicates which.

Signature r

Signature r must be non-zero and less than curve order.
// Valid
const auth = {
  r: 0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefn,
  ...
};

// Invalid
const auth = { r: 0n, ... };  // Throws ValidationError

// Invalid (r >= SECP256K1_N)
const auth = {
  r: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
  ...
};  // Throws ValidationError
Rationale: r is ECDSA signature component and must be in valid range [1, N-1].

Signature s

Signature s must be non-zero and at most SECP256K1_HALF_N.
// Valid (s <= N/2)
const auth = {
  s: 0x0edcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210n,
  ...
};

// Invalid (s = 0)
const auth = { s: 0n, ... };  // Throws ValidationError

// Invalid (s > N/2 - malleable)
const auth = {
  s: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n,
  ...
};  // Throws ValidationError
Rationale: Restricting s ≤ N/2 prevents signature malleability. For any valid signature (r, s), there exists another valid signature (r, -s mod N). Requiring s ≤ N/2 ensures only one canonical signature.

Malleability Prevention

What is Signature Malleability?

ECDSA signatures have inherent malleability: given valid signature (r, s), signature (r, N - s) is also valid for same message. Without malleability protection:
// Both signatures are valid for same authorization
const sig1 = { r, s: 0x123n, v: 0 };
const sig2 = { r, s: SECP256K1_N - 0x123n, v: 1 };

Protection

validate() rejects high s values:
import { Authorization } from 'tevm';

const auth = {
  chainId: 1n,
  address: contractAddress,
  nonce: 0n,
  yParity: 1,
  r: 0x123n,
  s: 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364000n  // High s
};

// Throws: "Signature s too high (malleable signature)"
Authorization.validate.call(auth);

Normalizing High s

If you receive signature with high s, normalize it:
function normalizeSignature(r: bigint, s: bigint, v: number): {
  r: bigint;
  s: bigint;
  v: number;
} {
  const SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
  const SECP256K1_HALF_N = SECP256K1_N >> 1n;

  if (s > SECP256K1_HALF_N) {
    return {
      r,
      s: SECP256K1_N - s,
      v: v === 0 ? 1 : 0  // Flip parity
    };
  }

  return { r, s, v };
}

Validation Patterns

Validate Before Processing

Always validate before any operations:
import { Authorization } from 'tevm';

function processAuthorization(auth: Authorization.Item): void {
  // Validate first
  Authorization.validate.call(auth);

  // Then process
  const authority = Authorization.verify.call(auth);
  const gas = Authorization.getGasCost.call(auth, false);

  console.log(`Authority: ${authority}, Gas: ${gas}`);
}

Batch Validation

Validate entire authorization list:
import { Authorization } from 'tevm';

function validateAuthList(authList: Authorization.Item[]): {
  valid: Authorization.Item[];
  invalid: Array<{ auth: Authorization.Item; error: string }>;
} {
  const valid: Authorization.Item[] = [];
  const invalid: Array<{ auth: Authorization.Item; error: string }> = [];

  for (const auth of authList) {
    try {
      Authorization.validate.call(auth);
      valid.push(auth);
    } catch (e) {
      if (e instanceof Authorization.ValidationError) {
        invalid.push({ auth, error: e.message });
      } else {
        invalid.push({ auth, error: String(e) });
      }
    }
  }

  return { valid, invalid };
}

const { valid, invalid } = validateAuthList(authList);
console.log(`Valid: ${valid.length}, Invalid: ${invalid.length}`);

Early Validation

Validate as soon as authorization is received:
import { Authorization } from 'tevm';

class AuthorizationQueue {
  private queue: Authorization.Item[] = [];

  add(auth: Authorization.Item): void {
    // Validate immediately
    Authorization.validate.call(auth);

    // Only add valid authorizations
    this.queue.push(auth);
  }

  processAll(): void {
    // All items in queue are pre-validated
    for (const auth of this.queue) {
      this.process(auth);
    }
  }

  private process(auth: Authorization.Item): void {
    // No need to validate again
    const authority = Authorization.verify.call(auth);
    console.log(`Processing: ${authority}`);
  }
}

Validation with Type Guards

Combine type guards with validation:
import { Authorization } from 'tevm';

function validateUnknown(data: unknown): Authorization.Item {
  // First check type
  if (!Authorization.isItem(data)) {
    throw new Error('Not an authorization item');
  }

  // Then validate structure
  Authorization.validate.call(data);

  return data;
}

const auth = validateUnknown(apiResponse);

Error Handling

Handle specific validation errors:
import { Authorization } from 'tevm';

function validateWithHandling(auth: Authorization.Item): boolean {
  try {
    Authorization.validate.call(auth);
    return true;
  } catch (e) {
    if (!(e instanceof Authorization.ValidationError)) {
      throw e;  // Re-throw non-validation errors
    }

    // Handle specific errors
    if (e.message.includes('Chain ID')) {
      console.error('Invalid chain ID');
    } else if (e.message.includes('Address')) {
      console.error('Invalid address');
    } else if (e.message.includes('malleable')) {
      console.error('Signature malleability detected');
    } else {
      console.error(`Validation failed: ${e.message}`);
    }

    return false;
  }
}

Performance

Validation Cost

Validation is O(1) with constant-time checks:
// All checks are constant time
- chainId !== 0n          // O(1)
- address.every()         // O(20) = O(1)
- yParity === 0 || 1      // O(1)
- r !== 0n                // O(1)
- s !== 0n                // O(1)
- r < SECP256K1_N         // O(1)
- s <= SECP256K1_HALF_N   // O(1)

Optimization

Validation is already optimized. For batch validation, consider:
// Sequential validation
for (const auth of authList) {
  Authorization.validate.call(auth);
}

// Stop on first error
function validateAll(authList: Authorization.Item[]): void {
  for (const auth of authList) {
    Authorization.validate.call(auth);  // Throws on first error
  }
}

// Collect all errors
function validateAllWithErrors(authList: Authorization.Item[]): string[] {
  const errors: string[] = [];
  for (const auth of authList) {
    try {
      Authorization.validate.call(auth);
    } catch (e) {
      if (e instanceof Authorization.ValidationError) {
        errors.push(e.message);
      }
    }
  }
  return errors;
}

Testing

Valid Authorization

import { Authorization, Address } from 'tevm';

const validAuth: Authorization.Item = {
  chainId: 1n,
  address: Address('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2'),
  nonce: 0n,
  yParity: 0,
  r: 0x123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefn,
  s: 0x0edcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210n
};

// Should not throw
Authorization.validate.call(validAuth);

Invalid Cases

import { Authorization } from 'tevm';

// Zero chain ID
expect(() => {
  Authorization.validate.call({ ...validAuth, chainId: 0n });
}).toThrow('Chain ID must be non-zero');

// Zero address
expect(() => {
  Authorization.validate.call({ ...validAuth, address: Address.zero() });
}).toThrow('Address cannot be zero address');

// Invalid yParity
expect(() => {
  Authorization.validate.call({ ...validAuth, yParity: 2 });
}).toThrow('yParity must be 0 or 1');

// Zero r
expect(() => {
  Authorization.validate.call({ ...validAuth, r: 0n });
}).toThrow('Signature r cannot be zero');

// Zero s
expect(() => {
  Authorization.validate.call({ ...validAuth, s: 0n });
}).toThrow('Signature s cannot be zero');

// High s (malleable)
expect(() => {
  Authorization.validate.call({
    ...validAuth,
    s: Authorization.SECP256K1_HALF_N + 1n
  });
}).toThrow('Signature s too high (malleable signature)');

See Also