Skip to main content

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