Skip to content

Add EIP: Algorithmic Transaction Wrapper #9633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d0a2c78
Add EIP: Multi-algorithm signing support
SirSpudlington Apr 12, 2025
1bfc3f3
Fix grammar
SirSpudlington Apr 12, 2025
4c275e6
Refactor title and desc
SirSpudlington Apr 12, 2025
88c7e85
Fix typos and reformat
SirSpudlington Apr 12, 2025
45f8cdb
Fix typos and reformat
SirSpudlington Apr 12, 2025
26cfed7
Implement @jochem-brouwer 's feedback
SirSpudlington Apr 12, 2025
d6e2650
Remove default and add example template
SirSpudlington Apr 12, 2025
8ac2404
Add test case message
SirSpudlington Apr 12, 2025
514bf6a
Add discussions-to link
SirSpudlington Apr 13, 2025
6aa44b6
Add future proofing information
SirSpudlington Apr 14, 2025
8e4e2f5
Add additional gas message
SirSpudlington Apr 14, 2025
f0cbf89
Specify how this transaction interfaces with existing fee structures
SirSpudlington Apr 14, 2025
f9fc06d
Apply suggestions from code review
SirSpudlington Apr 15, 2025
7b37cbe
Update draft-eip-multi-algorithm-pki.md
SamWilsn Apr 15, 2025
e9991dd
Rename draft-eip-multi-algorithm-pki.md to eip-7932.md
SamWilsn Apr 15, 2025
764ff3e
Commit to merge
SirSpudlington Apr 15, 2025
723624d
Merge branch 'Add-eip-multi-algorithm-pki' of https://github.com/JKin…
SirSpudlington Apr 15, 2025
78ccb3a
Modifications to `ecrecover` and `additional_info` for other signatures
SirSpudlington Apr 15, 2025
757b779
Fix linting issues
SirSpudlington Apr 15, 2025
c27a639
Add additional condition
SirSpudlington Apr 15, 2025
05d9d8c
Add `NULL algorithm` for edge cases such as eip-7702 tx's where the `…
SirSpudlington Apr 16, 2025
ccc94a5
Retrigger checks
SirSpudlington Apr 17, 2025
0672749
Fix linking issues
SirSpudlington Apr 17, 2025
43733e8
Small tweaks for `chain_id` values.
SirSpudlington Apr 17, 2025
a44023f
Add new precompile and remove redundant boolean
SirSpudlington Apr 18, 2025
53eb0b7
Small tweaks
SirSpudlington Apr 18, 2025
e3d3a5c
Improve clarity & denote edge cases
SirSpudlington Apr 18, 2025
11ff0dd
Linting & grammar
SirSpudlington Apr 18, 2025
9771654
More modifications and clarifications based on @g11tech feedback
SirSpudlington Apr 22, 2025
63da070
Fix typos
SirSpudlington Apr 29, 2025
cac067e
Update to new Magicians link
SirSpudlington May 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 264 additions & 0 deletions EIPS/eip-7932.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
---
eip: 7932
title: Algorithmic Transaction Wrapper
description: Introduces a new transaction type wrapping transactions in alternative signature algorithms
author: James Kempton (@SirSpudlington)
discussions-to: https://ethereum-magicians.org/t/eip-7932-algorithmic-transaction-wrapper/23514
status: Draft
type: Standards Track
category: Core
created: 2025-04-12
requires: 155, 1559, 2718, 2930, 4844, 7702
---

## Abstract

This EIP introduces a new [EIP-2718](./eip-2718.md) 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.

## Motivation

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.

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

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.

### Parameters

| Constant | Value |
| - | - |
| `ALG_TX_TYPE` | `Bytes1(0x07)` |
| `GAS_PER_ADDITIONAL_VERIFICATION_BYTE`| `16` |
| `SIGRECOVER_PRECOMPILE_ADDRESS`| `Bytes20(0x12)` <!-- TODO: Ensure that this is not already in use --> |
| `SIGRECOVER_PRECOMPILE_BASE_GAS` | `3000` |

### Algorithmic Transaction

This EIP introduces a new [EIP-2718](./eip-2718.md) 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](./eip-2718.md) 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:

- If the `alg_type` is the [NULL algorithm](#null-algorithm), the signature field MUST be left unchanged.
- If the transaction type is that of a legacy transaction, where `y_parity` MUST be equal to the chain ID value, the transactions signing data MUST also be calculated the same way as [EIP-155](./eip-155.md) specifies

All 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](./eip-7702.md)'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.

#### Example transaction

This is an example wrapped [EIP-1559](./eip-1559.md) 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`

### Algorithm specification

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](../assets/eip-7932/template-eip.md).

This EIP uses the `Algorithms` object to signify final and active algorithms. These are selected via the EIP process and hard-fork inclusion.

### Verification

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:

1. The transaction is invalid
2. If called from the precompile, the precompile MUST return 0x0 as the address.

```python
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:

```python
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))
```

### Gas calculation

All transactions that use more resources than the secp256k1 curve suffer an additional penalty. This penalty MUST be calculated as follows:

```python
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` precompile

This 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:

```python

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)

```

### NULL algorithm

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](./eip-7702.md)'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.

## Rationale

### Setting `y_parity`, `r`, `s` values to zero rather than removing them

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

### Opaque `signature_info` type

As 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](#gas-penalties) section solves along with the `MAX_SIZE` parameter.

### 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 datatype and must persist indefinitely to ensure later verification.

### Not adding the `secp256k1` curve

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

### Not specifying account key-sharing / migration

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.

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

### New precompile over modifying the `ecrecover` precompile

Initially, 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.

### Hard fork over [EIP‑4337](./eip-4337.md) Account Abstraction

This EIP allows for [EIP‑4337](./eip-4337.md) `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.

## Backwards Compatibility

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](./eip-2718.md) transaction has been added non-upgraded clients would not be able to process these transactions nor blocks that include these transactions.

## Test Cases

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](../assets/eip-7932/template-eip.md) listed above.

<!-- TODO, must be done before EIP enter review stage. -->

Check warning on line 252 in EIPS/eip-7932.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn`

warning[markdown-html-comments]: HTML comments are only allowed while `status` is one of: `Draft`, `Withdrawn` --> EIPS/eip-7932.md | 252 | <!-- TODO, must be done before EIP enter review stage. --> | ::: EIPS/eip-7932.md | 260 | <!-- TODO: Further security considerations need discussion. --> | = help: see https://ethereum.github.io/eipw/markdown-html-comments/

## Security Considerations

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.

<!-- TODO: Further security considerations need discussion. -->

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
50 changes: 50 additions & 0 deletions assets/eip-7932/template-eip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: Example EIP to add secp256k1 curve as an algorithmic type
description: Example EIP to add secp256k1 curve as an algorithmic type
Author: ExampleAuthor
discussions-to: fakeurl
status: Draft
type: Standards Track
category: Core
created: 2025-04-12
requires: 7932
---

## Abstract
This example EIP adds secp256k1 curve as an algorithmic type.

## Motivation
secp256k1 is the commonly used curve, therefore it should be added.

## Specification

This EIP defines a new [EIP-7932](../../EIPS/eip-7932.md) algorithmic type with the following parameters.

| Constant | Value |
| - | - |
| `ALG_TYPE` | `Bytes1(0x0)` |
| `GAS_PENALTY`| `0` |
| `MAX_SIZE` | `65` |

```python
def verify(signature_info: bytes, parent_hash: bytes32) -> bytes20:
assert(len(signature_info) == 96)
r, s, v = signature_info[0:32], signature_info[32:64], signature_info[64:]

# This assumes `ecrecover` is identical to the `ecrecover` function in solidity.
signer = ecrecover(parent_hash, v, r, s)

return signer
```

## Rationale
secp256k1 is the commonly used curve, therefore it should be added.

## Backwards Compatibility
No backward compatibility issues found.

## Security Considerations
Needs discussion.

## Copyright
Copyright and related rights waived via [CC0](../../LICENSE.md).