> ## 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.

# Validation

> Signature validation and verification utilities

<Card title="Try it Live" icon="play" href="https://playground.tevm.sh?example=primitives/signature.ts">
  Run Signature examples in the interactive playground
</Card>

# Validation

Signature validation and canonicalization functions.

## isCanonical

Check if ECDSA signature has canonical s-value.

### Signature

```typescript theme={null}
function isCanonical(signature: BrandedSignature): boolean
```

### Parameters

* `signature` - BrandedSignature to check

### Returns

* `true` - If signature is canonical (s ≤ n/2) or Ed25519
* `false` - If signature is non-canonical (s > n/2)

### Example

```typescript theme={null}
const sig = Signature.fromSecp256k1(r, s, 27);

if (Signature.isCanonical(sig)) {
  console.log('Signature is canonical');
} else {
  console.log('Signature needs normalization');
  const canonical = Signature.normalize(sig);
}

// Ed25519 always canonical
const ed25519Sig = Signature.fromEd25519(bytes);
console.log(Signature.isCanonical(ed25519Sig)); // true
```

## Canonicality Rules

### ECDSA (secp256k1, P-256)

A signature is canonical if `s ≤ n/2`, where `n` is the curve order.

**secp256k1:**

```
n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
n/2 = 7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0
```

**P-256:**

```
n = FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
n/2 = 7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8
```

### Why Canonicality Matters

ECDSA signatures have inherent malleability: both `(r, s)` and `(r, -s mod n)` are valid for the same message and public key.

**Security Issues:**

* Transaction malleability attacks
* Signature duplication
* Replay attacks

**Standards:**

* Bitcoin BIP-62: Require canonical form
* Ethereum: Enforce low s-value
* Most modern protocols: Mandate canonicality

### Example: Non-Canonical Signature

```typescript theme={null}
// High s-value (non-canonical)
const sHigh = new Uint8Array([
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
  0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b,
  0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x40,
]); // s > n/2

const sig = Signature.fromSecp256k1(r, sHigh, 27);
console.log(Signature.isCanonical(sig)); // false

// Normalized (canonical)
const canonical = Signature.normalize(sig);
console.log(Signature.isCanonical(canonical)); // true
```

## normalize

Normalize ECDSA signature to canonical form.

### Signature

```typescript theme={null}
function normalize(signature: BrandedSignature): BrandedSignature
```

### Parameters

* `signature` - BrandedSignature to normalize

### Returns

BrandedSignature with canonical s-value (s ≤ n/2)

**If already canonical:** Returns input unchanged
**If Ed25519:** Returns input unchanged (always canonical)
**If non-canonical:** Returns new signature with `s = n - s` and flipped v

### Example

```typescript theme={null}
const sig = Signature.fromSecp256k1(r, sHigh, 27);

// Normalize to canonical form
const canonical = Signature.normalize(sig);

console.log(Signature.isCanonical(sig)); // false
console.log(Signature.isCanonical(canonical)); // true

// v flipped when normalizing
console.log(Signature.getV(sig)); // 27
console.log(Signature.getV(canonical)); // 28

// r unchanged
const rOrig = Signature.getR(sig);
const rNorm = Signature.getR(canonical);
console.log(rOrig.every((b, i) => b === rNorm[i])); // true

// s normalized
const sOrig = Signature.getS(sig);
const sNorm = Signature.getS(canonical);
console.log(sOrig.every((b, i) => b === sNorm[i])); // false
```

### Normalization Process

**For non-canonical signature (s > n/2):**

1. Calculate `s_normalized = n - s`
2. Flip recovery ID: `v_new = (v === 27) ? 28 : 27`
3. Return new signature with `(r, s_normalized, v_new)`

```typescript theme={null}
// Before normalization
const sig = Signature.fromSecp256k1(r, sHigh, 27);
// r: [original r]
// s: [high s-value]
// v: 27

// After normalization
const canonical = Signature.normalize(sig);
// r: [original r] (unchanged)
// s: [n - s] (normalized)
// v: 28 (flipped)
```

### Algorithm Details

```typescript theme={null}
// Pseudocode for normalization
function normalize(sig: BrandedSignature): BrandedSignature {
  // Ed25519 always canonical
  if (sig.algorithm === 'ed25519') {
    return sig;
  }

  // Already canonical
  if (isCanonical(sig)) {
    return sig;
  }

  // Extract components
  const r = getR(sig);
  const s = getS(sig);

  // Get curve order
  const n = getCurveOrder(sig.algorithm);

  // Calculate s_normalized = n - s (modular subtraction)
  const sNormalized = modularSubtract(n, s);

  // Flip v if present
  const v = sig.v !== undefined
    ? (sig.v === 27 ? 28 : 27)
    : undefined;

  // Return normalized signature
  return fromSecp256k1(r, sNormalized, v);
}
```

### Recovery ID Flip

When normalizing, the recovery ID must flip:

```typescript theme={null}
// Original signature with high s
const sig1 = Signature.fromSecp256k1(r, sHigh, 27);

// Normalization flips v
const sig2 = Signature.normalize(sig1);
console.log(Signature.getV(sig2)); // 28

// Double normalization returns to original v
const sig3 = Signature.normalize(sig2);
console.log(Signature.getV(sig3)); // 27

// But s is different (both canonical)
const s2 = Signature.getS(sig2);
const s3 = Signature.getS(sig3);
console.log(s2.every((b, i) => b === s3[i])); // false
```

**Reason:** Negating s changes which of the two possible public keys is correct, so recovery ID must flip.

## Use Cases

### Ethereum Transaction Signing

```typescript theme={null}
// Sign transaction
const txHash = keccak256(encodedTx);
const sig = secp256k1.sign(txHash, privateKey);

// Ensure canonical before including in transaction
const canonical = Signature.normalize(sig);

// Only canonical signatures accepted by network
const tx = {
  ...txData,
  r: Signature.getR(canonical),
  s: Signature.getS(canonical),
  v: Signature.getV(canonical),
};
```

### Bitcoin Transaction Validation

```typescript theme={null}
// Parse DER signature from transaction
const sig = Signature.fromDER(derBytes, 'secp256k1');

// Bitcoin requires canonical signatures (BIP-62)
if (!Signature.isCanonical(sig)) {
  throw new Error('Non-canonical signature rejected');
}

// Verify signature
const valid = verifySig(sig, txHash, publicKey);
```

### Signature Verification

```typescript theme={null}
// Accept both canonical and non-canonical
function verifyFlexible(
  sig: BrandedSignature,
  message: Uint8Array,
  publicKey: Uint8Array
): boolean {
  // Normalize before verification
  const canonical = Signature.normalize(sig);
  return verify(canonical, message, publicKey);
}

// Strict verification (reject non-canonical)
function verifyStrict(
  sig: BrandedSignature,
  message: Uint8Array,
  publicKey: Uint8Array
): boolean {
  if (!Signature.isCanonical(sig)) {
    throw new Error('Non-canonical signature rejected');
  }
  return verify(sig, message, publicKey);
}
```

### API Validation

```typescript theme={null}
// Validate signature before storing
function storeSignature(sig: BrandedSignature): void {
  // Normalize to canonical form
  const canonical = Signature.normalize(sig);

  // Store canonical version
  db.signatures.insert({
    r: Signature.getR(canonical),
    s: Signature.getS(canonical),
    v: Signature.getV(canonical),
  });
}

// Retrieve and verify is canonical
function getSignature(id: string): BrandedSignature {
  const data = db.signatures.findById(id);
  const sig = Signature.fromSecp256k1(data.r, data.s, data.v);

  // Assert stored signatures are canonical
  if (!Signature.isCanonical(sig)) {
    throw new Error('Database corruption: non-canonical signature');
  }

  return sig;
}
```

## Validation Patterns

### Pre-Normalization Check

```typescript theme={null}
// Check before normalizing
if (!Signature.isCanonical(sig)) {
  console.log('Signature needs normalization');
  sig = Signature.normalize(sig);
}

// Now guaranteed canonical
console.assert(Signature.isCanonical(sig));
```

### Enforce Canonical

```typescript theme={null}
function ensureCanonical(sig: BrandedSignature): BrandedSignature {
  if (!Signature.isCanonical(sig)) {
    return Signature.normalize(sig);
  }
  return sig;
}

// Always returns canonical signature
const canonical = ensureCanonical(anySig);
```

### Reject Non-Canonical

```typescript theme={null}
function requireCanonical(sig: BrandedSignature): void {
  if (!Signature.isCanonical(sig)) {
    throw new NonCanonicalSignatureError(
      'Signature must have low s-value'
    );
  }
}

// Throws if not canonical
requireCanonical(sig);
```

## Security Considerations

### Transaction Malleability

```typescript theme={null}
// Problem: Both signatures valid for same transaction
const sig1 = Signature.fromSecp256k1(r, s, 27);
const sig2 = Signature.fromSecp256k1(r, sNeg, 28); // s_neg = n - s

// Both verify correctly
verify(sig1, txHash, pubKey); // true
verify(sig2, txHash, pubKey); // true

// But produce different transaction hashes
const tx1Hash = hash(encodeTx(sig1));
const tx2Hash = hash(encodeTx(sig2));
console.log(tx1Hash !== tx2Hash); // true

// Solution: Enforce canonical signatures
const canonical = Signature.normalize(sig1);
// Only canonical signatures allowed
```

### Signature Uniqueness

```typescript theme={null}
// Without canonicalization: multiple valid signatures
function getAllValidSignatures(r, s, v): BrandedSignature[] {
  const n = CURVE_ORDER;
  const sNeg = modSubtract(n, s);
  const vFlipped = v === 27 ? 28 : 27;

  return [
    Signature.fromSecp256k1(r, s, v),
    Signature.fromSecp256k1(r, sNeg, vFlipped),
  ];
}

// With canonicalization: unique signature
function getCanonicalSignature(r, s, v): BrandedSignature {
  const sig = Signature.fromSecp256k1(r, s, v);
  return Signature.normalize(sig);
}
```

### Replay Attack Prevention

```typescript theme={null}
// Store signature hash to prevent replay
const canonical = Signature.normalize(sig);
const sigHash = keccak256(Signature.toBytes(canonical));

// Check if signature already used
if (await db.usedSignatures.exists(sigHash)) {
  throw new Error('Signature already used');
}

// Mark as used
await db.usedSignatures.insert(sigHash);
```

## Performance

### Canonicality Check

```typescript theme={null}
// Fast check: O(n) byte comparison
const isCanonical = Signature.isCanonical(sig);

// Typical performance: < 0.001ms for 32-byte comparison
```

### Normalization

```typescript theme={null}
// O(n) operation: modular subtraction
const canonical = Signature.normalize(sig);

// If already canonical: O(1) (returns input)
// If needs normalization: O(n) (creates new signature)

// Typical performance: < 0.01ms
```

### Optimization

```typescript theme={null}
// Avoid unnecessary normalization
if (Signature.isCanonical(sig)) {
  // Use sig directly
  return sig;
} else {
  // Normalize only if needed
  return Signature.normalize(sig);
}

// Or use normalize (checks internally)
return Signature.normalize(sig); // No-op if canonical
```

## Testing

```typescript theme={null}
describe('Signature Validation', () => {
  it('detects canonical signatures', () => {
    const canonical = Signature.fromSecp256k1(r, sLow, 27);
    expect(Signature.isCanonical(canonical)).toBe(true);
  });

  it('detects non-canonical signatures', () => {
    const nonCanonical = Signature.fromSecp256k1(r, sHigh, 27);
    expect(Signature.isCanonical(nonCanonical)).toBe(false);
  });

  it('normalizes non-canonical signatures', () => {
    const sig = Signature.fromSecp256k1(r, sHigh, 27);
    const canonical = Signature.normalize(sig);

    expect(Signature.isCanonical(canonical)).toBe(true);
    expect(Signature.getV(canonical)).toBe(28); // v flipped
  });

  it('preserves canonical signatures', () => {
    const sig = Signature.fromSecp256k1(r, sLow, 27);
    const result = Signature.normalize(sig);

    expect(result).toBe(sig); // Same instance
    expect(Signature.getV(result)).toBe(27); // v unchanged
  });

  it('handles Ed25519 correctly', () => {
    const sig = Signature.fromEd25519(bytes);
    expect(Signature.isCanonical(sig)).toBe(true);
    expect(Signature.normalize(sig)).toBe(sig);
  });
});
```

## See Also

* [Utilities](./utilities.mdx) - Helper functions
* [Constructors](./constructors.mdx) - Creating signatures
* [Usage Patterns](./usage-patterns.mdx) - Common patterns
* [BIP-62: Dealing with Malleability](https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki)
