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 Transaction examples in the interactive playground
Transaction Signing
Signature verification and sender address recovery using secp256k1.
getSender
Recover sender address from transaction signature.
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
import { getSender } from 'tevm/Transaction'
const signedTx : Transaction . EIP1559 = {
type: Transaction . Type . EIP1559 ,
chainId: 1 n ,
nonce: 0 n ,
maxPriorityFeePerGas: 1000000000 n ,
maxFeePerGas: 20000000000 n ,
gasLimit: 21000 n ,
to: recipientAddress ,
value: 1000000000000000000 n ,
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
verifySignature
Verify transaction signature is valid.
function verifySignature ( tx : Any ) : boolean
Parameters
tx: Any - Signed transaction
Returns
boolean - true if signature is valid, false otherwise
Usage
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
isSigned
Check if transaction has a signature.
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
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
assertSigned
Assert transaction is signed (throws if not).
function assertSigned ( tx : Any ) : void
Parameters
tx: Any - Transaction to check
Throws
Error("Transaction is not signed") - If r or s is zero
Usage
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
Signature Components
Legacy Transactions
Legacy transactions use v/r/s signature format:
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:
// 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:
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
Get signing hash (transaction data without signature)
Recover public key from signature using secp256k1
Hash public key with keccak256
Take last 20 bytes as address
// 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:
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
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
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
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
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
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:
import { InvalidSignatureError } from 'tevm/errors'
const SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 n
const SECP256K1_N_DIV_2 = SECP256K1_N / 2 n
// 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.
Signature recovery is expensive (elliptic curve operations):
// 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:
// 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
EIP References