Alert Source Discuss
⚠️ Draft Standards Track: Core

EIP-7932: Secondary Signature Algorithms

Introduces a new transaction type and precompile for handling alternative signature algorithms

Authors James Kempton (@SirSpudlington)
Created 2025-04-12
Discussion Link https://ethereum-magicians.org/t/eip-7932-secondary-signature-algorithms/23514
Requires EIP-155, EIP-2718, EIP-7702

Abstract

This EIP does three things:

  • Creates a unified registry & standardized interface for introducing additional signature algorithms.
  • Introduces an EIP-2718 transaction type that can modify the signature data of a contained transaction to a different signature algorithm.
  • Introduces a precompile at address 0x12 for decoding these newly introduced algorithms.

Motivation

As quantum computers become more advanced, several new post-quantum (PQ) algorithms have been designed. These algorithms all have certain issues, such as large key sizes (>1KiB), large signature sizes, or long verification times. These issues make them more expensive to compute and store than the currently used secp256k1 curve.

This EIP provides a solution to the diversity in algorithms by adding a standardized way to represent alternative algorithms within a transaction.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Unless explicitly noted, integer encoding MUST be in big-endian format.

Parameters

Constant Value
MAX_ADDITIONAL_INFO 255
GAS_PER_ADDITIONAL_VERIFICATION_BYTE 16
SIGRECOVER_PRECOMPILE_ADDRESS Bytes20(0x12)
SIGRECOVER_PRECOMPILE_BASE_GAS 3000
ALGORITHMIC_TX_TYPE Bytes1(0x07)
SECP256K1_SIGNATURE_SIZE 65

Algorithm specification

Further algorithms MUST be specified via a distinct EIP.

Each type of algorithm MUST specify the following fields:

Field Name Description
ALG_TYPE The uint8 that would be present at signature_info[0]
GAS_PENALTY The additional gas penalty from verification of the signature

The GAS_PENALTY field MUST be assumed to be the worst-case scenario and MUST only account for verification costs, not storage nor signing GAS_PENALTY.

A verification function must be present per algorithm. The verification function MUST have the following signature:

def verify(signature_info: Bytes, payload_hash: Hash32) -> Bytes

The verify function MUST either return an error or return the full public key of the sender.

Specifications MUST include some form of security analysis on the provided algorithm and basic benchmarks justifying gas costs. Additionally, algorithms MUST ensure that variations of signatures cannot be made without the private key.

An example of this specification can be found here.

Deriving address from public keys

The function below MUST be used when deriving an address from a public key:


def pubkey_to_address(public_key: Bytes, algorithm_id: uint8) -> ExecutionAddress:
    if algorithm_id == 0xFF: # Compatibility shim to ensure backwards compatibility
        return ExecutionAddress(keccak(public_key[1:])[12:])

    # || is binary concatenation
    return ExecutionAddress(keccak(algorithm_id || public_key)[12:])

Algorithm Registry

This EIP uses the algorithm_registry object to signify algorithms that have been included within a hard fork.

The structure of the registry can be represented as the pythonic object dict[uint8, callable[[Bytes, Hash32], Bytes]].

A living EIP MAY be created on finalization of this EIP to track currently active algorithms across forks.

Algorithmic Transaction

The algorithmic transaction MUST NOT be present in newly created blocks after the introduction of EIP-6404

Format

A new EIP-2718 transaction is defined with a structure of:


ALGORITHMIC_TX_TYPE || rlp([
  tx_type,              # uint8
  tx_data,              # RLP encoded transaction data
  chain_id,             # uint64
  [
    additional_signature: Bytes
    ...
  ],
  signature_info,       # Bytes
])

The first byte of signature_info (signature_info[0]) represents the algorithm used to sign the transaction in the payload field. All instances of signature_info MUST be at least 1 byte for the selector.

The payload transaction MUST still contain the r, s, and y_parity fields which MUST be set to 0x0.

Signatures

The signing hash of the payload MUST be calculated with keccak256(ALGORITHMIC_TX_TYPE || rlp([tx_type, tx_signing_hash, chain_id, additional_signatures])).

tx_signing_hash is the hash specified by the respective transaction type denoted by the tx_type field.

Additional signatures

If a signature is being overridden via an additional_signatures entry, the r field MUST be set to keccak256(additional_signature) and the v and y_parity values MUST be set to 0x0. This MUST be verified during transaction validation. Each entry MUST NOT be of type secp256k1.

Each additional_signatures entry MUST be used at least once within the transaction and MUST not be duplicated. Non-used or duplicated entries will invalidate the tx. Entries MUST be sorted in ascending order (0x0 -> 0xff…ff) via uint256(keccak256(additional_signature)).

The additional_signatures field only needs to be populated if additional protocol-level signatures are required such as EIP-7702’s authorization_list.

It MUST also be checked that len(additional_signatures) <= MAX_ADDITIONAL_INFO.

Verification

The transaction signature MUST be verified using the verify function for the algorithm denoted via signature_info[0].

Processing & handling

The Algorithmic Transaction MUST generate a receipt of only the inner transaction, not of the AlgorithmicTransaction. Implementations MUST NOT be able to differentiate between an unwrapped and wrapped transaction by receipts alone.

When clients receive an Algorithmic Transaction via gossip or RPC they MUST validate both the Algorithmic Transaction and the inner transaction. If either the transaction or the inner transaction is invalid, the entire transaction is invalid.

Any discrimination of transactions MUST occur only with the inner transaction, e.g. gas prices for block ordering.

EIP-155 handling

As of 2025/11/05 the Algorithmic Transaction does not support EIP-155 handling. All wrapped legacy transactions MUST be assumed to be pre-EIP-155.

Verification

Implementations MUST check that signature_info[0] points to a known algorithm and that calling the verify function on the signature data returns a valid public key. ALL additional signatures MUST also pass this check.

If any checks fail, then the transaction containing them is invalid. If called from the precompile, the precompile MUST return bytes20(0x0). Transactions MUST also follow all rules outlined in the Transaction specification section.

Gas calculation

All transactions that use more resources than the secp256k1 curve suffer an additional penalty which MUST be calculated as specified in the calculate_penalty function.


def calculate_penalty(signature_info: bytes) -> int:
  gas_penalty_base = max(len(signature_info) - (SECP256K1_SIGNATURE_SIZE + 1), 0) * GAS_PER_ADDITIONAL_VERIFICATION_BYTE
  total_gas_penalty = gas_penalty_base + ALGORITHMS[signature_info[0]].GAS_PENALTY
  return total_gas_penalty

The penalty MUST be added to the base gas of each transaction before the transaction is processed. A penalty MUST also be added for every instance of an additional_signatures object. If the payload’s gas_limit is less than base_intrinsic_gas + penalty then the transaction MUST be invalidated.

This transaction also MUST inherit the intrinsics of the wrapped transaction’s fee structure (e.g. a wrapped EIP-1559 payload would behave as an EIP-1559 transaction).

secp256k1 algorithm

Field Value
ALG_TYPE 0xFF
GAS_PENALTY 0

def secp256k1_unpack(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]) -> tuple[uint256, uint256, uint8]:
    r = uint256.from_bytes(signature[0:32], 'big')
    s = uint256.from_bytes(signature[32:64], 'big')
    y_parity = signature[64]
    return (r, s, y_parity)

def secp256k1_validate(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]):
    SECP256K1N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
    r, s, y_parity = secp256k1_unpack(signature)
    assert 0 < r < SECP256K1N
    assert 0 < s <= SECP256K1N // 2
    assert y_parity in (0, 1)

def verify(signature_info: bytes, payload_hash: Hash32) -> Bytes:
  assert len(signature_info) == (SECP256K1_SIGNATURE_SIZE + 1)
  secp256k1_validate(signature_info[1:])

  ecdsa = ECDSA()
  recover_sig = ecdsa.ecdsa_recoverable_deserialize(signature_info[1:65], signature_info[65])
  public_key = PublicKey(ecdsa.ecdsa_recover(payload_hash, recover_sig, raw=True))
  uncompressed = public_key.serialize(compressed=False)
  return uncompressed

This algorithm MUST ONLY be used if the transaction’s signature is secp256k1 but the additional_signatures field contains one or more entries. It MUST NOT be present if the transaction does not contain additional signatures. secp256k1 MUST NOT be present in any additional_signatures entry.

sigrecover precompile

This EIP also introduces a new precompile located at SIGRECOVER_PRECOMPILE_ADDRESS.

This precompile MUST cost SIGRECOVER_PRECOMPILE_BASE_GAS when called regardless of validity. This cost MUST be aggregated with calculate_penalty(signature_info) and charged before the sigrecover precompile executes.

The precompile MUST output the 20-byte address of the signer provided. Callers MUST assume all zero bytes as a failure.

The precompile logic contains the following logic:


def sigrecover_precompile(input: Bytes) -> Bytes:
  # Recover signature, hash and type
  assert(len(input) >= 33)
  hash: Hash32 = input[:32]
  signature_info: Bytes = input[32:]
  algorithm_type = signature_info[0]
  
  # Ensure the algorithm exists
  if algorithm_type not in algorithm_registry:
    return ExecutionAddress(0x0)

  alg = algorithm_registry[algorithm_type]

  # Run verify function
  try:
    pubkey = alg.verify(signature_info, hash)
    return pubkey_to_address(pubkey, algorithm_type)
  except:
    return ExecutionAddress(0x0)
  

Rationale

Opaque signature_info type

As each algorithm has unique properties, e.g. signature recovery and key sizes, the object needs to hold every permutation of every possible signature and additional recovery information. A bytearray of a dynamic size would be able to achieve this goal. However, this leads to a DoS vector which the Gas penalties section solves.

Gas penalties

Having multiple different algorithms results in multiple different signature sizes and verification costs. Hence, every signature algorithm that is more expensive than the default ECDSA secp256k1 curve incurs an additional gas penalty. This is to discourage the use of overly expensive algorithms for no specific reason.

The GAS_PER_ADDITIONAL_VERIFICATION_BYTE value being 16 was taken from the calldata cost of a transaction, as it is a similar data type and must persist indefinitely to ensure later verification.

Not specifying account key-sharing / migration

Allowing a single account to share multiple keys creates a security risk as it reduces the security of all addresses to the weakest algorithm. This is also out of scope for this EIP and could be implemented via a future EIP.

Keeping a similar address rather than introducing a new address format

While adding a new address format for every new algorithm would ensure that collisions never happen and that security would not be bound by the lowest common denominator, it creates an excessively large burden on client and tooling developers.

New precompile over modifying the ecrecover precompile

Initially, modifying the ecrecover precompile seemed prudent over creating a new precompile. However, after an initial attempt, it proved too hacky for production use.

Hard fork over EIP‑4337 Account Abstraction

This EIP allows for EIP‑4337 Bundlers to settle UserOperations on-chain using a different algorithm. sigrecover also complements Account Abstraction well by reducing on-chain verification costs via sigrecover.

Backwards Compatibility

Non-EIP-7932 transactions will still be processed as the default secp256k1 curve. Therefore, there would be no backwards compatibility issues in processing other transactions.

Test Cases

Security Considerations

Allowing more ways to potentially sign transactions for a single account may decrease overall security for that specific account. However, this is partially mitigated by the increase in processing power required to trial all algorithms. Even still, adding additional algorithms may need further discussion to ensure that the security of the network would not be compromised.

Having signature_info be of no concrete type creates a chance that an algorithm’s decoding logic could be specified or implemented incorrectly, which could lead to, in the best case, invalid blocks or, at worst, enabling anyone to sign a fraudulent transaction for any account. This security consideration is delegated to the algorithm’s specification, and so care must be taken when writing these specifications to avoid critical security flaws.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

James Kempton (@SirSpudlington), "EIP-7932: Secondary Signature Algorithms [DRAFT]," Ethereum Improvement Proposals, no. 7932, April 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7932.