Skip to content
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

Initial commit for Signature Aggregation RIP-7560 transaction subtypes #2

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Changes from 3 commits
Commits
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
306 changes: 306 additions & 0 deletions RIPS/rip-9998.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
---
eip: 0
title: Aggregation for Native Account Abstraction
description: Aggregation allows Smart Contract Accounts to share a single signature data between multiple AA transactions.
author: Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn)
discussions-to: https://www.google.com/
status: Draft
type: Standards Track
category: Core
created: 2023-09-01
requires: 7560
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we remove validation/execution separation from 7560 and move it to PR3 than this RIP probably also requires that.

---

## Abstract

RIP-7560 defined a native way to achieve Account Abstraction on Ethereum. However, one big limitation remains:
each transaction must carry its own `signature` or other form of validation input in order to be included.

We propose an extension to the RIP-7560 that introduces a validation frame that is shared by multiple transactions.

This addition follows the design of [ERC-4337](./eip-4337) signature aggregation and will enable
Ethereum transactions to natively support sharing validation inputs for the first time.

## Motivation

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On L2s the cost of transaction is proportional to the size it requires to store on L1.
There are attempts to compress transactions. This compression works better when compressing multiple transactions together.
However, signatures is the one thing that can't be compressed at all.
Compression can reduce the calldata size of a transaction to below 32 bytes, and thus signature becomes a large burden on the storage data in use.
With signature aggregation, a single signature is used on-chain for a large # of transaction, greatly reduce the calldata overhead for each transaction.


Using validation schemes that allow signature aggregation enables significant optimisations and savings on
gas for execution and transaction data cost. This is especially relevant in the context of rollups that publish data on
the Ethereum mainnet.

Another reason to support aggregation for transactions is snark aggregation.
Some signature schemes, such as the ones relying on `secp256r1`, are expensive to verify.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may no longer be a good example, with the growing adoption of RIP-7212 (secp256r1 precompile)

An aggregated zero-knowledge proof that the signatures of a large number of transactions were verified
may be cheaper to verify on-chain than verifying each of them separately.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another motivation, that we may or may not want to mention here, is the ability to perform some validation on a set of transactions. For example, AtomicAggregator doesn't actually aggregate signatures (each account still validates its own) but enforces a relation between the transactions.

This could be a powerful tool for intents, CoW swaps, etc. but it makes "aggregation" a bit of a misnomer - it's a hack that uses the aggregation subtype for a different use case.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a strange beast. it seems completely unrelated to the account. you require a "protocol" (e.g in account signature) for a wallet to sign its "agreement" for the userop placement.
current definition is that the account leaves all is signature checking to that external entity, and this is a use-case where the account checks itself.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yoavw , do you still thikn we should mention the AtomicAggregator here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a strange beast and not related to signature aggregation (or account validation). The account would be validating as usual, just returning an aggregator for a different purpose. But I think it's a valid use case that could enhance intent protocols, for which there seems to be a lot of demand. An intent solver could benefit from being able to atomically bundle a set of transactions. And it doesn't require changing anything in the RIP - just renaming aggregator to something more inclusive. Intent protocols will probably use it that way even if it's called aggregator.

## Specification

### Transaction version AA_TX_TYPE_AGGREGATED_SIG with aggregated signature data

A new special version of RIP-7560 Transaction called `AA_TX_TYPE_AGGREGATED_SIG` is introduced to carry
the aggregated signature data:

```

0x04 || 0x01 || rlp([chainId, transactionCount, aggregator, aggregatedSignature, aggregatedSigValidationGasLimit, accessList])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember where we landed on the discussion regarding having gas fees and a signature for this transaction type. In this form it doesn't, so it will only be created by the builder. This precludes use cases where an aggregated batch comes from a separate entity such as an intent solver that uses AtomicAggregator to combine transactions from multiple users as part of the same intent.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how AtomicAggregator can create it: this tx type is not signed by itself, and doesn't pay for itself: It is thus can only be generated by the same bundler putting in all other UserOps into this block.


```

All on-chain computations that take place during the aggregated signature validation happen within the context
of this transaction.

### Calculation of Transaction Type AA_TX_TYPE_AGGREGATED_SIG hash

```

keccak256(0x04 || 0x01 || rlp(transaction_payload)

```

### Bundles of aggregated transactions

As aggregated transactions share their signatures, they form a set of transactions that are only valid together and in
a given order. We refer to this set of transactions as "aggregated bundle".

Upon inclusion in a block, each bundle consists of the single "Aggregator validation transaction" and an arbitrary
number of individual aggregated AA transactions.

There may be an arbitrary number of bundles in an Ethereum block, using same or different `aggregator` addresses.

### Aggregator validation transaction

The aggregator validation transaction must run before any of the transactions authorized by this `aggregator`.

If the execution of this transaction results in a revert the block is INVALID.

We define the following Solidity method and the `aggregator` of the transaction is invoked with a corresponding data:

```solidity

function validateSignatures(uint256 version, bytes[] aggregatedTransactions, bytes aggregatedSignature) external view;
Copy link

@drortirosh drortirosh Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a kind of method that is hard to be replaced with EOF section.
I has 2-d input (array of bytes), which requires opionated encoding.
OTOH, there is less chance of "signature collision", since it is only in the aggregator itself, not account or paymaster.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, encoding is needed. See our current discussion re SSZ. We still don't need a method for it. Just another EOF "entrypoint" which will be called with an array of SSZ-encoded transactions and the aggregated sig.


```

The `aggregatedTransactions` parameter is interpreted as an array of `TransactionType4` structs.

It contains an array of all transactions within a bundle that are verified by the `aggregator`.
The transactions' individual `signature` parameters are replaced with the `signatureReplacement` as described below.

The entire amount of gas consumed by this transaction is referred to as `gasUsedByAggregator`.

### Individual transaction validity

We define the following Solidity method in the `aggregator` contract defined in the transaction:

```solidity

function validateTransactionSignature(uint256 version, bytes transaction) external view returns (bytes signatureReplacement);

```

The `transaction` parameter is interpreted as a `TransactionType4` struct.

This function is only ever executed in a view mode to the `aggregator` and it reverts if the signature validation fails.
The gas limit of this view-only frame can be set to the block gas limit.

Individual transactions are validated for the purposes of mempool propagation and block building by running the
validation steps in the following order:

* `sender` deployment frame (once per account)
* `sender` validation frame (required)
* `paymaster` validation frame (optional)
* `aggregator` individual signature validation frame (required)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the above call to validateTransactionSignature (but only if sender validation frame returns this aggregator.


When block builder is running the 'aggregator individual signature validation frame', it MUST use this frame's
return value as `signatureReplacement` and replace the `signature` transaction parameter with `signatureReplacement`
when the transaction is being included in the block.

This step allows the `aggregator` to provide any necessary context or metadata to the `sender` validation step without

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate "Rationale for xxx" section should be extracted form the "spec"

having to provide the entire signature that has been verified by the `aggregator` already.

### Generating aggregated signatures

It is the block builder's job to combine an array of transaction signatures into a single aggregated signature.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this paragraph is misleading, and I'm not sure what it does here.
first, account declares its designed aggregator (return it in validationData)

I think we should reverse the rules:
We define aggregator contract SHOULD expose aggregateSignatures, so any bundler can use it.
It MAY stop using it, only if it knows all block-builders support a native code to perform the same operation.

Not all signature schemes have solidity implementations for aggregation, therefore not all block producers will be able
to include all kinds of aggregated transactions.

The `aggregator` contract MAY include an implementation for the aggregation by accepting the following view call:

```solidity

function aggregateSignatures(uint256 version, bytes transactions) external returns (bytes aggregatedSignature);

```

The `transactions` parameter is interpreted as an array of `TransactionType4` structs.

This call returns a value used as `aggregatedSignature` elsewhere. Aggregator MUST revert this call if it is unable to
calculate this value.

Note that the mempool MUST only include aggregated AA transactions for `aggregators` that have an implementation
of `aggregateSignatures` function.

A block builder MAY calculate this value using any alternative way.
If the aggregator does not implement `aggregateSignatures` function it is up to block builder to add
support for this `aggregator` and its signature scheme.

### Execution layer validity of a block with aggregated transactions included

The `validateAccountAbstractionTransaction` function's logic is not modified compared to RIP-7560.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except for retSender having to include the aggregator's address.


On the execution layer, the block transactions validity conditions are extended as follows:

```

if type(tx) is AccountAbstractionTransaction {
if (tx.version == AA_AGGREGATED_SIG_TX_VERSION){
aggregatedTxs := collectTransactions(tx.transactionCount)
aggregatedSignature := aggregateSig(aggregatedTxs)
aggregatorInput := ABI.encodeWithSelector('validateSignatures()', aggregatedTxs, aggregatedSignature)
gasUsedByAggregator := evm.Call(
from: AA_ENTRY_POINT,
to: tx.aggreagor,
input: aggregatorInput,
gas: config.aa_aggregated_sig_validation_gas_limit)
else if (tx.version == AA_AGGREGATED_TX_VERSION){
// NOTE: we subtract the share of aggregated tx signature valdiation gas used from the validation gas limit
tx.validationGasLimit = tx.validationGasLimit - (gasUsedByAggregator / tx.transactionCount)
validateAccountAbstractionTransaction(tx)
}
}
}

// note: make sure all transactions are accounted for
assert all_type_x_version_1_transactions_validated()

// note: 'aggregated signature data' transactions do not pay their own gas
// make sure no such transaction is left 'hanging' in a block without paying 'child' transactions
assert all_type_x_version_1_transactions_gas_paid_for()

```

### Passing cost savings to users via gas cost adjustments

The RIP-7560 charges the signature validation cost as part of a transaction gas usage.
It also treats transaction `signature` parameter as an arbitrary length byte arrays input. Its cost is calculated in the
same way the transaction `data` cost is calculated for previous transaction types.

With signature aggregation, however, the individual transactions no longer carry their own signature. They also share
the cost of validating the aggregated signature.

Therefore, the gas cost of the aggregated validation transaction is evenly divided by the number of transactions it
validates and is added to each aggregated transaction's gas cost.
Comment on lines +196 to +197
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This incentivizes the builder to make aggregated batches small (e.g. batches of 1 transaction). It's a zero-sum game - the more the user pays, the more the builder earns. And in the current model the user has no say in this.

The alternative is that the builder (or whoever sends the AA_TX_TYPE_AGGREGATED_SIG transaction) pays for aggregation directly, and gets compensated by the user transaction, e.g. via builderFee (if it's the builder) or via the transaction itself (if it's something like an intent solver). But even then, there's still information asymmetry that works against the user. The builder knows how many pending transactions it has for an aggregator, but the user doesn't. If the user sets builderFee too low for the current number of pending transactions, the transaction gets delayed until it is worthwhile to include it. If the user sets it too high, the savings on big aggregated batches go to the builder rather than the user.

We still need to figure out the incentives model for aggregation in a way that doesn't bring back pre-1559 inefficiencies.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we started the gas incentive for aggregation elsewhere on TG, so not starting the discussion again here.


This amount of consumed gas counts towards the `validationGasLimit` of the transaction, meaning that each
transaction's validation cost includes `gasUsedByAggregator / transactionCount`.
This value is referred to as `aggregatedValidationGasUsed`.

### Sender validation frame

The `AA_TX_TYPE_AGGREGATED_SIG` type transaction includes a parameter `transactionCount`.
This value indicates a number of transactions whose validation has been aggregated.

For AA transactions marked as "aggregated" by the `AA_TX_TYPE_AGGREGATED_SIG` type transaction,
the validation method `validateTransaction` return value is interpreted as:

```solidity

abi.encodePacked(aggregator, MAGIC_VALUE_SENDER, validBefore, validAfter)

```

The block including this transaction is only valid if the `aggregator` address returned by the `validateTransaction`
matches the `aggregator` address specified in the `AA_TX_TYPE_AGGREGATED_SIG` transaction.

### RPC methods (eth namespace)

#### `eth_aggregationFeeHistory`

We propose the RPC method `eth_aggregationFeeHistory` to be created.
This method behaves similarly to `eth_feeHistory` method used to estimate [EIP-1559](./eip-1559.md) gas fees,
but instead provides history of a per-transaction validation costs,
`aggregatedValidationGasUsed` and `builderFee`, for a given aggregator.
The values are calculated as an average across all transactions.

Value of `null` indicates there was no bundle with the given `aggregator` in the given block.

Input: `aggregator address`, `block count`

Returns:

```json
{
"oldestBlock": "100000",
"newestBlock": "100003",
"aggregatedValidationGasUsed": [
"450000",
null,
"400000",
"500000"
],
"builderFee": [
"0x2386f26fc10000",
null,
"0x2386f26fc10000",
"0x2386f26fc10000"
]
}
```

#### `eth_estimateGas`

In case an `aggregator` address is returned by the `validateTransaction` by the `sender`, relies on a
`eth_aggregationFeeHistory` logic in order to calculate sufficient `validationGasLimit` and `builderFee` values.

Returns `{validationGasLimit, callGasLimit, builderFee}` object.

#### `eth_getTransactionReceipt`

Accepts the Transaction Type `AA_TX_TYPE_AGGREGATED_SIG` hash.

This is a special kind of transaction with `from` field set to the `coinbase` and `to` field set to `aggregator`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any special reason to put "from:coinbase" ? this is something that can't be simulated reliably. maybe its better to set it to ENTRYPOINT ?


## Rationale

### Fixed transaction order in bundle

The `aggregator` contract maintains control over the number and order of transactions inside the "bundle".
This means that some of the strategies block builders apply in order to extract MEV from a block become
harder to do or even impossible.

However, allowing the block builder to arbitrarily reorder transactions in the block, mixing aggregated and regular
transactions, would make this RIP impractically complex while the MEV extraction was never an intended feature
in the first place.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block builders are free to reorder by splitting aggregated batches (even if they're for the same aggregator). They need to include multiple AA_TX_TYPE_AGGREGATED_SIG transactions for that aggregator in this case, but can control the order. Therefore the same strategies are still possible (except if something like AtomicAggregator is used to explicitly prevent them).

That's another reason I think the builder should be paying for AA_TX_TYPE_AGGREGATED_SIG transactions rather than splitting their cost among the aggregated transactions. If the builder chooses to make the batches smaller to control the order, the builder should be the one paying for the additional blockspace and work it causes.


## Backwards Compatibility

This EIP creates a new distinct version of RIP-7560 transactions and does not break backwards-compatibility with the
original specification.

Same paymasters and factories contracts can be used with signature aggregation.
Sender contracts need to opt in to signature aggregation explicitly by providing the `aggregator` address
as a returned data from the `validateTransaction` function.

## Security Considerations

### Bundle size determination

The proposed design leaves the freedom to determine the size of the "bundle" of aggregated AA transactions
to the block builder. However, accounts retain some control over the "bundle" size as well by setting the
`validationGasLimit` parameter, which may affect the minimal size of the bundle that works for this transaction.

This may create opposing forces where bundlers are occasionally incentivized to split one "bundle" into two
which pays more priority fee in total. This is somewhat mitigated by the `eth_estimateGas` relying on historic averages
to calculate the best possible `validationGasLimit` for the transaction.

### Malicious aggregators

The `aggregator` contracts are among te most trusted contracts in the entire ecosystem.
They can authorize transactions on behalf of accounts, and they can invalidate large numbers of transactions with
a simple storage change.

Both account developers and block builders should be extremely careful with the selection of `aggregator` contracts
that they are willing to support.

## Copyright

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