This EIP introduces a new EIP-2718 typed transaction that wraps (contains) another transaction, this EIP nullifies the default signature parameters and appends signature data to the front of the transaction with a selector, this effectively wraps a transaction and swaps out signature data for alternative algorithms and data. It also creates a new precompile to be able to decode these additional signature algorithms.
As quantum computers are getting more advanced, several new post-quantum (PQ) algorithms have been designed. These algorithms all contain drawbacks 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 current secp256k1 curve in use (as of 2025-04-12).
This EIP provides a future-proof solution to these algorithms by adding a standardized way to represent alternative algorithms within a transaction.
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.
Note: The "verification function" referred to by this EIP is the one specified by additional EIP & their signature types. When calling explicitly the "verification function" implementations MUST NOT call the precompile due to differing gas costs.
Constant | Value |
---|---|
ALG_TX_TYPE |
Bytes1(0x07) |
GAS_PER_ADDITIONAL_VERIFICATION_BYTE |
16 |
SIGRECOVER_PRECOMPILE_ADDRESS |
Bytes20(0x12) |
SIGRECOVER_PRECOMPILE_BASE_GAS |
3000 |
This EIP introduces a new EIP-2718 transaction with a TransactionType
of ALG_TX_TYPE
and a TransactionPayload
of the RLP serialization of the AlgTransactionPayloadBody
defined below:
[alg_type, signature_info, parent, additional_info]
The field alg_type
is an unsigned 8-bit integer (uint8) that represents the algorithm used to sign the transaction in the parent
field. This EIP does not define algorithms for use with this transaction type; however, it does specify a NULL algorithm (0xFF) which MUST trigger implementations to verify transaction fields.
The signature_info
field contains information required to verify the signature of the transaction in the parent
field. This is a byte-array of arbitrary length, which would be passed to the verification function.
The parent
field contains another serialized EIP-2718 Typed Transaction Envelope (i.e. the parent
field type is bytes
), which MUST be able to contain every possible TransactionType
, including legacy transactions with a TransactionType
of > 0x7f
, but the only exception to this rule is the Algorithmic Transaction
itself, which MUST NOT be placed within itself.
These parent
transactions all contain y_parity
, r
, s
values, which MUST be set to Bytes0()
if wrapped in a AlgTransactionPayloadBody
, there are two exceptions to this rule however:
alg_type
is the NULL algorithm, the signature field MUST be left unchanged.y_parity
MUST be equal to the chain ID value, the transactions signing data MUST also be calculated the same way as EIP-155 specifiesAll other transaction values MUST be unchanged from their original values.
The additional_info
field only needs to be populated if additional protocol level signatures are required such as EIP-7702's authorization_list
. This field MUST contain the RLP serialization of a list of the signatures [alg_type, signature_info]
and MUST be repeated for every NON-NULL signature inside the transaction. The order of which MUST be the same as the signatures appear in the parent
tx. This signature MUST also be checked to ensure that alg_type
is known and is not the NULL algorithm (0xFF), len(signature_info) <= alg.MAX_SIZE
and then MUST also be verified using the verify
function for the specific algorithm.
If new transaction types are specified they MUST NOT attempt to build on this EIP but instead MUST include the y_parity
, r
, s
values, this will prevent backwards compatibility issues and ensure that any transaction other than EIP-7932 txs can be safely assumed to be secp256k1.
The Algorithmic Transaction MUST NOT generate a transaction receipt with a TransactionType
of ALG_TX_TYPE
, it MUST emit the receipt of the transaction it is wrapping (the tx in the parent
field). 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 transaction in the parent
field, any ordering (e.g. based on gas price) MUST be done on the transaction in the parent
field. If either transaction is invalid they MUST NOT propagate the transaction to peers.
This is an example wrapped EIP-1559 transaction where alg_type == 0x5
ALG_TX_TYPE || rlp([0x5, b"signing-data", 0x02 || rlp([0x1, 0x1, 0x1, 0x1, 21000, 0x0, 0x1, 0x, [], 0x0, 0x0, 0x0])])
The parent
field serializes to 0x02ce01010101825208800180c0808080
, therefore the above transaction serializes to 0x07df058c7369676e696e672d646174619002ce01010101825208800180c0808080
Further algorithms MUST be specified via an additional EIP.
Each type of algorithm MUST specify the following fields:
| Field Name | Description |
|-|-|
|ALG_TYPE
| The uint8 of the algorithm unique ID |
|MAX_SIZE
| The maximum size of signature_info
field in a transaction |
|GAS_PENALTY
| The additional gas penalty from verification of the signature |
The GAS_PENALTY
field MUST only account for verification costs, not storage nor signing.
New algorithms MUST also specify how to recover and verify a valid address (bytes20
) from the signature_info
field inside the transaction, the verification function MUST follow the following signature:
def verify(signature_info: bytes, parent_hash: bytes32) -> bytes20
The verify function MUST return 0x0
if there was an error recovering a valid address from the signature, otherwise the function MUST return the address of the signer.
Specifications MUST also justify why their GAS_PENALTY
is high enough to not cause a DOS vector, and MUST include some form of security analysis on the algorithm and that it will not cause potential security issues.
An example of this specification can be found here.
This EIP uses the Algorithms
object to signify final and active algorithms. These are selected via the EIP process and hard-fork inclusion.
Implementations MUST consider transactions invalid where len(tx.signature_info) > alg.MAX_SIZE
, this also applies for objects inside additional_info
.
The following checks MUST always be made against any type of signature_info
and alg_type
. If the result of these checks do not pass then one of the following apply:
assert(Algorithms[alg_type] != None) # `Algorithms` is a dictionary containing every defined algorithm
assert(len(signature_info) <= alg.MAX_SIZE)
The validity/processing of the transaction should be processed similarly to the following function:
def process_transaction(tx: Transaction, from_address: bytes20 = None, start_gas = 21000, additional_info: List[Callable[[bytes32], bytes20]]):
match tx:
# Verification for other transactions, if `from_address != None`
# then the verifier MUST NOT attempt to validate the `y_parity`, `r`, `s`
# parameters. Additionally, the verification function MUST start the initial gas value
# at `start_gas`. Every further signature where all parameters are null MUST instead use
# `additional_info` field corresponding to to which they appeared (i.e. the first signature to be null is at additional_info[0]).
# If the transaction is a legacy transaction and `from_address != None`, implementations
# MUST ensure that `y_parity == chainid`.
...
ALG_TX:
assert(from_address == None) # Ensure no double-wrapping
assert(Algorithms[alg_type] != None)
alg = Algorithms[alg_type]
assert(len(signature_info) <= alg.MAX_SIZE)
from_address = None
if alg_type != 0xFF: # NULL TX
valid, from_address = alg.verify(tx.signature_info, calculate_signing_hash(wrapped), chain_id) # calculate_signing_hash is defined within the wrapped transaction's EIP.
assert(valid)
# This is only an example, a method like this should not be used in production, instead the signature info should be passed
# into the processing function.
additional_info_verifiers = []
for additional_info in tx[3:]:
additional_info_verifiers.append(lambda sig_hash: Algorithms[additional_info.alg_type].verify(additional_info.signature_info, calculate_signing_hash(sig_hash)))
process_transaction(tx.parent, from_address, 21000 + calculate_penalty(tx.signature_info, alg))
All transactions that use more resources than the secp256k1 curve suffer an additional penalty. This penalty MUST be calculated as follows:
def calculate_penalty(signing_data: bytes, algorithm: int) -> int:
gas_penalty_base = max(len(signing_data) - 65, 0) * GAS_PER_ADDITIONAL_VERIFICATION_BYTE
total_gas_penalty = gas_penalty_base + ALGORITHMS[algorithm].GAS_PENALTY
return total_gas_penalty
The penalty MUST be added onto the 21000
base gas of each transaction BEFORE the transaction is processed. If the wrapped tx's gas_limit
is less then 21000 + calculate_penalty(signing_data, algorithm)
than the transaction MUST be considered invalid and MUST NOT be included within blocks. This transaction also MUST inherit the intrinsics of the wrapped tx's fee structure (e.g. a wrapped EIP-1559 tx would behave as a EIP-1559 tx).
The penalty MUST be applied to every instance of an additional_info
object, which then MUST be added onto the transaction's initial gas.
sigrecover
precompileThis EIP also introduces a new precompile located at SIGRECOVER_PRECOMPILE_ADDRESS
.
This precompile MUST NOT be called when verifying transaction parameters, instead this call should be verified as specified above.
This precompile MUST cost SIGRECOVER_PRECOMPILE_BASE_GAS
when calling even if algorithm does not exist, this price MUST be aggregated with algorithm specific GAS_PENALTY
and the output of calculate_penalty
for the signature data, this penalty is charged once the sigrecover
precompile executes.
The precompile logic executes the following logic:
def sigrecover_precompile(input: Bytes) -> Bytes:
# Recover signature length and type
assert(len(input) >= 64)
hash = input[:32]
alg_type = input[32]
sig_length = int.from_bytes(input[33:64], "little")
# Ensure the algorithm exists and signature is correct size
if alg_type not in Algorithms:
return bytes20(0x0)
alg = Algorithms[alg_type]
if sig_length > alg.MAX_SIZE:
return bytes20(0x0)
# Run verify function
return alg.verify(input[64:64 + sig_length], hash)
The NULL algorithm (0xFF) MUST be present if the signature parameters inside the wrapped transaction are still valid for secp256k1
, but there are more parameters in the transaction such as EIP-7702's authorization_list
that require swapping out for different algorithms.
The signature_info
field MUST be zero-sized if the NULL algorithm is not present.
The NULL algorithm MUST NOT be present if the transaction does not contain additional signatures.
y_parity
, r
, s
values to zero rather than removing themKeeping the y_parity
, r
, s
values inside the transactions keeps the previous parsing, verification and processing logic the same and allows for minimal modification to the other specifications while still preventing excessive space usage.
signature_info
typeAs each algorithm has unique properties, i.e. signature recovery and key sizes, a object is needed to hold every permutation of every possible key and signature. A bytearray of dynamic size would be able to achieve this goal, this does lead to a DoS vector which the Gas penalties section solves along with the MAX_SIZE
parameter.
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 datatype and must persist indefinitely to ensure later verification.
secp256k1
curveHaving a type for secp256k1
allows for several iterations of the same object to be present across the network. Additionally the only purpose for this curve would be for prototyping and testing with client teams, as the resultant receipt, logs and state change would be the same as a non-wrapped transaction.
Allowing a single account to share multiple keys creates a security risk as it reduces the security to the weakest algorithm. This is also out of scope for this EIP and could be implemented via a future EIP.
While adding a new address format for every new algorithm would ensure that collisions never happen and that security is not bound by the lowest common denominator, the amount of changes that would have to be made and backwards compatibility issues would be too vast to warrant this.
ecrecover
precompileInitially, modifying the ecrecover
precompile was going to be selected over creating a new precompile, however, this was ruled out after it took too much complexity to implement or may break backwards compatibility.
This EIP allows for EIP‑4337 Bundler
s to settle UserOperation
s onchain using a different algorithm which in the future may be the only option if post-quantum issues cause the secp256k1 curve to be phased out.
Non-EIP-7932 transactions will still be included within blocks and will be treated as the default secp256k1 curve. Therefore there would be no backwards compatibility issues will processing other transactions. However, as a new EIP-2718 transaction has been added non-upgraded clients would not be able to process these transactions nor blocks that include these transactions.
These test cases do not involve processing other types of transactions. Only the wrapping, unwrapping and verification of these transactions without interfacing with the parent
tx held inside the main tx.
All the following test cases use the parameters from the example eip specified in the Algorithm Specification Section listed above.
Allowing more ways to potentially create 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 discussing 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 algorithms logic could be specified or implemented incorrectly, which could lead to, in the best case, invalid blocks, or at worst, the ability for anyone to sign a fraudulent transaction for any account. This security consideration is delegated to the algorithms specification, therefore care must be taken when writing these algorithm specifications to avoid critical security flaws.
Copyright and related rights waived via CC0.