SASSAFRAS
Authors: Jeff Burdges, Fatemeh Shirazi, Alistair Stewart, Sergey Vasilyev
BADASS BABE is a constant-time block production protocol. It intends to ensure that there is exactly one block produced with constant-time intervals rather than multiple or none. It extends on BABE to address this shortcoming of BABE. While Jeff's Write-up describes the whole design space of constant-time block production, here we describe a practical instantiation of the protocol using zk-SNARKs to construct a ring-VRF.
Layman overview
We want to run a lottery to distribute the block production slots in an epoch, to fix the order validators produce blocks by the beginning of an epoch. Each validator signs the same on-chain randomness (VRF input) and publishes this signature (VRF output=[value, proof]). This value is their lottery ticket, that can be validated against their public key. The problem with this approach is that the lottery-winners are publicly known in advance and risk becoming targets of attacks. We aim to keep the block production order anonymous. The assignment of the validators to the slots should be fixed for the whole epoch, but noone besides the assigned validator should know whose a slot is. However, we can't validate the tickets prior the lottery using their public keys as it would deanonymize the validators. If tickets were not validated prior to the lottery then instead we can validate them after the lottery by an honest validator claiming their slots when producing blocks.
However, the problem is that anyone can submit fake tickets, and though they won't be able to produce a block, slots would be preassigned to them. Effectively, it results in empty slots, which defeats the goal of the protocol. To address this problem, we need a privacy-preserving way of validating a ticket. So an honest validator when submitting their ticket accompanies it with a SNARK of the statement: "Here's my VRF output that has been generated using the given VRF input and my secret key. I'm not telling you my keys, but my public key is among those of the nominated validators", that is validated before the lottery.
Now we have a way of making the ticket itself anonymous, we need a way to anonymously publish it to the chain. All ways of doing this with full anonymity are expensive. Fortunately, one of the simplest schemes is good enough for our purposes: a validator just sends each of their tickets to a random validator who later puts it on-chain as a transaction.
Plan
In an epoch we use BABE randomness for the epoch as ring VRF inputs to produce a number of outputs and publish them on-chain. After they get finalized we sort them and their order defines the order of block production for the epoch .
Parameters
- the set of nominated validators - number of slots per epoch, for an hour-long epoch with 6 second slots - redundancy factor, for an epoch of slots we want to have tickets in expectation for block production. We set . - attempts number of tickets generated per validator in epoch - a bound on a number of tickets that can be gossiped, used for DoS resistance
Keys
In addition to their regular keys, we introduce for each validator a keypair on a SNARK-friendly curve Jubjub. We must ensure that the keys are generated before the randomness for an epoch they are used in is determined.
Given the security parameter and some randomness generate a key pair
As an optimization we introduce an aggregate public key (called a commitment in Jeff's writeup) for the whole set of validators, that is basically a Merkle root built upon the list of individual public keys. In conjuction to that we use the copath to identify a public key in the tree as a private input to a SNARK.
Phases
Here we describe the regular operation of the protocol starting from a new set of validators being nominated. Bootstrapping the protocol from the genesis or soft-forking Kusama is not described here.
1) Setup
Once per era, as a new set of validators gets nominated or some other parameter changes, we reinitialize the protocol with new values for the threshold and the aggregated public key .
Each validator
- Calculates the threshold that prevents the adversary to predict how many more blocks a block producer is going to produce.
- Computes the aggregated public key and copath of s public key
- Obtains the SNARK CRS and checks for subversion if it has changed or hasn't done it earlier.
2) VRF generation Phase
We aim to have at least VRF outputs (tickets) published on-chain (we can't really guarantee that, but the expected value will be ).
Randomness
At the epoch we use the randomness as provided by BABE, namely
We use to create inputs to the ring-VRF, and the corresponding tickets will be consumed in .
It's critical that is still the concatenation of regular BABE VRF outputs. It follows that we run regular VRFs and ring VRFs in parallel. This is because ring VRF outputs will be revealed in epoch and hence if we use ring VRF outputs for randomness would be revealed too early. Thus we use VRFs that are unrevealed until the corresponding blocks are produced.
If we have a VDF, then all this would need to be determined an epoch prior i.e.
with being the concatenation of BABE VRFs from . The VDF would be run at the start of so that the output would be on-chain before starts.
VRF production
Each validator
- Given the randomness , computes a bunch of VRF outputs for the inputs , :
Selects the "winning" outputs that are below the threshold : where is a function that effectively maps VRF outputs to the interval . We call the set of corresponding to winning outputs .
Uses its copath generate proofs for the selected outputs ,
where consists of the SNARK and its public inputs .
As the result of this phase every validator obtains a number, possibly 0, of winning tickets together with proofs of their validity that need to be published on-chain.
3) Publishing Phase
We want block producers for at least a large fraction of slots unknown in advance. Thus well-behaved validators should keep their tickets private. To this end validators dont publish their winning VRF outputs themselves immediately, but instead relay them to another randomly selected validator (proxy) who then publishes it.
Concretely, chooses another validator , based on the output for . To this end, the validator takes and sends its winning ticket to the th validator in a fixed ordering. Then the validator signs the message: where refers to encrypted to a public key of . We number the winning outputs using ranging from up to and gossip them. If we have more than outputs below , we gossip only the lowest . This limitation is so that it is impossible for a validator to spam the network.
Once a valiaotr receives a messages it checks whether it has received a message with the same and and if so it discards the new message. Otherwise, the validator forwards (gossips) the message and decrypts it to find out whether the validator is the intended proxy. Validators gossip messages that are intended for them further to be secure against traffic correlation.
Once a validator decrypts a message with their private key they verify that they were the correct proxy, i.e. that corresponds to them. If so, then at some fixed block number, they send a transaction including for inclusion on-chain. Note that the validator might have been proxy for a number of tickets, in that case, it sends a number of transaction on designated block number.
If a validators ticket is not included on-chain before some later block number, either because the proxy is misbehaving or because they havent sent the winning ticket to any proxies, then publishes the transaction themselves. The reason why a validator would not send a winning ticket to any proxy is that it has more than winning tickets.
4) Verification
A transaction of this sort is valid for inclusion in a block if it can be verified as follows.
To verify the published transactions , we need to verify the SNARK. For this we need
- the corresponding input , which we can calculate from and ,
- the published output
- the aggregate public key .
All of these are the public inputs in SNARK verification:
5) Sorting
In the epoch we have the list of verified VRF outputs generated during the epoch which are finalized on-chain. For each of these outputs, we combine the ouput with the randomness , with either if we do not have a VDF or if we do have a VDF. Then we compute .
To determine the block production order for the epoch , each validator sorts the list of in ascending order and drops the largest values if any: , where and for .
The tickets are assigned to slots in an "Outside-in" ordering as follows. If we number the from lowest to highest as from to in increasing order, then the last slot is , the first slot is , the penultimate slot is , the second slot is etc.
Example of outsidein sorting: (1,2,3,4,5)->(2,4,5,3,1)
In the unlikely event that , there will be some unassigned slots in the middle of the epoch, and for these we use AuRa.
Concretely, for the algorithm for assiging lots that uses outside-in sorting, we take lists of even and odd numbered elements, reverse the list of odd elements, then concatenate the list of even elements, the list of aura slots and the reversed list of odd elements.
6) Claiming the slots
To produce a block in the assigned slot, the validator needs to include the ticket, a VRF output , that corresponds to the slot together with a non-anonymous proof that this is the output of their VRF.
Thus we introduce and the corresponding calls that are basically Schnorr knowledge of exponent proofs (PoKE). When validating the block nodes verify these proofs.
The validator must also include a never before seen VRF output, called the BABE VRF above. This may be done with the existing (non-jubjub) key on the same input (r_m || i).
Probabilities and parameters.
The first parameter we consider is . We need that there is a very small probability of their being less than winning tickets, even if up to of validators are offline. The probability of a ticket winning is . Let be the number of validators who actually participate and so . These validators make attempts each for a total of attempts. Let be the nimber of winning tickets.
Then it's expectation has . If we set , this is . In this case, .
Using Bernstein's inequality:
For , this gives under , which is certainly small enough. We only need the Aura fallback to deal with censorship. On the other hand, we couldn't make smaller than and still have tolerance against validators going offline. So is a sensible choice, and we should never need the Aura fallback.
The next parameter we should set is . The problem here is that if a validator gets winning tickets in an epoch, then when the adversary sees these, they now know that there will be no more blocks from .