Nouns Vortex:
A privacy preserving voting for Nouns DAO

Presented by team 水 (Mizu)

telegram contacts: @levs57, @curryrasul

This is our R&D team presentation for Noun's DAO voting research sprint.

About us

We are a team of friends, we both work in zk and are passionate at advancing the field forward, both theoretically and practically.

Rasul: I am systems engineer, currently working on RLN (Rate-Limiting Nullifier) at Ethereum Foundation in Privacy & Scaling Explorations team, and mainly specialize in zk circuits engineering and Rust. I have experience of participating (and winning) in various blockchain hackathons & contests; for the last hackathon we built VoAn (ANonymous VOting protocol) on NEAR (explanation and app).

Lev: I have the research mathematician background (and several publications in algebraic geometry which are hardly relevant in this scope). I have just participated in Ethereum Foundation's grant project devoted to MACI voting system, and with my coauthor we've developed this MPC algorithm I'm very proud of. I also bear the Groth16 Hacker Soulbound, which was given to the ten winners of ZK Hack x Geometry Puzzle. I mostly design stuff, and think about security.

The design

While this presentation is supposed be mostly about our team and our achievements, we have judged that our most relevant achievement is that we have actually thought about this kind of problems for a long time.

Therefore, the best way of representing us is actually laying down a detailed exposition of our proposed design. There are some gaps in it that will need to be filled in during R&D process, but we believe that "research" part is almost ready at this point, and proudly propose the following ➜➜➜

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Design 水

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Design rationale and outline 水

We would like to briefly state our general design goals, to ensure that we are on the same page with the DAO.

  • Unconditional privacy.
  • Minimal dependency on off-chain computation.
    • Ideally, voting should be on mainnet fully.
  • Auditability, minimal external dependencies.
  • Optimized gas costs.

We do not yet know what is exactly implied by Nounesness ⌐◨-◨, but we feel these should be the main points.

That leads us to some decisions. First of all, we regrettably reject any notion of non-coercibility. The best current solution in this direction is MACI, and while one of the authors works on improving its centralized dependencies, it is very far from being figured out.

We understand that voter collusion or bribing might be an issue, but we also do not feel that non-coercibility notion provides enough incentives against malicious 51% attacks; maybe only improves situation a little bit. Therefore, we would recommend DAO to continue using the veto power against bribing attacks, and at the same time to keep researching into the game-theoretic mechanisms allowing to eventually replace it (likely, with some sort of Schelling point mechanic and ragequit option).

Our technique is a merge of two approaches.

The main approach we want to employ is similar in nature to Tornado.Cash, Semaphore and similar projects. One of the authors have implemented it in VoAn. By design, it hides the voter identity, but can not hide the vote power. Naively, this can be fixed by requiring to vote with each Noun separately, but that is inconvenient.

Second approach is using homomorphic ElGamal encryption and lookup tables, similar to Open Vote Network. This approach is sound, however it requires every voter to not DoS the protocol after the first round. As we do not want to require everyone to deposit a big collateral, we use similar, but distinct method allowing us to separate voters from tally authorities. We require n-of-n secrecy threshold for them, and inclusion in the tally authorities must be permissionless; therefore DoS attack is possible. We, however, will require the collateral from an authority proportional to the expected cost of doing the voting in a non-efficient way described above; that way such an attacker would only delay the voting. Our design rationale here is that bigger accounts who benefit the most from this upgraded value hiding system will be likely able to present the required collateral and actually have an incentive to participate as authority to ensure the secrecy of their own vote.

We also use two additional techniques, one to deal with multisigs and hardware wallets, and one to deal with scalability.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Additional techniques 水

For multisigs and hardware wallets, we suggest creating a key rotation mechanic, allowing any account (either EOA or smart contract) to register in the system. It then obtains a key which , internally, can act as a multisig with some reasonable threshold adjustable in a registration phase. We also provide a system which allows to commit to the vote from the account, in case this level of security is still not enough for some hardware wallet users.

Should we implement ECDSA signature option which would support hardware wallets natively? It requires quite a beefy hardware to work, sorry state of affairs can be checked here. Currently we believe it is optional.

Second technique (which we call "mini-rollup") is our main gas cost optimization trick. It basically boils down to doing almost everything optimistically, namely, both validating the proofs and validating the required Merkle tree root. However, we argue that it doesn't incur most of the issues normally related to optimistic rollups because the challenge game is 1-round for proofs, and for Merkle tree it can be played in parallel with voting (this is the part where we are figuring out exact details). This also, apparently, gives the Nouns DAO an interesting promotion opportunity which we will describe further.

One could argue that we actually could instead scale the system using some custom zk-recursion technique. However, we believe such undertaking to be out of scale for this project, both in terms of our operational capability and auditability of dependencies. Therefore, we'd like to come up with surprisingly effective simple solution, not the one that is surprisingly hard to execute.

In what follows, we provide a description of each of these systems in detail, in the following order: registration, optimistic proof pool, homomorphic approach, fallback approach.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Tl ; dr 水

Here we provide non-technical high-level explanation of our system.

  1. Users can register and deploy public key (or even multisig), which they can then rotate at will by submitting the re-register transaction on-chain.

  2. If user doesn't want to use hot private keys for voting at all, they can use an alternative system, which enforces the vote by submitting a commitment from the main account on-chain.

    • If there is a significant improvement in ecdsa-circom, the hardware wallets can be integrated directly, however, currently we believe this is infeasible for most users.
  3. Otherwise, individual proposals do not require registration for voting.

  4. Each proposal has a permissionless set of tallying authorities. To register as an authority, user must deposit a collateral. These authorities have n-of-n secrecy threshold to collectively read the voting power of individual votes (i.e., if they all collude, it is possible to de-anonymize votes of whale accounts). Therefore, we believe it will be rational for whale accounts interested in their privacy to register as the tallying authorities.

  5. Due to having n-of-n secrecy threshold, it also has 1-of-n liveness threshold. In case any of the authorities fails to follow the protocol, they are slashed (and the collateral is used to compensate gas expenses). The voting then goes into the fallback mode.

  6. Normal voting mode uses account-hiding mechanic similar to Tornado Cash, and El Gamal homomorphic encryption to encrypt votes (similar to Open Vote Network).

  7. Fallback voting uses account-hiding mechanic, and allows user to vote s times if they have s voting power. Big accounts in this regime will need to send votes separately and ensure they can not be correlated using timing / ip sniffing.

  8. All zero knowledge proofs can be submitted from any on-chain account. The community is advised to spin up a few relayer servers to pass these proofs in batches.

  9. The proofs are submitted to optimistic proof-checking pool to drastically reduce the cost of the system.

  10. Every voter's frontend has a .js script to check voting integrity and alert them in case of the submission of the incorrect proof.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Registration 水

The registration phase looks as follows. Every account A can register in a system by sending a hash of the following pair: commit = H(key, force).

This is updateable, and will need a system similar to ERC721Checkpointable.sol by Nouns DAO, because for a particular voting the snapshot of the commitment needs to be considered - notably, taken not at the proposal block (as done for Nouns count), but at the vote starting block.

In what follows, we will use proof-friendly primitives: Poseidon as hash function, denoted H, and babyJubJub curve for the in-system elliptic curve operations.

Now, we explain what is contained in this commitment. It is not enforced at this stage, but it is an appropriate place for an explanation.

key is a hash of the following data:

    threshold, total: Fp, // satisfying 1 <= threshold <= total <= LIMIT; suggest LIMIT=7;
    pubkeys[total] : pubkey // an array of n public keys in babyJubJub curve
    seed : Fp // a random nonce used to produce nullifiers

On the registration phase, we suggest multisignature users to translate their operational logic to our proof system, by choosing threshold and total corresponding to parameters of their multisig, and providing temporary public keys of each multisig party.

force is a root of a sparse Merkle tree which implements the key->value array, containing the enforced values (i.e., it has default value (0,0), and it having value either 0, Y, N at leaf i means that in the proposal i this account will be able to vote only with corresponding value, 0 corresponding to abstaining, and Y and N being elliptic curve generators corresponding to "yes" and "no" votes). It basically just contains compressed commands that must be fulfilled by voting from this account.

This finishes the registration phase - no checks are done at this stage at all.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Optimistic proofs 水

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Merkle tree update 水

First, let us recall that, for example, in Tornado Cash the user actually need to provide two proofs - one on the deposit, of the correct update of the Merkle tree, and the other on the withdrawal.

For Nouns Vortex, the tree update becomes a much more pressing issue. The appropriate "naive" way of doing it would involve changing ERC721Checkpointable.sol in such a way that any transfer or delegation would incur the update of a tree. This would incur a huge additional gas cost on every transfer, not to mention that it is actually just incompatible with allowance mechanic - smart contract is not able to provide us with the proof.

In our framework, the natural solution looks as follows: proposer, at the start of the voting, needs to send two transactions: first one "takes the snapshot", and the second one submits the Merkle tree root of all the accounts with nonzero voting power, ordered lexicographically, together with their snapshot states of voting power and commit's.

Now, we need some sort of the challenge game to prevent the proposer from submitting an incorrect Merkle root. The most naive game would be the following 1-round interaction:

Proposer: propose Merkle tree root, put up some collateral

Challenger: calculate Merkle tree root on-chain, if it is different - slash the proposer and take the collateral

This, however, requires a lot of gas. We are considering low-round games as possible replacement, to reduce collateral requirement on the proposer.

Generally, this is not too big of an issue, because this game can be played in parallel with normal voting flow (and any user that is executing a proof against an incorrect Merkle root is fraudulent anyway).

We reserve this part for further research.

Addendum (19.02.2023): there is an approach which reduces Merkle tree construction to the validation of the proving pool, described in the next chapter. This works roughly as follows:

  1. Each nouns tx or redelegation is logged into the block chain of operations in a following way: head[n] = keccak(head[n-1], operation). This will incur additional ~3000 gas cost per tx.

  2. In order to validate the new Merkle tree root, proposer needs to produce a proof with public inputs old_merkle_root, old_head, new_merkle_root, new_head, attesting the transition to the new Merkle tree. The amount of constraints in such a proof depends on the amount of txs between the last validated checkpoint and new checkpoint; therefore in case there were more than ~15 txs, multiple proofs will need to be constructed instead.

  3. The cost of proof checking is relatively small thanks to the proving pool mechanism, described in the next chapter.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Nounism 水


Nounism ⌐◨-◨ : an optimistic proof checker from Nouns ecosystem

This part of the system is a separate smart contract, which, in our opinon, should be exposed to everyone for potential integration. Nouns DAO, therefore, will be able to contribute to the general Ethereum ecosystem. Specifically, it will allow anyone to use the same system to check their proofs at low cost, in particular, any roll-up and any voting or financial privacy protocol will be able to use it. Prototype of one such application - communicated to authors by @twisterdev is expected to be presented on Eth Denver.

The basic features of contract are really simple. This is, basically, a pool for Groth16 proofs. Each proof is put there on a timelock, and after the required time has passed, it "matures".

Anyone, at any point, can challenge the immature proof and force it to be calculated. A small collateral needs to be deposited with the proof to ensure that it can pay for its gas.

Multiple proofs can be deposited in the same transaction, to also save on 27k gas for signature check.

The only subtle issue to be tackled is the size of collateral. We would like to make it fully automatic and ungoverned from the start, but it is impossible to fully predict the upcoming gas prices. While the condition of gas prices suddenly spiking and then staying consistently high for few days is unusual, this must be settled in some way. We have different ideas on this topic, the simplest one would be requiring at least 3x collateral based on observed avg gas price on last few deposits, and supporting a small (limited from above) automatic treasury that covers the gas cost in case of collateral deficit.

In the unlikely scenario when even this doesn't work (for example, treasury got depleted by an attacker using the same mechanic) it is still possible to slash them. Both Nouns Vortex and other possible applications have more than enough incentives to do it.

Addendum (19.02.2023): There is an additional benefit of separating this mechanism into the public smart contract. While we initially aim towards the optimistic pool (as it is readily available and easiest to execute), there are also other kinds of proving pools possible based on recursive proving techniques, and hopefully in a not too far off future they will become available. Efficiency of such a pool benefits greatly from additional throughput, and therefore it is reasonable to have only one such contract for the whole Ethereum and its zk-applications.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Homomorphic voting scheme 水

We assume that we have achieved the following state:

  • There is a Merkle tree with leaves leaf[i] where i runs through the set of all users who had non-zero voting balance at vote initialization block, in some canonical order. Leaf is calculated as leaf[i] = H(commit[i], nouns[i]), where nouns is voting power at the checkpoint. In what follows, we assume this root is correct; otherwise, voting will be challenged and cancelled.

  • There is a set of registered tally authorities, their decryption public keys live in the map auth_pub. We assume that there is also a decyption public key D which is a sum of auth_pub. It can be either calculated onchain, or also supplied by proposer and calculated optimistically.

  • We also assume we are given some independent generators of babyJubJub, denoted G, Y, N.

Now, in order to vote, user needs to create the following proof.

Public inputs: voting_id, voting_merkle_root, null, enc_vote, which are subject to the following relations:

  • Public check: voting_id coincides with the current public voting id, voting_merkle_root coincides with supplied Merkle root.

  • There exists the leaf of the Merkle tree voting_merkle_root, denoted further as leaf, which decomposes (eventually) to private inputs key, force, nouns, subject to some relations:

  • H(key.seed, voting_id) = null - unique nullifier to prevent double-voting

  • There is an elliptic curve point vote_value which is subject to some additional relations:

    • The leaf of the force Merkle tree force[i] == vote_value or force[i] == (0, 0).

    • If it was (0, 0), there are key.threshold signatures of the message (voting_id, H(vote_value, seed)) with different public keys from an array key.pubkeys. // here, additional hashing with seed done to prevent this signatures from revealing private information, so they can be exchanged over insecure channel

  • vote_value = 0 or Y or N.

  • The value enc_vote is a homomorphic El Gamal encryption of the vote, namely a pair of points: (C, K), where C = (vote_value * nouns) + (rand * D) and K = rand * G for some random scalar rand.

Now, these proofs should be relayed on-chain and put in the proof-checking pool. We suggest that community spins up few public relayers for this purpose. Submitting proofs from some external EOA is fine, too, but it will require some collateral (relayers will require collateral too, but relatively small because they will submit proof in a batch).

When the proof-checking delay has passed, the resulting values enc_vote are submitted back and added up. This can also be done optimistically without much effort.

Let us denote the total sum of all valid enc_vote[i] as enc_result = (C_res, K_res).

Now, each decryption authourity i submits dec[i] = priv K_res with the proof that it was formed correctly with their private key priv satisfying priv*G = auth_pub[i]. If they fail to submit it, they are slashed and the voting goes into fallback mode.

Denote dec to be sum of dec[i].

The res_point = C_res - dec. Now, anyone can provide values yay, nay such that yay*Y + nay*N = res_points. The way to obtain these values is lookup. This lookup is quadratic in the amount of Nouns (which is fine on our scale), but if there will be more voting options, it is possible that the scheme will need to be altered a bit to instead send multiple points.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Fallback scheme 水

Here, we describe an alternative, simpler scheme which does not encrypt the vote value, and, instead allows to vote with Nouns separately.

It has few disadvantages; first of all, the on-going amount of yes/no votes is readily observable. Therefore, whale user trying to hide their activity will need a significant amount of effort: they will need to send votes at different timings, and, ideally, submit them to relayer from different ip addresses.

Because this effort is significant, we suggest that this scheme is only used as a fallback in case one of decryption authorities fails to provide the decryption. The gas cost of the scheme is also higher (due to each Noun being a separate vote); totally, this will determine the amount of collateral the decryption authority needs to provide.

The changes to the proof are fairly minimal:

  1. Instead of exposing enc_vote, it should expose vote_value itself.

  2. Nullifier is calculated as null = H(key.seed, voting_id, s) where 0 <= s < nouns. This ensures that the holder of nouns voting power can vote exactly nouns times.

Otherwise, the scheme is completely analogous.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Required frontend infra 水

Because our scheme heavily relies on (albeit simple, but still) optimistic validation, we believe that the best way to ensure its security is giving every user a tool to validate vote integrity.

We suggest that the frontend of the voting site is pinned in IPFS (which is generally a good practice), and there is a short snarkjs script verifying integrity of both the proving pool and initial Merkle tree - the scale more than admits it.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Roadmap 水

  • Win the Private Voting Research Sprint by Nouns DAO;
  • Initialize repo for the implementations;
  • Design:
    • Merkle tree
    • Collateral amount
  • Circuit implementation;
  • Smart-contract implementation;
  • Client app implementation.

Nouns Vortex:
A privacy preserving voting for Nouns DAO

水 Optimistic pool 水

Here, we outline our design of Optimistic Pool. It is an independent contract, serving as a co-processor to optimistically check large amount of independent operations. It could be treated as an optimistic rollup with extremely parallelized execution.

High-level overview

Optimistic Pool holds a sequence of statements, called claims. Claims have types. Adding a new type is permissionless - typically, claim type is a particular kind of statement which needs to be checked - for example, a verifier for a certain zk-proof could be a claim type, (or maybe some other function which costs a lot of gas to verify directly).

In essence, claim type is just an external contract which is assumed to have a specific form. If it doesn't, validating it might be undesirable, so there is a way to opt out from validation of a specific claim type.

Claims periodically form batches - a sequence of claims which is then processed by the optimistic pool. When the batch is finalized, the special parties called blessers can declare which claims they deem correct (blessed), incorrect (cursed) or refuse to process. To do it in an orderly fashion, blessers form a queue.

To get into a queue, blesser needs to deposit a bond in ETH. Blessers can be challenged, and their stake will be taken if they:

  • Bless some statement such that an actual ground truth checker for this claim type returns "false".

  • Curse some statement such that ground truth checker returns "true".

  • Process any statement and not process other statement of the same type in the same batch (censorship prevention).

Blessers collect small tips from the claimers.

After the end of the challenge period, it is possible to query a particular claim and check whether it got blessed.

API (wip)

This is an overview of the desired API. The actual implementation is not fully settled yet, there are some micro-optimizations possible.

ClaimKind - an address of some external contract; there is an append-only, permissionless array of existing claim types, claimKindArray.

Claim - a struct (uint32 claimKindId, uint224 claimValue).

The ClaimKind contract is assumed to have some specific interface, in particular, it must have function which packs some data, obtains uint224 value and calls deposit_claim(uint32 _claimKindId, uint224 _claimValue) payable function of the Optimistic Pool contract.

deposit_claim does obvious checks (like msg.sender == claimKindArray[_claimKindId], and tip being enough), and adds the claim into the claimQueue, which will be described further.

Typically, data should be packed using keccak hash from calldata, however, we have left the space for other implementations, considering the upcoming EIP4844 and other possible data availability solutions.

check_claim(uint224 _claimValue, bytes[] _advice) returns bool - the function that checks whether the claim is correct or not. MUST return 0 if it is incorrect, 1 if it is correct, and revert in all other cases. It is not recommended to validate ClaimKind-s with checker functions not satisfying this requirement, as well as functions in which the result may change with time, or depend on sender address or other execution context.

ClaimBatch - batched sequence of Claim's. Implementation details might vary, but likely it is some sort of append-only array. ClaimBatches occur every few hours, and deposit_claim only adds claims to the current batch.

claimQueue is a sequence of ClaimBatch-es. (in practice, it doesn't need to be an actual infinite queue, and will likely cycle once in a ~ month to save on the gas costs).

Blessing - tightly packed sequence of 0, 1 and 2 - s + some auxiliary information.

  • 0-s mean "Claim incorrect"
  • 1-s mean "Claim correct"
  • 2-s mean "Refuse to assess"

Auxiliary information is the claimedAmount of 0's and 1's in a blessing, and blesser address. We are so lazy we do not even want to compute this, so instead it will be checked optimistically.

Each ClaimBatch goes through the following process:

Formation --> Blessing period -> Challenge period --> Finalized

New claims can be added only during the formation period. In the blessing period, new blessings can occur. In both blessing and challenge period the blessings can be challenged, but in challenge period new blessings can not occur.

In the finalized state, external contracts can query and check whether the claim is blessed, cursed, or not processed. we have also considered adding some automatic call-back system, but we feel it can be added independently and is not actually required by the core functionality of the contract.

Blessings for a particular ClaimBatch are kept in the append-only array, together with address blesser and bool isInvalidated value.

Any blessing can be challenged using the following API:

challenge_blessing(uint _batch_id, uint _blessing_id, bytes[] _challenge) which is a function that

  • Checks that this blessing is !isInvalidated.
  • Parses _challenge and depending on these either:
    • Checks that claimedAmount is incorrect.
    • Checks that there are two elements satisfying censorship requirement (i.e. there are 2 claims of the same ClaimKind one of which is processed, and other is ignored).
    • Calls check_claim on the contested claim and checks that the answer differs from claimed by the blesser.
  • In any of those cases, it sets isInvalidated = true, and slashes the blesser.

Blessers form a blesser queue. In order to participate in a queue, one must call blesser_get_into_queue(), which is a payable function, pledging the CURRENT_BOND_VALUE of eth.

Function bless(uint _batchId, Blessing _blessing) only works in the blessing period of the batch _batchId. Moreover, for the first half of the blessing period the available blessers are only blessers from the queue - with a running requirement blesserOffset * CUTOFF_SPEED < block.timestamp - batchBlessingInitTimestamp. In the end of the first half there should be ~10 blessers available. In the second half, anyone is allowed to bless.

Any blesser that is not the first one in the queue must still pledge the bond independently, and is not ejected from the queue (and by all means functions as a normal independent blesser).

In order to withdraw tips, blessers also use optimistic computations - with the exception of the main blesser (the one in the head of the queue), which can just take claimedAmount and leave.

Other blessers only get tips for things that they have blessed and previous ones did not. Therefore, in order to do it, they must submit a statement of a particular ClaimKind, which consists of a withdrawal statement - sequence of 0s and 1s, with 1 meaning that the corresponding claim was blessed or cursed, and was not blessed or cursed by anyone before them, and amount being the amount of 1s. This statement is then processed normally using the aforementioned mechanism.

The system has few constants and few moving variables. Constants are BLESSING_PERIOD and CHALLENGE_PERIOD - these need to be chosen based on censorship-resistance properties of Ethereum, and the time required to socially coordinate challenge in case of known watchers being DOSed. We believe few hours are likely enough, and conservatively put CHALLENGE_PERIOD = 6 hours and BLESSING_PERIOD = 2 hours.

Arguably, in a more mature system this could be much lower, though varying gas costs are also a concern. compared to 7 day finality of classic multi-round optimistic rollup we believe this performance is still okay-ish for most applications; we are open to hear analysis of this problem from different PoVs

The BATCH_FORMATION_PERIOD should optimally be quite big (up to 8 hours) to ensure there are enough statements to efficiently process. It can be lowered when batches become bigger, because the gas savings stop at log_3(2**256)-sized batches (the maximal size such that ternary blessing word fits into uint256).

Two main moving variables are CURRENT_BOND_VALUE and CURRENT_BASE_TIP_VALUE. Each ClaimKind should also declare multiplier, which represents the amount of gas spent on checking the claim. Base tip value is then modified by this multiplier.

This part is largely "here be dragons", still, but we describe some way of adjusting them automatically.

  1. Tip value grows if blesser queue is too short (say, less than 10) and lowers if blesser queue is too long (say, more than 100).

  2. Collateral can not be smaller than some base value (say, ~1 ETH), and increases and decreases based on avg block base gas cost. This requires some sort of gas logging mechanism, but generally it doesn't need to be very precise. Generally, the potential to go into the failure mode only occurs if the gas spikes in such a way that cost of the singular challenge becomes > than collateral. This puts some upper limit on CLAIM_KIND_MULTIPLIER[_claimKindId], likely unachieveable due to block gas limit. Generally, targeting something like the cost of 300M gas (according to block.basefee averaged along few previous batches) should be a reasonably effective strategy. This value becomes bigger than 1 ETH at ~300 gwei sustained gas cost. The failure mode here would be gas spiking at least 30x times (for ClaimKind taking 10M gas to check) in an instant and staying there forever.

  3. Even in the case of such failure mode, the bad challenges can be slashed at loss. One can consider dao-ish or even automatic treasury targeting some larger value, which can be automatically used in such a failure mode to compensate for slashing expenses. In such case, bottom for collateral and hence fee can be even lower than 1 ETH.