diff --git a/404.html b/404.html index 91a3c53..7c653f4 100644 --- a/404.html +++ b/404.html @@ -91,7 +91,7 @@ diff --git a/approved/0001-agile-coretime.html b/approved/0001-agile-coretime.html index 0824fa4..d686f48 100644 --- a/approved/0001-agile-coretime.html +++ b/approved/0001-agile-coretime.html @@ -90,7 +90,7 @@ diff --git a/approved/0005-coretime-interface.html b/approved/0005-coretime-interface.html index 6aa2e25..b1a59c4 100644 --- a/approved/0005-coretime-interface.html +++ b/approved/0005-coretime-interface.html @@ -90,7 +90,7 @@ diff --git a/approved/0007-system-collator-selection.html b/approved/0007-system-collator-selection.html index 08c7cfe..824975e 100644 --- a/approved/0007-system-collator-selection.html +++ b/approved/0007-system-collator-selection.html @@ -90,7 +90,7 @@ diff --git a/approved/0008-parachain-bootnodes-dht.html b/approved/0008-parachain-bootnodes-dht.html index 6d3dfec..119eb1c 100644 --- a/approved/0008-parachain-bootnodes-dht.html +++ b/approved/0008-parachain-bootnodes-dht.html @@ -90,7 +90,7 @@ diff --git a/approved/0010-burn-coretime-revenue.html b/approved/0010-burn-coretime-revenue.html index 70c8d86..a7fd98e 100644 --- a/approved/0010-burn-coretime-revenue.html +++ b/approved/0010-burn-coretime-revenue.html @@ -90,7 +90,7 @@ diff --git a/approved/0012-process-for-adding-new-collectives.html b/approved/0012-process-for-adding-new-collectives.html index d260af9..395f689 100644 --- a/approved/0012-process-for-adding-new-collectives.html +++ b/approved/0012-process-for-adding-new-collectives.html @@ -90,7 +90,7 @@ diff --git a/approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html b/approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html index 3cf734a..79153b8 100644 --- a/approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html +++ b/approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html @@ -90,7 +90,7 @@ diff --git a/approved/0014-improve-locking-mechanism-for-parachains.html b/approved/0014-improve-locking-mechanism-for-parachains.html index cf4eb81..9963d20 100644 --- a/approved/0014-improve-locking-mechanism-for-parachains.html +++ b/approved/0014-improve-locking-mechanism-for-parachains.html @@ -90,7 +90,7 @@ diff --git a/approved/0022-adopt-encointer-runtime.html b/approved/0022-adopt-encointer-runtime.html index 69ab0fb..43674c2 100644 --- a/approved/0022-adopt-encointer-runtime.html +++ b/approved/0022-adopt-encointer-runtime.html @@ -90,7 +90,7 @@ diff --git a/approved/0032-minimal-relay.html b/approved/0032-minimal-relay.html index 564c5e8..89e08d8 100644 --- a/approved/0032-minimal-relay.html +++ b/approved/0032-minimal-relay.html @@ -90,7 +90,7 @@ diff --git a/approved/0042-extrinsics-state-version.html b/approved/0042-extrinsics-state-version.html index ee3f885..b62c8e6 100644 --- a/approved/0042-extrinsics-state-version.html +++ b/approved/0042-extrinsics-state-version.html @@ -90,7 +90,7 @@ diff --git a/approved/0043-storage-proof-size-hostfunction.html b/approved/0043-storage-proof-size-hostfunction.html index 1753a4f..72ea14e 100644 --- a/approved/0043-storage-proof-size-hostfunction.html +++ b/approved/0043-storage-proof-size-hostfunction.html @@ -90,7 +90,7 @@ diff --git a/approved/0045-nft-deposits-asset-hub.html b/approved/0045-nft-deposits-asset-hub.html index e6ef5f6..48aceed 100644 --- a/approved/0045-nft-deposits-asset-hub.html +++ b/approved/0045-nft-deposits-asset-hub.html @@ -90,7 +90,7 @@ diff --git a/approved/0047-assignment-of-availability-chunks.html b/approved/0047-assignment-of-availability-chunks.html index 3ff2e7b..9532891 100644 --- a/approved/0047-assignment-of-availability-chunks.html +++ b/approved/0047-assignment-of-availability-chunks.html @@ -90,7 +90,7 @@ diff --git a/approved/0050-fellowship-salaries.html b/approved/0050-fellowship-salaries.html index c34d2ad..760cc52 100644 --- a/approved/0050-fellowship-salaries.html +++ b/approved/0050-fellowship-salaries.html @@ -90,7 +90,7 @@ diff --git a/approved/0056-one-transaction-per-notification.html b/approved/0056-one-transaction-per-notification.html index fb61752..a6ed1d6 100644 --- a/approved/0056-one-transaction-per-notification.html +++ b/approved/0056-one-transaction-per-notification.html @@ -90,7 +90,7 @@ diff --git a/approved/0059-nodes-capabilities-discovery.html b/approved/0059-nodes-capabilities-discovery.html index 9a9b2ed..9ce51a1 100644 --- a/approved/0059-nodes-capabilities-discovery.html +++ b/approved/0059-nodes-capabilities-discovery.html @@ -90,7 +90,7 @@ diff --git a/approved/0078-merkleized-metadata.html b/approved/0078-merkleized-metadata.html index 7ad6b1b..e165aed 100644 --- a/approved/0078-merkleized-metadata.html +++ b/approved/0078-merkleized-metadata.html @@ -90,7 +90,7 @@ diff --git a/approved/0084-general-transaction-extrinsic-format.html b/approved/0084-general-transaction-extrinsic-format.html index 9ba21f1..d1c516e 100644 --- a/approved/0084-general-transaction-extrinsic-format.html +++ b/approved/0084-general-transaction-extrinsic-format.html @@ -90,7 +90,7 @@ diff --git a/index.html b/index.html index 9245bf5..ba72c60 100644 --- a/index.html +++ b/index.html @@ -90,7 +90,7 @@ diff --git a/introduction.html b/introduction.html index 9245bf5..ba72c60 100644 --- a/introduction.html +++ b/introduction.html @@ -90,7 +90,7 @@ diff --git a/new/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html b/new/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html index 7ada5ea..d8d2c93 100644 --- a/new/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html +++ b/new/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html @@ -90,7 +90,7 @@ @@ -291,7 +291,7 @@ - @@ -305,7 +305,7 @@ - diff --git a/print.html b/print.html index 918fffc..9d46588 100644 --- a/print.html +++ b/print.html @@ -91,7 +91,7 @@ @@ -288,6 +288,859 @@ detailing proposed changes to the technical implementation of the Polkadot netwo

None

None

+

(source)

+

Table of Contents

+ +

RFC-0026: Sassafras Consensus Protocol

+
+ + + +
Start DateSeptember 06, 2023
DescriptionSassafras consensus protocol specification
AuthorsDavide Galassi
+
+

Abstract

+

Sassafras is a novel consensus protocol designed to address the recurring +fork-related challenges encountered in other lottery-based protocols.

+

The protocol aims to create a mapping between each epoch's slots and the +validators set while ensuring that the identity of validators assigned to +the slots remains undisclosed until the slot is actively claimed during block +production.

+

1. Motivation

+

Sassafras Protocol has been rigorously detailed in a comprehensive +research paper authored by the +Web3 foundation research team.

+

This RFC is primarily intended to detail the critical implementation aspects +vital for ensuring interoperability and to clarify certain aspects that are +left open by the research paper and thus subject to interpretation during +implementation.

+

1.1. Relevance to Implementors

+

This RFC focuses on providing implementors with the necessary insights into the +protocol's operation.

+

In instances of inconsistency between this document and the research paper, +this RFC should be considered authoritative to eliminate ambiguities and ensure +interoperability.

+

1.2. Supporting Sassafras for Polkadot

+

Beyond promoting interoperability, this RFC also aims to facilitate the +implementation of Sassafras within the Polkadot ecosystem.

+

Although the specifics of deployment strategies are beyond the scope of this +document, it lays the groundwork for the integration of Sassafras into the +Polkadot network.

+

2. Stakeholders

+

2.1. Blockchain Developers

+

Developers responsible for creating blockchains who intend to leverage the +benefits offered by the Sassafras Protocol.

+

2.2. Polkadot Ecosystem Contributors

+

Developers contributing to the Polkadot ecosystem, both relay-chain and +para-chains.

+

The protocol will have a central role in the next generation block authoring +consensus systems.

+

3. Notation

+

This section outlines the notation and conventions adopted throughout this +document to ensure clarity and consistency.

+

3.1. Data Structures Definitions

+

Data structures are primarily defined using standard ASN.1, +syntax with few exceptions

+

To ensure interoperability of serialized structures, the order of the fields +must match the structures definitions found within this document.

+

3.2. Types Alias

+

We define some type alias to make ASN.1 syntax more intuitive.

+ +

3.2. Pseudo-Code

+

It is advantageous to make use of code snippets as part of the protocol +description. As a convention, the code is formatted in a style similar to +Rust, and can make use of the following set of predefined functions:

+

Syntax:

+ +

3.3. Incremental Introduction of Types and Functions

+

More types and helper functions are introduced incrementally as they become +relevant within the document's context.

+

4. Protocol Introduction

+

The timeline is segmented into a sequentially ordered sequence of slots. +This entire sequence of slots is then further partitioned into distinct segments +known as epochs.

+

The Sassafras protocol aims to map each slot within an epoch to the designated +validators for that epoch, utilizing a ticketing system.

+

The protocol operation can be roughly divided into five phases:

+

4.1. Submission of Candidate Tickets

+

Each of the validators associated to the target epoch generates and submits +a set of candidate tickets to the blockchain. Every ticket is bundled with an +anonymous proof of validity.

+

4.2. Validation of Candidate Tickets

+

Each candidate ticket undergoes a validation process for the associated validity +proof and compliance with other protocol-specific constraints.

+

4.3. Tickets and Slots Binding

+

After collecting all valid candidate tickets, a deterministic method is used to +uniquely associate a subset of these tickets with the slots of the target epoch.

+

4.4. Claim of Ticket Ownership

+

During the block production phase of the target epoch, validators are required +to demonstrate their ownership of tickets. This step discloses the identity of +the ticket owners.

+

5. Bandersnatch VRFs Cryptographic Primitives

+

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. Rather, its primary aim is to offer a concise and +accessible explanation of the primitive's role and usage which is relevant +within the scope of this RFC.

+

For an in-depth explanation, refer to the Bandersnatch VRF +spec

+

Bandersnatch VRF can be used in two flavors:

+ +

Together with the input, which determines the signed VRF output, both the +flavors offer the capability to sign some arbitrary additional data (extra) +which doesn't contribute to the VRF output.

+

5.1 Plain VRF Interface

+

Function to construct a VrfSignature.

+
#![allow(unused)]
+fn main() {
+    fn vrf_sign(
+        secret: BandernatchSecretKey,
+        input: OctetString,
+        extra: OctetString,
+    ) -> VrfSignature
+}
+

Function for signature verification returning a Boolean value indicating the +validity of the signature (1 on success):

+
#![allow(unused)]
+fn main() {
+    fn vrf_verify(
+        public: PublicKey,
+        input: OctetString,
+        extra: OctetString,
+        signature: VrfSignature
+    ) -> Unsigned<1>;
+}
+

Function to derive the VRF output from input and secret:

+
#![allow(unused)]
+fn main() {
+    fn vrf_output(
+        secret: BandernatchSecretKey,
+        input: OctetString,
+    ) -> OctetString<32>;
+}
+

Function to derive the VRF output from a signature:

+
#![allow(unused)]
+fn main() {
+    fn vrf_signed_output(
+        signature: VrfSignature,
+    ) -> OctetString<32>;
+}
+

Note that the following condition is always satisfied:

+
#![allow(unused)]
+fn main() {
+    let signature = vrf_sign(secret, input, extra);
+    vrf_output(secret, input) == vrf_signed_output(signature)
+}
+

In this document, the types SecretKey, PublicKey and VrfSignature are +intentionally left undefined. Their definitions can be found in the Bandersnatch +VRF specification and related documents.

+

5.4.2. Ring VRF Interface

+

Function to construct RingVrfSignature.

+
#![allow(unused)]
+fn main() {
+    fn ring_vrf_sign(
+        secret: SecretKey,
+        prover: RingProverKey,
+        input: OctetString,
+        extra: OctetString,
+    ) -> RingVrfSignature;
+}
+

Function for signature verification returning a Boolean value +indicating the validity of the signature (1 on success). +Note that this function doesn't require the signer's public key.

+
#![allow(unused)]
+fn main() {
+    fn ring_vrf_verify(
+        verifier: RingVerifierKey,
+        input: OctetString,
+        extra: OctetString,
+        signature: RingVrfSignature,
+    ) -> Unsigned<1>;
+}
+

Function to derive the VRF output from a ring signature:

+
#![allow(unused)]
+fn main() {
+    fn ring_vrf_signed_output(
+        signature: RingVrfSignature,
+    ) -> OctetString<32>;
+}
+

Note that the following condition is always satisfied:

+
#![allow(unused)]
+fn main() {
+    let signature = vrf_sign(secret, input, extra);
+    let ring_signature = ring_vrf_sign(secret, prover, input, extra);
+    vrf_signed_output(plain_signature) == ring_vrf_signed_output(ring_signature);
+}
+

In this document, the types RingProverKey, RingVerifierKey, and +RingSignature are intentionally left undefined. Their definitions can be found +in the Bandersnatch VRF specification and related documents.

+

6. Sassafras Protocol

+

6.1. Protocol Configuration

+

The ProtocolConfiguration is constant and primarily influences certain checks +carried out during tickets validation. It is defined as:

+
#![allow(unused)]
+fn main() {
+    ProtocolConfiguration ::= Sequence {
+        epoch_length: Unsigned32,
+        attempts_number: Unsigned8,
+        redundancy_factor: Unsigned8,
+    }
+}
+

Where:

+ +

The attempts_number influences the anonymity of block producers. As all +published tickets have a public attempt number less than attempts_number, +all the tickets which share the attempt number value must belong to different +block producers, which reduces anonymity late as we approach the epoch tail. +Bigger values guarantee more anonymity but also more computation.

+

Details about how exactly these parameters drives the ticket validity +probability can be found in section 6.2.2.

+

6.2. Header Digest Log

+

Each block's header contains a Digest, which is a sequence of DigestItems +where the protocol is allowed to append any information required for correct +progress.

+

The structures are defined to be quite generic and usable by other subsystems:

+
#![allow(unused)]
+fn main() {
+    DigestItem ::= Sequence {
+        id: OctetString<4>,
+        data: OctetString
+    }
+
+    Digest ::= Sequence<DigestItem>
+}
+

For Sassafras related DiegestItems the id is set to the constant ASCII string "SASS".

+

6.3. On-Chain Randomness

+

On-Chain, we maintain a sequence with four randomness entries.

+
#![allow(unused)]
+fn main() {
+    RandomnessBuffer ::= Sequence<OctetString<32>, 4>
+}
+

During epoch N

+ +

The buffer is entries are updated after block execution.

+

6.4. Epoch's First Block

+

The first block produced during an epoch N must include a descriptor for some +of the subsequent epoch (N+1) parameters. This descriptor is defined as:

+
#![allow(unused)]
+fn main() {
+    NextEpochDescriptor ::= Sequence {
+        randomness: OctetString<32>,
+        authorities: Sequence<PublicKey>,
+    }
+}
+

Where:

+ +

This descriptor is SCALE encoded and embedded in the block header's digest +log.

+

A special case arises for the first block of epoch 0, which each node produces +independently during the genesis phase. In this case, the NextEpochDescriptor +relative to epoch 1 is shared within the second block, as outlined in section +6.4.1.

+

6.4.1. Startup Parameters

+

Some of the initial parameters for the first epoch, Epoch #0, are set through +the genesis configuration, which is defined as:

+
#![allow(unused)]
+fn main() {
+    GenesisConfig ::= Sequence {
+        authorities: Sequence<PublicKey>,
+    }
+}
+

The on-chain randomness accumulator is initialized only after the genesis +block is produced, and its value is set to the hash of the genesis block.

+

Since block #0 is generated locally by each node as part of the genesis +process, the first block that a validator explicitly produces for Epoch +#0 is block #1. Therefore, block #1 is required to contain the +NextEpochDescriptor for the following epoch, Epoch #1.

+

The NextEpochDescriptor for Epoch #1:

+ +

6.5. Offchain Tickets Creation and Submission

+

During epoch N, each validator associated to epoch N+2 constructs a set of +tickets which may be eligible (6.5.2) to be delivered +to on-chain proxies, which are the validators scheduled for epoch N+1.

+

These tickets are constructed using the on-chain randomness snapshot taken +after the execution of the last block of epoch N-1 together with other +parameters and aims to secure ownership of one or more slots of epoch N+2.

+

Each validator is allowed to submit a maximum number of tickets, constrained by +attempts_number field of the ProtocolConfiguration.

+

The ideal timing for the candidate validator to start constructing the tickets +is subject to strategy. A recommended approach is to initiate tickets creation +once the last block of epoch N-1 is either probabilistically or, even better, +deterministically finalized. This delay is suggested to prevent wasting +resources creating tickets that might become unusable if a different chain +branch is chosen as the canonical one.

+

As said, proxies collect tickets during epoch N and when epoch N+1 begins +the collected tickets are submitted on-chain. +TODO (inherents/ unsigned ext?).

+

6.5.1. Ticket Identifier

+

Each ticket has an associated identifier defined as:

+
#![allow(unused)]
+fn main() {
+    TicketId ::= OctetString<32>;
+}
+

The value of the TicketId is completely determined by the output of the +Bandersnatch VRF with the following unbiasable input:

+
#![allow(unused)]
+fn main() {
+    let ticket_vrf_input = CONCAT(
+        BYTES("sassafras_ticket"),
+        GET(randomness_buffer, 1),
+        BYTES(attempt_index)
+    );
+
+    let ticket_id = vrf_output(AUTHORITY_SECRET_KEY, ticket_vrf_input);
+}
+

Where:

+ +

6.5.2. Tickets Threshold

+

A TicketId value is valid for on-chain submission if its value, when interpreted +as a big-endian 256-bit integer normalized as a float within the range [0..1], +is less than the ticket threshold computed as:

+
T = (r·s)/(a·v)
+
+

Where:

+ +

In an epoch with s slots, the goal is to achieve an expected number of tickets +for block production equal to r·s.

+

It's crucial to ensure that the probability of having fewer than s winning +tickets is very low, even in scenarios where up to 1/3 of the authorities +might be offline.

+

To accomplish this, we first define the winning probability of a single ticket +as T = (r·s)/(a·v).

+

Let n be the actual number of participating validators, where v·2/3 ≤ n ≤ v.

+

These n validators each make a attempts, 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] ≤ e^(-s/21).

+

For instance, with s = 600 this results in Pr[X < s] < 4·10⁻¹³. +Consequently, this approach offers considerable tolerance for offline nodes and +ensures that all slots are likely to be filled with tickets.

+

For more details about threshold formula please refer to the +probabilities and parameters +paragraph in the Web3 foundation description of the protocol.

+

6.5.3. Ticket Body

+

Every ticket candidate has an associated body, defined as:

+
#![allow(unused)]
+fn main() {
+    TicketBody ::= Sequence {
+        attempt_index: Unsigned8,
+        opaque: OctetString,
+    }
+}
+

Where:

+ +

6.5.4. Ticket Signature

+

TicketBody must be signed using the Bandersnatch Ring VRF flavor (5.4.2).

+
#![allow(unused)]
+fn main() {
+    let signature = ring_vrf_sign(
+        secret_key,
+        ring_prover_key
+        ticket_vrf_input,
+        ENCODE(ticket_body),
+    );
+}
+

ring_prover_key object is constructed using the set of public keys which +belong to the target epoch's validators and the zk-SNARK context parameters +(for more details refer to the Bandersnatch VRFs specification).

+

Finally, the body and the ring signature are combined within the TicketEnvelope:

+
#![allow(unused)]
+fn main() {
+    TicketEnvelope ::= Sequence {
+        ticket_body: TicketBody,
+        ring_signature: RingVrfSignature
+    }   
+}
+

6.6. Onchain Tickets Validation

+

All the actions in the steps described by this paragraph are executed by +on-chain code.

+

Validation rules:

+
    +
  1. +

    Ring signature is verified using the on-chain ring_verifier_key derived by the +static ring context parameters and the next epoch validators public keys.

    +
  2. +
  3. +

    Ticket identifier is locally recomputed from the RingVrfSignature and its value +is checked to be less than the tickets' threshold.

    +
  4. +
  5. +

    Tickets submissions can't occur within a block part of the epoch's tail, which +are a given number of the slots at the end of the epoch. The tail length is a +configuration value (e.g. 1/6 of epoch length) part of the configuration. +This constraint is to give time to the on-chain tickets to be probabilistically +(or even better deterministically) finalized and thus further reduce the fork chances.

    +
  6. +
  7. +

    All tickets which are proposed within a block must be valid and all of them +must end up in the on-chain queue. That is, no submitted ticket should be +discarded.

    +
  8. +
  9. +

    No duplicates are allowed.

    +
  10. +
+

If at least one of the checks fails then the block must be discarded.

+

Valid tickets bodies, together with the ticket identifiers, are all persisted on-chain +and kept incrementally sorted according to the TicketId interpreted as a 256-bit +big-endian unsigned integer.

+

Pseudo-code for ticket validation for steps 1 and 2:

+
#![allow(unused)]
+fn main() {
+    let ticket_vrf_input = CONCAT(
+        BYTES("sassafras_ticket"),
+        GET(randomness_buffer, 2),
+        BYTES(envelope.body.attempt_index)
+    );
+
+    let result = ring_vrf_verify(
+        verifier,
+        ticket_vrf_input,
+        ENCODE(ticket_body),
+        envelope.ring_signature
+    );
+    assert(result == 1);
+
+    let ticket_id = ring_vrf_signed_output(envelope.ring_signature);
+    assert(ticket_id < ticket_threshold);
+}
+

6.7. Ticket-Slot Binding

+

Before the beginning of the claiming phase (i.e. what we've called the target +epoch), the on-chain list of tickets must be associated with the next epoch's +slots such that there must be at most one ticket per slot.

+

Given an ordered sequence of tickets [t₀, t₁, ..., tₙ] to be assigned to +n slots, the tickets are allocated according to the following outside-in +strategy:

+
    slot_index  : [  0,  1,  2,  3 ,  ... ]
+    tickets     : [ t₀, tₙ, t₁, tₙ₋₁, ... ]
+
+

Here slot-index is a relative value computed as:

+
slot_index = slot - epoch_start_slot
+
+

The association between each ticket and a slot is recorded on-chain and thus +is public. What remains confidential is the identity of the ticket's author, and +consequently, who possesses the validator to claim the corresponding slot. This +information is known only to the author of the ticket.

+

If the number of published tickets is less than the number of epoch slots, +some orphan slots in the end of the epoch will remain unbounded to any ticket. +For claiming strategy refer to 6.8.2. +Note that this situation always apply to the first epochs after genesis.

+

6.8. Slot Claim

+

With tickets bound to epoch slots, every validator acquires information about +the slots for which they are supposed to produce a block.

+

The procedure for slot claiming depends on whether a given slot has an +associated ticket according to the on-chain state.

+

If a slot is associated with a ticket, the primary authoring method is used. +Conversely, the protocol resorts to the secondary method as a fallback.

+

6.8.1. Primary Method

+

We can proceed to claim a slot using the primary method if we are the +legit owner of the ticket associated to the given slot.

+

Let randomness_buffer be the instance of RandomnessBuffer stored in the +chain state and ticket_body be the TicketBody that is associated to the +slot to claim, the VRF input for slot claiming is constructed as:

+
#![allow(unused)]
+fn main() {
+    let seal_vrf_input = CONCAT(
+        BYTES("sassafras_ticket"),
+        GET(randomness_buffer, 3),
+        BYTES(ticket_body.attempt_index)
+    );
+}
+

This seal_vrf_input, when signed with the correct validator secret key must +generate the same TicketId associated on-chain to the target slot.

+

6.8.2. Secondary Method

+

Given that the authorities registered on-chain are kept in an ordered list, +the index of the validator which has the privilege to claim an orphan slot +is given by the following procedure:

+
#![allow(unused)]
+fn main() {
+    let hash_input = CONCAT(
+        GET(randomness_buffer, 2),
+        relative_slot_index,
+    );
+    let hash = BLAKE2(hash_input);
+    let index_bytes = CONCAT(GET(hash, 0), GET(hash, 1), GET(hash, 2), GET(hash, 3));
+    let index = DECODE<Unsigned32>(index_bytes) % LENGTH(authorities);
+}
+

With relative_slot_index the slot offset relative to the epoch's start and authorities +the Sequence of current epoch validators.

+

Let randomness_buffer be the instance of RandomnessBuffer stored in on-chain state +then the VRF input for slot claiming is constructed as:

+
#![allow(unused)]
+fn main() {
+    let seal_vrf_input = CONCAT(
+        BYTES("sassafras_fallback"),
+        GET(randomness_buffer, 3),
+    );
+}
+

6.8.3. Claim Data

+

The slot claim data is a digest entry which contains additional information +which is required by the protocol in order to verify the block:

+
#![allow(unused)]
+fn main() {
+    ClaimData ::= Sequence {
+        slot: Unsigned32,
+        validator_index: Unsigned32,
+        randomness_source: VrfSignature,
+    }
+}
+ +

Given the seal_vrf_input constructed using the primary or secondary method, +the claim is derived as follows:

+
#![allow(unused)]
+fn main() {
+    let randomness_vrf_input = CONCAT(
+        BYTES("sassafras_randomness"),
+        vrf_output(AUTHORITY_SECRET_KEY, seal_vrf_input)
+    );
+
+    let randomness_source = vrf_sign(
+        AUTHORITY_SECRET_KEY,
+        randomness_vrf_input,
+        []
+    );
+
+    let claim = ClaimData {
+        slot,
+        validator_index,
+        randomness_source,
+    }
+}
+

The claim object is SCALE encoded and pushed into the header digest log.

+

6.8.4. Block Seal

+

A block is sealed as follows:

+
#![allow(unused)]
+fn main() {
+    let unsealed_header_bytes = ENCODE(header);
+
+    let seal = vrf_sign(
+        AUTHORITY_SECRET_KEY,
+        seal_vrf_input,
+        unsealed_header_bytes
+    );
+
+    PUSH(header.digest, ENCODE(seal));
+}
+

With header the block's header without the seal digest log entry.

+

The seal object is a VrfSignature instance, which is SCALE encoded and +pushed as the last entry of the block's header digest log.

+

6.9. Slot Claim Verification

+

The last entry is extracted from the header digest log, and is interpreted as +the seal VrfSignature. The unsealed header is then SCALE encoded in order to +be verified.

+

The next entry is extracted from the header digest log, and is interpreted as a +ClaimData instance.

+

The validity of the signatures is then verified using as the public key the +validator key corresponding to the validator_index found in the ClaimData, +together with the VRF input (which depends on primary/secondary method) and +additional data expected to have been used by the block author.

+
#![allow(unused)]
+fn main() {
+    let seal_signature = DECODE<VrfSignature>(POP(header.digest));
+    let unsealed_header_bytes = ENCODE(header);
+    let claim_data = DECODE<ClaimData>(POP(header.digest));
+
+    let public_key = GET(authorities, claim_data.validator_index);
+
+    let result = vrf_verify(
+        public_key,
+        seal_vrf_input,
+        unsealed_header_bytes,
+        seal_signature
+    );
+    assert(result == 1);
+
+    let randomness_vrf_input = vrf_signed_output(seal_signature);
+
+    let result = vrf_verify(
+        public_key,
+        randomness_vrf_input,
+        [],
+        claim_data.randomness_source
+    );
+    assert(result == 1);
+}
+

With:

+ +

If signatures verification is successful, then the verification process diverges +based on whether the slot is associated with a ticket according to the on-chain +state.

+

6.9.1. Primary Method

+

For slots tied to a ticket, the primary verification method is employed. +This method verifies ticket ownership using the TicketId associated to the slot.

+
#![allow(unused)]
+fn main() {
+    let ticket_id = vrf_signed_output(seal_signature);
+    assert(ticket_id == expected_ticket_id);
+}
+

With expected_ticket_id the ticket identifier committed on-chain together +with the associated ticket_body.

+

6.9.2. Secondary Method

+

If the slot doesn't have any associated ticket then the validator index contained in +the claim data must match the one given by the procedure outlined in section +6.8.2.

+

6.10. Randomness Accumulator

+

The randomness accumulator is updated using the randomness_source signature found +within the ClaimData object.

+

In particular, fresh randomness is derived and accumulated after block +execution as follows:

+
#![allow(unused)]
+fn main() {
+    let fresh_randomness = vrf_signed_output(claim.randomness_source);  
+
+    let prev_accumulator = POP(randomness_buffer);
+    let curr_accumulator = BLAKE2(CONCAT(randomness_accumulator, fresh_randomness));
+    PUSH(randomness_buffer, curr_accumulator);
+}
+

7. Drawbacks

+

None

+

8. Testing, Security, and Privacy

+

It is critical that implementations of this RFC undergo thorough testing on +test networks.

+

A security audit may be desirable to ensure the implementation does not +introduce unwanted side effects.

+

9. Performance, Ergonomics, and Compatibility

+

9.1. Performance

+

Adopting Sassafras consensus marks a significant improvement in reducing the +frequency of short-lived forks.

+

Forks are eliminated by design. Forks may only result from network disruptions +or protocol attacks. In such cases, the choice of which fork to follow upon +recovery is clear-cut, with only one valid option.

+

9.2. Ergonomics

+

No specific considerations.

+

9.3. Compatibility

+

The adoption of Sassafras affects the native client and thus can't be introduced +just via a runtime upgrade.

+

A deployment strategy should be carefully engineered for live networks.

+

This subject is left open for a dedicated RFC.

+

10. Prior Art and References

+ +

11. Unresolved Questions

+

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.

+

12.1. Interactions with On-Chain Code

+ +

12.2. Deployment Strategies

+ +

12.3. ZK-SNARK URS Initialization

+ +

12.4. Anonymous Submission of Tickets.

+

(source)

Table of Contents