Skip to content

Conversation

spalladino
Copy link
Contributor

No description provided.

@spalladino spalladino force-pushed the palla/optimistic-attestation-signatures branch 4 times, most recently from 0938b8c to c83a23b Compare June 30, 2025 22:30

It's unclear to me whether this may lead to situations where proposers purposefully omit attestations for a block, knowing that this gets "patched" in the following one. This doesn't seem to be the case if the attestation committee refuses to sign off N+1 given the lack of attestations on L1 for N, but I still wanted to flag it.

The open question remains on whether L2 nodes should accept blocks N and N+1 in the example above, or wait until their epoch gets proven. For simplicity, I'd push for only accepting such blocks once they get proven.
Copy link
Contributor

Choose a reason for hiding this comment

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

Rephrasing the discussion here as "should proposers build on top of (potentially valid) blocks that have no attestations?"

This is equivalent to - borrowing your terminology - taking off the training wheels provided by the attestations. If we don't trust our proving system, wouldn't we then impose the requirement that attestations are posted? If we don't, then the equilibria is that no one posts any attestations except for the last proposer who must post them (or just make them available to the prover - depending on design).

If this is an acceptable outcome then probably better to remove attestations altogether.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rephrasing the discussion here as "should proposers build on top of (potentially valid) blocks that have no attestations?"

I agree that the answer to that discussion should be "no". But we still need to consider what should a node do if, for whatever reason, this happens (since it is a valid state in which the system can be).

Choose a reason for hiding this comment

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

You could allow the proposer N+1 to collect "prune" votes from the validator committee if you are worried about providing a KZG inclusion proof for the signatures?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@The-CTra1n I assume you're referring here to moving attestations to blobs, which is a separate discussion? Moving stuff to blobs increases complexity, yes, not just for providing the KZG proof but also because it changes our blob layout, which impacts other circuits. Still, if we decided to go down that route, I think that the KZG proof is easier than collecting votes.

Choose a reason for hiding this comment

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

Oh, I just thought this was for general discussion on the big doc you wrote. My bad.

Ok so your current angle is posting the sigs to calldata, not blobs, but not verifying. Seems like a non negligible cost given we would expect the unhappy case of reverifying the sigs to almost never get called. You have better line of sight on the circuit changes necessary though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You have better line of sight on the circuit changes necessary though

I don't, I'll have to bring this up with @iAmMichaelConnor heh

@spalladino spalladino force-pushed the palla/optimistic-attestation-signatures branch from c83a23b to 8b27682 Compare July 1, 2025 12:56
@spalladino spalladino force-pushed the palla/optimistic-attestation-signatures branch from 8b27682 to 0432a0e Compare July 1, 2025 13:29

### `submitProof`

In addition to verifying the rollup validity proof, `submitProof` also needs to check the validity of attestations in the last block in the epoch.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

@spalladino spalladino Jul 1, 2025

Choose a reason for hiding this comment

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

See "Proving" and "How do provers verify the attestations from the last block in the epoch" above

let committeeCommitment = getCommitteeCommitmentAtSlot(slotNumber)

let digest = block.proposalDigest
let recoveredCommittee = [ecrecover(attestation, digest) if attestation is signature else attestation for attestation in attestations]
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't follow the if attestation is signature else attestation. What is an attestation if not a signature?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've renamed it to attestationsOrAddresses. In its current implementation, the attestations array at position i contains either the signature from committee[i], or the address from committee[i] (if committee member i did not attest). This means that the correctness of attestation is checked by hashing together all committee addresses as provided via the attestationsOrAddresses argument, and comparing against the stored committee commitment.


Two options remain: posting them to calldata or to blobs. The flow in both cases is similar: proposers post attestations in either of them, and store in L1 a commitment to them (we can also modify the block hash to include a commitment to a set of attestations, to avoid an extra `SSTORE`, but this is a larger change). On block proposal, we check that the hash corresponds to the data posted. On (in)validation, the caller re-posts the attestations to L1, which get re-hashed and compared against the stored commitment, and then verified.

Calldata for a 48-sized committee is `(48 * 2/3 * 65) + (48 * 1/3 * 20) = 2400` bytes, or `38400` gas (note that after [EIP7623](https://eips.ethereum.org/EIPS/eip-7623) this could shoot up to `96k` depending on execution gas). This can be saved in favor of moving the attestations to blocks.
Copy link
Collaborator

Choose a reason for hiding this comment

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

That EIP7623 went live in pectra, so we're living in a 96K world now.

Also, now that we have EIP2537, we might should revisit BLS. @LHerskind had calculated that it was quite more expensive to use BLS with verification at a committee of 48, but if in the happy path, all we pay for is ~100B calldata and storing ~4 words (which can be in a roundabout so basically free), plus some new complexity in validator registration and setting up an epoch, it could be worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's really really interesting


Alternatively, we can keep this method open for anyone to call. Assuming there is no reward for this action, we expect only block proposers to actually call it. Should we introduce a gas rebate and reward, we could end up with multiple nodes racing to claim this reward.

Given the incentives, I suggest keeping the method open and with no rewards. And to minimize complexity, I suggest no gas rebates at all.
Copy link
Contributor

Choose a reason for hiding this comment

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

I have doubts about this approach.
Users' tx fees won't allow for the L1 cost of a fraud demonstration.
In a bull market, with high L1 demand, the Proposer could find the costs of a fraud demonstration to be significant. They (and subsequent proposerts) might be disincentivised from submitting a proposal altogether. This subsection hasn't convinced me that it won't be a problem, so perhaps it'll need modelling.

Half-baked idea:
In the early life of the network, whilst proposers are given block rewards (rather than burning tokens), perhaps the good proposer could be given the bad proposer's block reward. But again, this would need to be modelled. And it doesn't work once rewards go negative.

Choose a reason for hiding this comment

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

Eventually, the offending proposer needs to be slashed to cover the cost of the honest proposer trying to fork out the offending block: this is an objective (slashable) offense. To start though, tuning block rewards to cover any need to fork a bad block should be fine

- @aminsammara or @joeandrews
- @PhilWindle

## Summary
Copy link
Contributor

Choose a reason for hiding this comment

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

The summary and title alludes to going full optimistic. I really don't like the fully optimistic approach. In my view, it opens up wide for certain attacks that we don't have with delayed verification and support for early challenges. I would avoid saying optimistic unless it is optimistic, otherwise it is just confusing.


Given the tradeoffs in security, I push for the second option.

### Who can invalidate blocks, and what is the incentive?
Copy link
Contributor

Choose a reason for hiding this comment

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

If it is required to verify attestations at the time of proof submission, there is automatically a method of invalidation there - though a slow and nasty one.

For the early challenge, I agree going with no reward or rebate for the short term. Longer term it is something that can be covered in a slash.


Two options remain: posting them to calldata or to blobs. The flow in both cases is similar: proposers post attestations in either of them, and store in L1 a commitment to them (we can also modify the block hash to include a commitment to a set of attestations, to avoid an extra `SSTORE`, but this is a larger change). On block proposal, we check that the hash corresponds to the data posted. On (in)validation, the caller re-posts the attestations to L1, which get re-hashed and compared against the stored commitment, and then verified.

Calldata for a 48-sized committee is `(48 * 2/3 * 65) + (48 * 1/3 * 20) = 2400` bytes, or `38400` gas (note that after [EIP7623](https://eips.ethereum.org/EIPS/eip-7623) this could shoot up to `96k` depending on execution gas). This can be saved in favor of moving the attestations to blobs.
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we would really hit the 96K as it would require that the rest was below those 🤷. Also something where version of "chunking" can influence to the point where the extra complexity of blobs here might just not be worth it.

require(committeeCommitment == hash(committee))

let digest = block.proposalDigest
require(ecrecover(attestations[invalidIndex], digest) !== committee[invalidIndex])
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to make sure that the attestation checked is actually provided, as it would be a bit of a mess if possible to invalidate because you point out that one of the 15 "excess" values was not provided.

storage.tips = storage.tips.updatePendingBlockNumber(blockNumber - 1)
```

Considering we need to do only a single `ECRECOVER`, we can estimate the gas for this operation to be `3000 * 31 = 93k` gas less than the current proposal validation, plus `4200` for the two SLOAD operations (`attestationsHash` and `committeeCommitment`), and the 38400 gas for calldata. This results in `160k - 93k + 4.2k + 38k = 110k` gas. Note that this function should hardly ever be called.
Copy link
Contributor

Choose a reason for hiding this comment

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

As I recall we need > 2/3, so it it would be 32 less, as we usually do 33.


Assuming our circuits and proving systems are sound, a prover can post a proof for a given epoch without having to verify any attestation, which is enough for convincing L1 of the correctness of the proven state root. However, if we were to do this, we lose the training wheels provided by the economic security of the attestation committee, in the event of a bug in proving.

It follows that we want attestations to be verified. And as mentioned above, we know that verifying the attestations for the last block in an epoch is equivalent to verifying them for every block _in the epoch_, since every block in the epoch is attested by the same committee members, so the total stake is the same. So we should demand provers to verify the attestations of the last block in the epoch when they upload a proof.
Copy link
Contributor

Choose a reason for hiding this comment

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

Based on messages seen in slack, we will need to improve clarity of this section to better convey that we are verifying the attestations on L1, and not just having the provers do it locally for themselves.

spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 17, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 17, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 21, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 21, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 24, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 24, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 25, 2025
spalladino added a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 28, 2025
github-merge-queue bot pushed a commit to AztecProtocol/aztec-packages that referenced this pull request Jul 28, 2025
Implements AztecProtocol/engineering-designs#69

Median `propose` gas cost is `321250` according to
`happy.t.sol#test_100_val`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants