Start Date | September 06, 2023 |
Description | Sassafras consensus protocol description and structures |
Authors | Davide Galassi |
Sassafras is a novel consensus protocol designed to address the recurring fork-related challenges encountered in other lottery-based protocols, such as Babe.
Sassafras aims to establish a unique association between each epoch's slots and the validators, ensuring that there will be one and only one validator per slot.
The protocol ensures the anonymity of the validator associated to a slot until the slot is not claimed at block production time.
Sassafras Protocol has been extensively documented in a comprehensive research paper. However, this RFC serves the purpose of conveying essential implementation details that are crucial for interoperability and clarifying aspects left open for implementation discretion.
This RFC focuses on providing implementors with the necessary insights into the protocol's operation.
To avoid ambiguities and interoperability issues, this document takes precedence over the research paper in cases where discrepancies arise between the two.
In addition to fostering interoperability, another objective of this RFC is to facilitate the implementation of Sassafras within the Polkadot ecosystem. While the specifics of deployment mechanics are beyond the scope of this document, it paves the way for integrating Sassafras into the Polkadot network.
Developers responsible for creating relay-chains and para-chains within the Polkadot ecosystem who intend to leverage the benefits offered by the Sassafras Protocol.
Developers contributing to the Polkadot relay-chain, which plays a pivotal role in facilitating the interoperability and functionality of the Sassafras Protocol within the broader Polkadot network.
This section outlines the notation and conventions used throughout the document to ensure clarity and consistency.
Data structures are primarily defined using ASN.1, with a few exceptions:
- Integer types are not explicitly defined in ASN.1 and in the context of
this document
U<n>
should be interpreted asn
-bit unsigned integers (e.g.U32
)
To ensure interoperability of serialized structures, it's important to consider the order of fields in their definitions.
In cases where no specific instructions are given, structures should be serialized using SCALE.
Through this document it is advantageous to make use of code snippets as part of the comprehensive description. These code snippets shall adhere to the subsequent conventions:
-
For simplicity, code snippets are presented in a Rust-like pseudo-code format.
-
The function
BYTES(x: T)
returns anOCTET_STRING
representing the raw byte array representation of the objectx
with typeT
.- if
T
isVisibleString
(aka ascii string): it returns the sequence of bytes of its ascii representation. - if
T
isU<n>
: it returns the little-endian encoding of the integerU<n>
asn/8
bytes.
- if
-
The function
SCALE(x: T)
returns anOCTET_STRING
representing theSCALE
encoding of the variablex
with typeT
. -
The function
U<n>(x: OCTET_STRING)
returns aU<n>
interpretingx
as the little-endian encoding of an
bits unsigned integer. -
The function
BLAKE2_<n>
returns bytes of the standard blake2b hash.
Types and helper functions will be introduced incrementally as they become relevant within the document's context.
We find this approach more agile, especially given that the set of types used is not extensive or overly complex.
This incremental presentation enhances readability and comprehension.
The Sassafras Protocol employs a binding mechanism between validators and slots through the use of a ticket.
The protocol is organized into five discrete and asynchronous phases:
Validators generate and submit their candidate tickets along with validity proofs to the blockchain. The validity proof ensures that the tickets are legitimate while maintaining the anonymity of the authorship.
Submitted candidate tickets undergo a validation process to verify their authenticity, integrity and compliance with other protocol-specific rules.
After collecting all candidate tickets, a deterministic method is employed to associate some of these tickets with specific slots.
Validators assert ownership of tickets during the block production phase. This step establishes a secure binding between validators and their respective slots.
During block verification, the claims of ticket ownership are rigorously validated to uphold the protocol's integrity.
This chapter provides a high-level overview of the Bandersnatch VRF primitive as it relates to the Sassafras protocol.
It's important to note that this section is not intended to serve as an exhaustive exploration of the mathematically intensive foundations of the cryptographic primitive. Instead, its primary purpose is to offer a concise and comprehensible interpretation of the primitive within the context of this RFC.
For a more detailed and mathematical understanding of Ring-VRFs, we recommend referring to the Ring-VRF research paper.
The VRF Input, denoted as VrfInput
, is constructed using a domain and some
arbitrary data using the vrf_input(domain: OCTET_STRING, buf: OCTET_STRING)
function. This function is left opaque here and can be read in the actual
reference implementation.
The VRF Input, denoted as VrfInput
, is constructed by combining a domain identifier
with arbitrary data using the vrf_input
function:
fn vrf_input(domain: OCTET_STRING, buf: OCTET_STRING) -> VrfInput;
While the specific implementation details of this function are intentionally
omitted here, you can find the complete implementation in the
bandersnatch_vrfs
reference implementation.
Helper function to construct a VrfInput
from a sequence of data
items:
fn vrf_input_from_items(domain: OCTET_STRING, data: SEQUENCE_OF OCTET_STRING) -> VrfInput {
buf = OCTET_STRING(SIZE(0));
for item in data {
buf.append(item);
buf.append(length(item) as U8);
}
return vrf_input(domain, buf);
}
Given a VrfInput
object, the corresponding VrfOutput
, also referred to as
VrfPreOutput
, is computed using a Bandersnatch secret key.
A VrfOutput
can be created in two ways: as a standalone object or as part of a
VRF signature. In both scenarios, the resulting VrfOutput
remains the same, but
the primary difference lies in the inclusion of a signature in the latter, which
serves to confirm its validity.
In practice, the VrfOutput
functions as a seed to produce a variable number
of pseudo-random bytes. These bytes are considered 'verified' when VrfOutput
is
accompanied by a signature.
When used as a standalone object, VrfOutput
is primarily employed in situations
where the protocol necessitates to check the generated bytes according to some
protocol specific criteria before applying the signature.
To facilitate the construction of VrfOutput
from a secret key and VrfInput
,
the following helper function is used:
fn vrf_output(secret: BandernatchSecretKey, input: VrfInput) -> VrfOutput;
Additionally, a helper function is provided for producing len
bytes from
VrfInput
and VrfOutput
:
fn vrf_bytes(vrf_input: VrfInput, vrf_output: VrfOuput, len: U32) -> OCTET_STRING;
Just like the VrfInput
support function, we have intentionally excluded the
detailed implementation of this function in this document. However, you can
see the complete implementation in the dleq_vrfs
reference implementation:
This section defines the data that is to be signed using the VRF primitive:
VrfSignatureData ::= SEQUENCE {
transcript: Transcript,
vrf_input: SEQUENCE_OF VrfInput
}
transcript
: represents aark-transcript
object.vrf_input
: sequence ofVrfInputs
to be signed.
To simplify the construction of a VrfSignatureData
object, a helper function is provided:
TranscriptData ::= OCTET_STRING;
fn vrf_signature_data(
transcript_label: OCTET_STRING,
transcript_data: SEQUENCE_OF TranscriptData,
vrf_inputs: SEQUENCE_OF VrfInput
) -> VrfSignatureData {
transcript = TRANSCRIPT(transcript_label);
for data in transcript_data {
transcript.append(data);
}
return VrfSignatureData { transcript, vrf_inputs }
}
Bandersnatch VRF offers two signature options: plain signature, which in practice is like a Schnorr signature, or Ring signature. The Ring signature option allows for anonymous signatures using a key from a predefined set of enabled keys, known as the ring.
This section describes the signature process for VrfSignatureData
using the
plain Bandersnatch signature flavor.
PlainSignature ::= OCTET_STRING;
VrfSignature ::= SEQUENCE {
signature: PlainSignature,
outputs: SEQUENCE-OF VrfOutput
}
signature
: represents the actual signature (opaque).outputs
: a sequence ofVrfOutput
s corresponding to theVrfInput
s values.
Helper function to create a VrfSignature
from VrfSignatureData
:
fn plain_vrf_sign(
secret: BandernatchSecretKey,
signature_data: VrfSignatureData
) -> VrfSignature
Helper function for validating the signature and returning a Boolean value
indicating the validity of the signature (True
if it's valid):
fn plain_vrf_verify(
public: BandersnatchPublicKey,
signature: VrfSignature
) -> Boolean;
In this document, the types BandersnatchSecretKey
, BandersnatchPublicKey
and PlainSignature
are intentionally left undefined as are not relevant. Their
definitions can be found in the bandersnatch_vrfs
reference implementation.
This section deals with the signature process for VrfSignatureData
using the
Bandersnatch Ring signature flavor.
RingSignature ::= OCTET_STRING;
RingVrfSignature ::= SEQUENCE {
signature: RingSignature,
ring_proof: RingProof,
outputs: SEQUENCE_OF VrfOutput
}
signature
: represents the actual signature (opaque).ring_proof
: denotes the proof of ring membership.outputs
: sequence ofVrfOutput
objects corresponding to theVrfInput
values.
Helper function to create a RingVrfSignature
from VrfSignatureData
:
fn ring_vrf_sign(
secret: BandersnatchSecretKey,
signature_data: VrfSignatureData,
prover: RingProver
) -> RingVrfSignature;
Helper function for validating the signature and returning a Boolean value
indicating the validity of the signature (True
if it's valid).
It's important to note that this function does not require the signer's public key.
fn ring_vrf_verify(
signature: RingVrfSignature,
verifier: RingVerifier
) -> Boolean;
In this document, the types BandersnatchSecretKey
, BandersnatchPublicKey
,
RingSignature
, RingProof
, RingProver
and RingVerifier
are intentionally
left undefined as are not relevant. Their definitions can be found in the
bandersnatch_vrfs
reference implementation.
The first block produced for an epoch N
is required to include the descriptor
for the next epoch N+1
.
The descriptor of next epoch is the NextEpochDescriptor
.
AuthorityId ::= BandersnatchPublicKey;
Randomness ::= OCTET_STRING(SIZE(32));
NextEpochDescriptor ::= SEQUENCE {
authorities: SEQUENCE_OF AuthorityId,
randomness: Randomness,
configuration: ProtocolConfiguration OPTIONAL
}
authorities
: list of authorities for the next epoch.randomness
: randomness value associated to the next epoch.configuration
: next epoch optional protocol configuration.
The NextEpochDescriptor
must be SCALE
encoded and embedded in the block
header digest data.
The identifier for the digest element is BYTES("SASS")
.
Security Consideration: Instances of NextEpochDescriptor
are generated through
on-chain code whenever a block is identified as the first of an epoch.
Consequently, every node executing the block has the capability to verify if
the descriptor generated during block execution matches the one produced by the
block author, which is stored in the digest data.
Each epoch has an associated randomness value defined by the
NextEpochDescriptor
Randomness
element.
The first block of epoch N
contains the randomness associated to the next epoch N+1
.
Randomness for epoch N+1
is computed using the VRF output values which are associated
to each block of epoch N-1
.
Assuming block Bi
is produced during epoch N-1
. Then the randomness for epoch N+1
is incrementally generated as RandomnessAccumulator = Blake2(Bi.VrfOut)'
.
The first block produced for epoch N
will propose as epoch N+1
randomness the value
of RandomnessAccumulator
as found after the production of the last block associated
to epoch N-1
.
The ProtocolConfiguration
primarily influences certain checks carried out
during ticket validation. It is defined as follows:
ProtocolConfiguration ::= SEQUENCE {
attempts_number: U32,
redundancy_factor: U32
}
attempts_number
: number of tickets that can be submitted by each next-epoch authority.redundancy_factor
: factor that enhances the likelihood of a candidate ticket being deemed valid according to some protocol specific checks.
Further details regarding this configuration can be found in the section dedicated to candidate ticket validation (ticket-id threshold computation).
ProtocolConfiguration
values can be adjusted via a dedicated extrinsic which should have origin set to Root
.
A valid configuration proposal submitted on epoch K
will be propagated in
the NextEpochDescriptor
at the begin of epoch K+1
and will be effectively
enacted on epoch K+2
.
A new ProtocolConfiguration
can be submitted using a dedicated extrinsic,
with the requirement that its origin is set to Root
. If a valid proposal is
submitted during epoch N-1
, it will be embedded into the NextEpochDescriptor
at the beginning of epoch N
.
As a shorthand notation, in this section we will refer to one of the next epoch validators simply as 'the validator'.
Upon the beginning of a new epoch N
, the validator will create a set of
'tickets' to be submitted on-chain. These tickets aim to secure ownership of one
of the slots in the upcoming epoch N+1
.
Each validator is allowed to submit a maximum number of tickets whose value is
found in the next epoch ProtocolConfiguration
attempts_number
field.
Each ticket has an associated unique identifier, denoted as TicketId
.
TicketId ::= U128
The value of the TicketId
is determined through the output of the Bandersnatch
VRF, using the following inputs:
- Epoch
N+1
randomness: aRandomness
obtained from theNextEpochDescriptor
. - Epoch
N+1
index: aU64
value tracked by the Sassafras on-chain code. - Attempt index: a
U32
value from0
toattempts_number
.
Let next_epoch
be an object with the information associated to the next epoch.
ticket_id_vrf_input = vrf_input_from_items(
BYTES("sassafras-ticket-v1.0"),
[
next_epoch.randomness,
BYTES(next_epoch.epoch_index),
BYTES(attempt_index)
]
);
ticket_id_vrf_output = vrf_output(AUTHORITY_SECRET_KEY, ticket_id_vrf_input);
ticket_bytes = vrf_bytes(ticket_id_vrf_input, ticket_id_vrf_output, 16);
ticket_id = U128(ticket_bytes);
A TicketId
value is considered valid if it is less than the ticket threshold.
Parameters:
v
: the number of authorities (aka validators) in the epochs
: number of slots in the epochr
: the redundancy factora
: number of attemptsT
: ticket threshold value (0 ≤ T ≤ 1
)
For an epoch of s
slots we want to have a number of tickets in expectation for
block production equal to the r·s
.
We need that there is a very small probability of their being less than s
winning tickets, even if up to 1/3
of authorities are offline.
First we set the probability of a ticket winning as T = (r·s)/(a·v)
.
Let n
be the number of validators who actually participate and so v·2/3 ≤ n ≤ v
.
These n
validators make a
attempts each, for a total of a·n
attempts.
Let X
be the random variable associated to the number of winning tickets, then
its expected value is:
E[X] = T·a·n = (r·s·n)/v
By setting r = 2
, we get
s·4/3 ≤ E[X] ≤ s·2
Using Bernestein's inequality we get Pr[X < s] ≤ exp(-s/21)
.
For s = 600
this gives Pr[X < s] < 4·10⁻¹³
, and thus we end up with a great
tolerance over offline nodes and we end-up filling all the slots with tickets
with high probability.
For more details about threshold formula please refer to the Probabilities and parameters paragraph of the layman description of Sassafras protocol.
For every candidate ticket an associated ticket-body is constructed.
TicketBody ::= SEQUENCE {
attempt_index: U32,
erased_pub: Ed25519PublicKey,
revealed_pub: Ed25519PublicKey
}
attempt_index
: attempt index used to generate the associated ticket_id.erased_pub
: Ed25519 ephemeral public key which gets erased as soon as the ticket is claimed.revealed_pub
: Ed25519 ephemeral public key which gets exposed as soon as the ticket is claimed.
The process of generating an erased key pair is intentionally left undefined, allowing the implementor the freedom to choose the most suitable strategy.
Revealed key pair is generated using bytes produced by the VRF with input parameters equal to those employed in ticket-id generation, only the label is different.
Let next_epoch
be an object with the information associated to the next epoch.
revealed_vrf_input = vrf_input_from_items(
domain: BYTES("sassafras-revealed-v1.0"),
data: [
next_epoch.randomness,
BYTES(next_epoch.epoch_index),
BYTES(attempt_index)
]
);
revealed_vrf_output = vrf_output(AUTHORITY_SECRET_KEY, ticket_id_vrf_input);
revealed_seed = vrf_bytes(revealed_vrf_input, revealed_vrf_output, 32);
revealed_pub = ed25519_secret_from_seed(revealed_seed).public();
The usage of the EphemeralPublicKey
s will be clarified in the ticket claiming section.
TicketBody
must be signed using the Bandersnatch Ring-VRF flavor.
sign_data = vrf_signature_data(
transcript_label: BYTES("sassafras-ticket-body-v1.0"),
transcript_data: [
SCALE(ticket_body)
],
vrf_inputs: [
ticket_id_vrf_input
]
)
ring_signature = ring_vrf_sign(AUTHORITY_SECRET_KEY, RING_PROVER, sign_data)
RING_PROVER
object is constructed using the set of public keys which belong to
the next epoch authorities and the ZK-SNARK initialization parameters
(more details in the bandersnatch_vrfs reference implementation).
The body and the ring signature are combined in the TicketEnvelope
:
TicketEnvelope ::= SEQUENCE {
ticket_body: TicketBody,
ring_signature: RingVrfSignature
}
All the ticket envelopes corresponding to valid tickets are submitted on-chain via a dedicated unsigned extrinsic.
All the actions in the steps described by this paragraph are executed by on-chain code.
The tickets are received via a dedicated unsigned extrinsic call.
Generic validation rules:
- Tickets submissions must occur within the first half of the epoch. (TODO: I expect this is to give time to the chain finality consensus to finalize the on-chain tickets before next epoch starts)
- The transaction must be submitted by one of the current session validators.
Ticket specific validation rules:
- Ring signature is verified using the on-chain
RingVerifier
. - Ticket identifier is computed from the (verified)
VrfOutput
contained in the ring signature and its value is checked to be less than the ticket-threshold.
Valid tickets bodies are persisted on-chain.
Before the beginning of the next epoch, i.e. the epoch in which the tickets are supposed to be claimed, the on-chain list of tickets must be associated with the next epoch's slots.
The assignment process can happen any time in the second half of the submission epoch, before the beginning of the next epoch.
There must be at most one ticket per slot.
- Initially, the complete list of tickets is sorted based on their ticket-id, with smaller values coming first.
- In cases where there are more tickets than available slots, the list is pruned by removing the larger value.
- Tickets are then assigned to the slots using an outside-in assignment strategy.
Outside-In Assignment
Given an ordered sequence of tickets [t0, t1, t2, ..., tk]
to be assigned to
n
slots, where n ≥ k
, the tickets are allocated according to the following
strategy:
slot-index : [ 0, 1, 2, ............ , n ]
tickets : [ t1, t3, t5, ... , t4, t2, t0 ]
Here slot-index
is a relative value computed as epoch_start_slot - epoch_slot
.
The association between each ticket and its corresponding slot is recorded on the blockchain and is publicly visible to all. What remains confidential is the identity of the ticket owner, and consequently, who possesses the authority to claim the corresponding slot. This information is known only to the author of the ticket.
In cases where the number of available tickets is less than the number of epoch slots, some (orphan) slots in the middle of the epoch will remain unbounded to any ticket.
In such situations, these unassigned slots are allocated using a fallback assignment method.
The on-chain authority set contains the authorities for the current epoch in a specific order. The index of the authority which has the privilege to claim a slot is calculated as follows:
index = BLAKE2_64(SCALE((epoch_randomness, slot))) mod authorities_number;
TODO: what about using epoch_randomness_accumulator
instead of epoch_randomness
?
The accumulator is updated using the randomness which ships with every block, thus
we know who is the author of block N only after block N-1 has been imported.
Is a bit more resistant to DoS. But given the sporadic nature of secondary method
maybe this is not a bit deal anyway.
With tickets bound to epoch slots, every validator acquires information about the slots for which they are eligible to produce a block.
The procedure for block authoring varies based on whether a given slot has an associated ticket according to the on-chain state.
If a slot is associated with a ticket, we will employ the primary authoring method. Conversely, if the slot lacks an associated ticket, we will resort to the secondary authoring method as a fallback.
Let ticket_body
represent the TicketBody
that has been committed to the on-
chain state, curr_epoch
denote an object containing information about the
current epoch, and slot
represent the absolute monotonic slot number.
Follows the construction of the VrfSignatureData
:
randomness_vrf_input = vrf_input_from_items(
domain: BYTES("sassafras-randomness-v1.0"),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.epoch_index),
BYTES(slot)
]
);
revealed_vrf_input = vrf_input_from_items(
domain: BYTES("sassafras-revealed-v1.0"),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.epoch_index),
BYTES(ticket_body.attempt_index)
]
);
sign_data = vrf_signature_data(
transcript_label: BYTES("sassafras-claim-v1.0"),
transcript_data: [
SCALE(ticket_body)
],
vrf_inputs: [
randomness_vrf_input,
revealed_vrf_input
]
);
The inclusion of revealed_vrf_input
allows the verifier to reconstruct the
revealed_pub
key which has been committed into the TicketBody
.
As the ticket ownership was already checked using the primary method, this step is purely optional and serves only to enforce the claim.
TODO: is this step really necessary?
- Isn't better to keep it simple if this step doesn't offer any extra security?
- We already have a strong method to claim ticket ownership.
The Fiat-Shamir transform is utilized to obtain a 32-byte challenge associated
with the VrfSignData
transcript.
Validators employ the secret key associated with erased_pub
, which has been
committed in the TicketBody
, to sign this challenge.
challenge = sign_data.transcript.challenge();
erased_signature = ed25519_sign(ERASED_SECRET_KEY, challenge)
If the slot doesn't have any associated ticket then the validator is the one
with index equal to the rule exposed in paragraph 6.4.1
.
Given randomness_vrf_input
constructed as shown for the primary method, the
VrfSignatureData
is constructed as:
sign_data = vrf_signature_data(
transcript_label: BYTES("sassafras-slot-claim-transcript-v1.0"),
transcript_data: [ ],
vrf_inputs: [
randomness_vrf_input
]
)
To establish ownership of a slot, the block author must construct a SlotClaim
object
which contains all the necessary information to assert ownership of the slot.
SlotClaim ::= SEQUENCE {
authority_index: U32,
slot: U64,
signature: VrfSignature,
erased_signature: Ed25519Signature OPTIONAL
}
-
authority_index
: index of the block author in the on-chain authorities list. -
slot
: absolute slot number (this is not the relative index within the epoch) -
signature
: This is a signature that includes one or twoVrfOutputs
.- The first
VrfOutput
is always present and is used to generate per-block randomness. This is not relevant to claim ticket ownership. - The second
VrfOutput
is included if the slot is associated with a ticket. This is relevant to claim ticket ownership using primary method.
- The first
-
erased_signature
: optional signature providing an additional proof of ticket ownership (see 6.5.1.1).
signature = plain_vrf_sign(AUTHORITY_SECRET_KEY, sign_data);
claim = SlotClaim {
authority_index,
slot,
signature,
erased_signature
}
The claim
object is SCALE encoded and sent as a block's header digest log
item.
Validation of SlotClaim
object as found in the block's header.
The procedure depends on whether the slot has an associated ticket or not according to the on-chain state.
If there is a ticket linked to the slot, we will utilize the primary verification method; otherwise, the protocol resorts to the secondary one.
In both scenarios, the signature within the SlotClaim
is verified using a
VrfSignData
that matches the one according to paragraph 6.5. If signature
verification fails then the claim is not legit.
Given claim
the instance of SlotClaim
within the block header.
plain_vrf_verify(AUTHORITY_PUBLIC_KEY, sign_data, claim.signature);
This verification is performed to confirm ticket ownership and is performed
utilizing the second VrfOutput
contained within the SlotClaim
signature
.
By using the VrfOutput
object together with the corresponding expected
VrfInput
the verifier should be able to reconstruct the revealed_pub
key
committed in the TicketBody
. If there is a mismatch, the claim is not legit.
reveled_vrf_output = claim.signature.vrf_outputs[1];
revealed_seed = vrf_bytes(revealed_vrf_input, revealed_vrf_output, 32);
revealed_pub = ed25519_secret_from_seed(revealed_seed).public();
If the erased_signature
element within the SlotClaim
is present the
erased_pub
key is used to verify it.
The signed challenge is generated through the identical steps as outlined in section 6.5.1.1.
If the slot doesn't have any associated ticket then the validator index which signed the claim should match the one given by the rule outlined in section 6.4.1.
The first VrfOutput
which ships with the SlotClaim
signature
is mandatory and
is always used to provide some randomness which gets accumulated in on-chain state
after block processing.
Given claim
the instance of SlotClaim
within the block header, and
accumulator
the current value for current epoch randomness accumulator,
the accumulator
value is updated as follows:
randomness_vrf_input = vrf_input_from_items(
domain: BYTES("sassafras-randomness-v1.0"),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.epoch_index),
BYTES(slot)
]
);
randomness_vrf_output = claim.signature.vrf_outputs[0];
randomness = vrf_bytes(randomness_vrf_input, randomness_vrf_output, 32);
accumulator = BLAKE2_256(CONCATENATE(accumulator, randomness));
The updated accumulator
value is stored on-chain.
The randomess accumulated during epoch N
will be used, at the start of the
next epoch (N+1
), as the value to be stored within the NextEpochDescriptor
randomness
element (see section 6.1) to be used as the epoch N+2
randomness
value.
As outlined throughout the document, epoch randomness value secures various protocol-specific functions, including ticket generation and assignment of fallback slots (refer to section 6.4.1). Additionally, users may utilize this value for other purposes as needed.
None
TODO ?
Describe the impact of the proposal on these three high-importance areas
- how implementations can be tested for adherence,
- effects that the proposal has on security and
- privacy per-se, as well as any possible implementation pitfalls which should be clearly avoided.
The utilization of Sassafras consensus represents a significant advancement in the mitigation of short-lived fork occurrences.
Generation of forks are not possible when following the protocol and the only source of forks is network partitioning. In this case, on recovery, the decision of which fork to follow is not opinionated and there is only one choice.
TODO ?
If the proposal alters exposed interfaces to developers or end-users, which types of usage patterns have been optimized for?
The adoption of Sassafras impacts native client code and thus can't be introduced just via a runtime upgrade.
- Web3 Foundation research page: https://research.web3.foundation/Polkadot/protocols/block-production/SASSAFRAS
- Sassafras whitepaper: https://eprint.iacr.org/2023/031.pdf
- Ring-VRF whitepaper: https://eprint.iacr.org/2023/002.pdf
- Sassafras reference implementation tracking issue: https://github.com/paritytech/substrate/issues/11515
- Sassafras reference implementation main PR: paritytech/substrate#11879
None
While this RFC lays the groundwork and outlines the core aspects of the protocol, several crucial topics remain to be addressed in future RFCs to ensure the protocol's completeness and security.
These topics include:
- Protocol Migration. Exploring how this protocol can seamlessly replace an already operational instance of another protocol is essential. Future RFCs should delve into the deployment strategy, including considerations for a smooth transition process.
-
Timing and Procedure: Determining the timing and procedure for the ZK-SNARK SRS (Structured Reference String) initialization ceremony. Future RFCs should provide insights into whether this process should be performed before the deployment of Sassafras and the steps involved.
-
Sharing with Parachains: Considering the complexity of the ceremony, we must understand whether the SRS is shared with parachains or maintained independently.
- Mixnet Integration: Submitting tickets directly can pose a risk of potential deanonymization through traffic analysis. Subsequent RFCs should investigate the potential for incorporating Mixnet technology or other privacy-enhancing mechanisms to address this concern.