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

# Transaction Signing

> Signature verification and sender recovery

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

# Transaction Signing

Signature verification and sender address recovery using secp256k1.

## getSender

Recover sender address from transaction signature.

```typescript theme={null}
function getSender(tx: Any): AddressType
```

### Parameters

* `tx: Any` - Signed transaction (any type)

### Returns

`AddressType` - 20-byte sender address recovered from signature

### Throws

* `Error("Transaction is not signed")` - If transaction has zero r or s
* `Error("Unknown transaction type")` - If transaction type is invalid
* `Error("Not implemented")` - If type-specific recovery not implemented yet
* Signature recovery errors for invalid signatures

### Usage

```typescript theme={null}
import { getSender } from 'tevm/Transaction'

const signedTx: Transaction.EIP1559 = {
  type: Transaction.Type.EIP1559,
  chainId: 1n,
  nonce: 0n,
  maxPriorityFeePerGas: 1000000000n,
  maxFeePerGas: 20000000000n,
  gasLimit: 21000n,
  to: recipientAddress,
  value: 1000000000000000000n,
  data: new Uint8Array(),
  accessList: [],
  yParity: 0,
  r: signatureR,
  s: signatureS,
}

// Recover sender
const sender = getSender(signedTx)
console.log('From:', Address.toChecksummed(sender))
// "0x742d35Cc6634C0532925a3b844Bc9e7595f51e3e"
```

Source: [getSender.ts:12-27](https://github.com/evmts/voltaire/blob/main/src/primitives/Transaction/getSender.ts#L12-L27)

## verifySignature

Verify transaction signature is valid.

```typescript theme={null}
function verifySignature(tx: Any): boolean
```

### Parameters

* `tx: Any` - Signed transaction

### Returns

`boolean` - `true` if signature is valid, `false` otherwise

### Usage

```typescript theme={null}
import { verifySignature } from 'tevm/Transaction'

const tx: Transaction.EIP1559 = { /* signed transaction */ }

if (verifySignature(tx)) {
  console.log('Valid signature')
  // Safe to process transaction
} else {
  console.log('Invalid signature')
  // Reject transaction
}
```

Source: [verifySignature.ts](https://github.com/evmts/voltaire/blob/main/src/primitives/Transaction/verifySignature.ts)

## isSigned

Check if transaction has a signature.

```typescript theme={null}
function isSigned(tx: Any): boolean
```

### Parameters

* `tx: Any` - Transaction to check

### Returns

`boolean` - `true` if transaction has non-zero r and s, `false` otherwise

### Usage

```typescript theme={null}
import { isSigned, getSender } from 'tevm/Transaction'

const tx: Transaction.EIP1559 = { /* ... */ }

if (isSigned(tx)) {
  const sender = getSender(tx)
  console.log('Signed by:', sender)
} else {
  console.log('Unsigned transaction')
}
```

Source: [isSigned.ts:7-14](https://github.com/evmts/voltaire/blob/main/src/primitives/Transaction/isSigned.ts#L7-L14)

## assertSigned

Assert transaction is signed (throws if not).

```typescript theme={null}
function assertSigned(tx: Any): void
```

### Parameters

* `tx: Any` - Transaction to check

### Throws

* `Error("Transaction is not signed")` - If r or s is zero

### Usage

```typescript theme={null}
import { assertSigned, getSender } from 'tevm/Transaction'

try {
  assertSigned(tx)
  // Safe to proceed - transaction is signed
  const sender = getSender(tx)
  console.log('From:', sender)
} catch (e) {
  console.error('Transaction not signed:', e.message)
}
```

Source: [assertSigned.ts:6-13](https://github.com/evmts/voltaire/blob/main/src/primitives/Transaction/assertSigned.ts#L6-L13)

## Signature Components

### Legacy Transactions

Legacy transactions use `v/r/s` signature format:

```typescript theme={null}
type Legacy = {
  v: bigint    // Recovery ID + chain ID (EIP-155)
  r: Uint8Array  // 32 bytes
  s: Uint8Array  // 32 bytes
}
```

**v value calculation:**

* Pre-EIP-155: `v = 27 + yParity` (yParity is 0 or 1)
* Post-EIP-155: `v = chainId * 2 + 35 + yParity`

**Example:**

```typescript theme={null}
// Chain ID 1, yParity 0
v = 1 * 2 + 35 + 0 = 37

// Chain ID 1, yParity 1
v = 1 * 2 + 35 + 1 = 38

// Chain ID 137 (Polygon), yParity 0
v = 137 * 2 + 35 + 0 = 309
```

### Typed Transactions (EIP-2930+)

All typed transactions use `yParity/r/s` format:

```typescript theme={null}
type EIP1559 = {
  yParity: number  // 0 or 1
  r: Uint8Array    // 32 bytes
  s: Uint8Array    // 32 bytes
}
```

**yParity** directly encodes the recovery ID (0 or 1), no chain ID encoding needed.

## Sender Recovery Process

1. **Get signing hash** (transaction data without signature)
2. **Recover public key** from signature using secp256k1
3. **Hash public key** with keccak256
4. **Take last 20 bytes** as address

```typescript theme={null}
// Pseudocode
function getSender(tx: Transaction) {
  // 1. Get hash that was signed
  const signingHash = getSigningHash(tx)

  // 2. Recover public key (65 bytes uncompressed)
  const publicKey = secp256k1.recover(
    signingHash,
    tx.r,
    tx.s,
    tx.yParity || getYParity(tx.v)
  )

  // 3. Hash public key
  const publicKeyHash = keccak256(publicKey.slice(1))  // Skip 0x04 prefix

  // 4. Take last 20 bytes
  return publicKeyHash.slice(12) as AddressType
}
```

## Type-Specific Methods

Each transaction type has specialized methods:

```typescript theme={null}
import { Legacy, EIP1559 } from 'tevm/Transaction'

// Legacy
const legacySender = Legacy.getSender.call(legacyTx)
const legacyValid = Legacy.verifySignature.call(legacyTx)

// EIP-1559
const eip1559Sender = EIP1559.getSender(eip1559Tx)
const eip1559Valid = EIP1559.verifySignature(eip1559Tx)
```

## Usage Patterns

### Transaction Pool Validation

```typescript theme={null}
import { verifySignature, getSender } from 'tevm/Transaction'
import { InvalidSignatureError, TransactionError } from 'tevm/errors'

class TransactionPool {
  async add(tx: Transaction.Any): Promise<void> {
    // Verify signature
    if (!verifySignature(tx)) {
      throw new InvalidSignatureError('Invalid signature', {
        context: { tx }
      })
    }

    // Get sender
    const sender = getSender(tx)

    // Check sender balance
    const balance = await getBalance(sender)
    const cost = tx.gasLimit * getGasPrice(tx) + tx.value
    if (balance < cost) {
      throw new TransactionError('Insufficient balance', {
        code: 'INSUFFICIENT_BALANCE',
        context: { balance, cost, sender }
      })
    }

    // Add to pool
    this.transactions.set(hash(tx), { tx, sender })
  }
}
```

### Authorization Check

```typescript theme={null}
import { getSender } from 'tevm/Transaction'
import { InvalidSignerError } from 'tevm/errors'

function requireSender(tx: Transaction.Any, expectedSender: AddressType) {
  const actualSender = getSender(tx)

  if (!Address.equals(actualSender, expectedSender)) {
    throw new InvalidSignerError(
      `Unauthorized: expected ${Address.toHex(expectedSender)}, ` +
      `got ${Address.toHex(actualSender)}`,
      {
        code: 'UNAUTHORIZED_SIGNER',
        context: { expected: expectedSender, actual: actualSender }
      }
    )
  }
}
```

### Replay Protection

```typescript theme={null}
import { getSender, getChainId, verifySignature } from 'tevm/Transaction'
import { InvalidSignatureError, TransactionError } from 'tevm/errors'

function validateTransaction(tx: Transaction.Any, currentChainId: bigint) {
  // Verify signature
  if (!verifySignature(tx)) {
    throw new InvalidSignatureError('Invalid signature', {
      context: { tx }
    })
  }

  // Check chain ID (replay protection)
  const txChainId = getChainId(tx)
  if (txChainId && txChainId !== currentChainId) {
    throw new TransactionError(`Wrong chain: expected ${currentChainId}, got ${txChainId}`, {
      code: 'WRONG_CHAIN_ID',
      context: { expected: currentChainId, actual: txChainId }
    })
  }

  // Get sender
  const sender = getSender(tx)
  return sender
}
```

### Batch Verification

```typescript theme={null}
import { verifySignature } from 'tevm/Transaction'

async function verifyBatch(transactions: Transaction.Any[]): Promise<boolean[]> {
  // Parallelize signature verification
  return Promise.all(
    transactions.map(tx => Promise.resolve(verifySignature(tx)))
  )
}

// Usage
const txs = [tx1, tx2, tx3]
const results = await verifyBatch(txs)

txs.forEach((tx, i) => {
  if (!results[i]) {
    console.error('Invalid signature:', hash(tx))
  }
})
```

### Safe Sender Recovery

```typescript theme={null}
import { isSigned, getSender } from 'tevm/Transaction'

function safGetSender(tx: Transaction.Any): AddressType | null {
  try {
    if (!isSigned(tx)) {
      return null
    }
    return getSender(tx)
  } catch (error) {
    console.error('Sender recovery failed:', error)
    return null
  }
}
```

## Signature Malleability

ECDSA signatures have malleability issue - for every valid signature `(r, s)`, there's another valid signature `(r, -s mod n)`.

Ethereum requires `s` value to be in lower half of curve order:

```typescript theme={null}
import { InvalidSignatureError } from 'tevm/errors'

const SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n
const SECP256K1_N_DIV_2 = SECP256K1_N / 2n

// Valid signatures must have s <= N/2
if (s > SECP256K1_N_DIV_2) {
  throw new InvalidSignatureError('Invalid signature: s value too high', {
    code: 'S_VALUE_TOO_HIGH',
    context: { s, maxS: SECP256K1_N_DIV_2 }
  })
}
```

This is checked automatically in signature verification.

## Performance Considerations

Signature recovery is expensive (elliptic curve operations):

```typescript theme={null}
// Cache sender addresses
const senderCache = new WeakMap<Transaction.Any, AddressType>()

function getCachedSender(tx: Transaction.Any): AddressType {
  let sender = senderCache.get(tx)
  if (!sender) {
    sender = getSender(tx)
    senderCache.set(tx, sender)
  }
  return sender
}
```

For batch processing:

```typescript theme={null}
// Parallelize if using WebAssembly or worker threads
const senders = await Promise.all(
  transactions.map(tx => Promise.resolve(getSender(tx)))
)
```

## Implementation Status

| Type     | getSender | verifySignature | Status      |
| -------- | --------- | --------------- | ----------- |
| Legacy   | Partial   | Partial         | In progress |
| EIP-2930 | Partial   | Partial         | In progress |
| EIP-1559 | Partial   | Partial         | In progress |
| EIP-4844 | Partial   | Partial         | In progress |
| EIP-7702 | Partial   | Partial         | In progress |

Many methods currently throw "Not implemented" - check test files for implementation status.

## See Also

* [Hashing](/primitives/transaction/hashing) - Transaction and signing hash computation
* [Serialization](/primitives/transaction/serialization) - RLP encode and decode transactions
* [Secp256k1](/crypto/secp256k1) - ECDSA signature operations
* [Address](/primitives/address) - Ethereum address primitive

## EIP References

* [EIP-155: Simple replay attack protection](https://eips.ethereum.org/EIPS/eip-155)
* [EIP-2718: Typed Transaction Envelope](https://eips.ethereum.org/EIPS/eip-2718)
* [Yellow Paper: ECDSA signature](https://ethereum.github.io/yellowpaper/paper.pdf)
* [secp256k1 curve parameters](https://en.bitcoin.it/wiki/Secp256k1)
