diff --git a/404.html b/404.html index 76b28b6..4c1d61c 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 02e69f6..6add9ea 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 c460a33..cf959b0 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 5d3f814..1584cf8 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 c81bc8d..d66dde2 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 eac96d8..5963522 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 3db9fae..d8cbae5 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 b57f9e8..524be30 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 545df13..8a071c2 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 3312b71..f030557 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 241f637..115ebe9 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 43c2b2f..ba64e34 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 6a07d97..ab92d2e 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 d60d6c6..4c164ea 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 07a8224..bbdf527 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/0048-session-keys-runtime-api.html b/approved/0048-session-keys-runtime-api.html index bdd3f6d..3e511c1 100644 --- a/approved/0048-session-keys-runtime-api.html +++ b/approved/0048-session-keys-runtime-api.html @@ -90,7 +90,7 @@ diff --git a/approved/0050-fellowship-salaries.html b/approved/0050-fellowship-salaries.html index 220c203..fbe4a85 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 92592df..1c256b6 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 58a9445..1b29abd 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 1377b6b..568ae8c 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 b6d9217..e309148 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 8362ba8..07d1d3c 100644 --- a/index.html +++ b/index.html @@ -90,7 +90,7 @@ diff --git a/introduction.html b/introduction.html index 8362ba8..07d1d3c 100644 --- a/introduction.html +++ b/introduction.html @@ -90,7 +90,7 @@ diff --git a/new/00xx-secondary-marketplace-for-regions.html b/new/00xx-secondary-marketplace-for-regions.html index c352275..ca41098 100644 --- a/new/00xx-secondary-marketplace-for-regions.html +++ b/new/00xx-secondary-marketplace-for-regions.html @@ -90,7 +90,7 @@ diff --git a/print.html b/print.html index 1c093a2..93404d3 100644 --- a/print.html +++ b/print.html @@ -91,7 +91,7 @@ @@ -430,6 +430,882 @@ detailing proposed changes to the technical implementation of the Polkadot netwo

Additionally, I want to express a special thanks to Samuel Haefner and Shahar Dobzinski for fruitful discussions and helping me structure my thoughts.

Unresolved Questions

The technical feasability needs to be assessed.

+

(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 +authorities set while ensuring that the identity of authorities 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 +core 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 +greater Polkadot ecosystem.

+

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 mostly defined using standard ASN.1, +syntax with few exceptions.

+

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

+

3.2. Types Alias

+

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

+ +

3.2. Pseudo-Code

+

It is convenient 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:

+ +

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.

+

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

+

The core protocol operation can be roughly divided into four phases.

+

4.1. Submission of Candidate Tickets

+

Each of the authorities scheduled for the target epoch generate and submits +a set of candidate tickets. Every ticket has an unbiasable pseudo random score +and 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. Valid tickets +are persisted on-chain.

+

4.3. Tickets Slots Binding

+

After collecting all valid candidate tickets and before the beginning of the +target epoch, 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 block production phase of the target epoch, block's author is required +to prove ownership of the ticket associated to the block's slot. This step +discloses the identity of the ticket owner.

+

5. Bandersnatch VRFs Cryptographic Primitives

+

It is 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 the protocol. For a more detailed explanation, refer to +the Bandersnatch VRF +technical specification

+

Bandersnatch VRF comes 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 Bare VRF Interface

+

Function to construct a VrfSignature.

+
#![allow(unused)]
+fn main() {
+    fn vrf_sign(
+        secret: SecretKey,
+        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: SecretKey,
+        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, SecretKey, PublicKey and VrfSignature types are +intentionally left undefined. Their definitions can be found in the Bandersnatch +VRF specification.

+

5.4.2. Ring VRF Interface

+

Function to construct RingVrfSignature.

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

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

+
#![allow(unused)]
+fn main() {
+    fn ring_vrf_verify(
+        verifier: RingVerifier,
+        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(signature) == ring_vrf_signed_output(ring_signature);
+}
+

In this document, the types RingProver, RingVerifier, and RingVrfSignature +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 type contains some parameters to tweak the +protocol behavior 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.5.2.

+

6.2. Header Digest Log

+

Each block's header contains a Digest log, which is defined as an ordered +sequence of DigestItems:

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

The Digest sequence is used to propagate information required for the +correct protocol progress. The information within each DigestItem is opaque +outside the protocol's context and is represented as a SCALE-encoded version of +protocol-specific structures.

+

For Sassafras related entries, the DiegestItems id is set to the ASCII +string "SASS".

+

Possible digest entries for Sassafras:

+ +

If any of the digest entries are found in the wrong place or in a block where +they are not specified as mandatory, then the block is considered invalid.

+

6.3. On-Chain Randomness

+

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

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

During epoch N:

+ +

The buffer's entries are updated after block execution.

+

6.4. Epoch Change Signal

+

The first block produced during epoch N must include a descriptor for some +of the parameters to be used by the subsequent epoch (N+1).

+

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 a DigestItem of the +Digest log.

+

6.4.1. Startup Parameters

+

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

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

The on-chain RandomnessBuffer is initialized after the genesis block. +The first entry is set as the Blake2b hash of the genesis block, each of +the following entry is set as the Blake2b hash of the previous entry.

+

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

+

NextEpochDescriptor for epoch #1:

+ +

6.5. Tickets Creation and Submission

+

During epoch N, each authority scheduled for epoch N+2 constructs a set +of tickets which may be eligible (6.5.2) for on-chain +submission via the relayers, which are the authorities scheduled for epoch N+1.

+

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

+

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

+

The ideal timing for the candidate authority 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 will be unusable if a different chain branch is +chosen as canonical.

+

As said, during epoch N, tickets relayers collect (offchain) tickets targeting +epoch N+2. When epoch N+1 starts, the collected tickets are submitted +on-chain by relayers (which are the authorities scheduled for epoch N+1) as +"inherent extrinsic"s, a special type of mandatory transaction inserted by the +block author at the beginning of the block's transactions sequence.

+

6.5.1. Ticket Identifier

+

Each ticket has an associated identifier defined as:

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

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

+
#![allow(unused)]
+fn main() {
+    let ticket_vrf_input = CONCAT(
+        BYTES("sassafras_ticket_seal"),
+        target_randomness,
+        BYTES(attempt)
+    );
+
+    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 authorities, where v·2/3 ≤ n ≤ v.

+

These n authorities 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 Envelope

+

Each ticket candidate is represented by a TicketEnvelope:

+
#![allow(unused)]
+fn main() {
+    TicketEnvelope ::= Sequence {
+        attempt: Unsigned8,
+        extra: OctetString,
+        signature: RingVrfSignature
+    }   
+}
+

Where:

+ +

The envelope data must be signed using Bandersnatch Ring VRF (5.4.2).

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

With ticket_vrf_input defined as in 6.5.1.

+

6.6. On-chain 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 ring_verifier derived by the static +ring context parameters and the next epoch authorities public keys.

    +
  2. +
  3. +

    TicketId is locally computed from the RingVrfSignature and its value +is checked to be less than tickets' threshold.

    +
  4. +
  5. +

    On-chain tickets submission can't occur within a block part of the +epoch's tail, which encompasses a configurable number of the slots at the end +of the epoch. 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 at the beginning of the target epoch.

    +
  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.

    +
  8. +
  9. +

    No tickets duplicates are allowed.

    +
  10. +
+

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

+

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

+
#![allow(unused)]
+fn main() {
+    let ticket_vrf_input = CONCAT(
+        BYTES("sassafras_ticket_seal"),
+        target_randomness,
+        BYTES(envelope.attempt)
+    );
+
+    let result = ring_vrf_verify(
+        verifier,
+        ticket_vrf_input,
+        envelope.extra,
+        envelope.ring_signature
+    );
+    ASSERT(result == 1);
+
+    let ticket_id = ring_vrf_signed_output(envelope.ring_signature);
+    ASSERT(ticket_id < ticket_threshold);
+}
+

Valid tickets are persisted on-chain in a bounded sorted sequence of +TicketBody objects. Items within this sequence are sorted according to +their TicketId, interpreted as a 256-bit big-endian unsigned integer.

+
#![allow(unused)]
+fn main() {
+    TicketBody ::= Sequence {
+        id: TicketId,
+        attempt: Unsigned8,
+        extra: OctetString,
+    }
+
+    Tickets ::= Sequence<TicketBody>
+}
+

The on-chain tickets sequence bound is set as the epoch length according to the +protocol configuration.

+

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 to the next epoch's +slots such that there is at most one ticket per slot.

+

Given an ordered sequence of tickets [t₀, t₁, ..., tₙ], the tickets are +associated 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 tickets and a slots is recorded on-chain and thus +is public. What remains confidential is the identity of the ticket's author, and +consequently, who is enabled to claim the corresponding slot. This information +is known only to the ticket's author.

+

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

+

6.8. Slot Claim

+

With tickets bounded to the target epoch slots, every designated authority +acquires the information about the slots for which they are required 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 has an associated +ticket, then the primary authoring method is used. Conversely, the protocol +resorts to the secondary method as a fallback.

+

6.8.1. Primary Method

+

An authority, can claim a slot using the primary method if it is the legit +owner of the ticket associated to the given slot.

+

Let target_randomness be the entry in RandomnessBuffer relative to the epoch +the block is targeting and attempt be the attempt used to construct the ticket +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_seal"),
+        target_randomness,
+        BYTES(attempt)
+    );
+}
+

The seal_vrf_input, when signed with the correct authority 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 on-chain in an ordered +list, the index of the authority which has the privilege to claim an orphan +slot is given by the following procedure:

+
#![allow(unused)]
+fn main() {
+    let hash_input = CONCAT(
+        target_randomness,
+        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 target epoch's start +and authorities the sequence of target epoch authorities.

+

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

+
#![allow(unused)]
+fn main() {
+    let seal_vrf_input = CONCAT(
+        BYTES("sassafras_fallback_seal"),
+        target_randomness
+    );
+}
+

6.8.3. Claim Data

+

ClaimData is a digest entry which contains additional information required by +the protocol to verify the block:

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

Given the seal_vrf_input constructed using the primary or secondary method, +the randomness source signature is generated 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 = SlotClaim {
+        slot,
+        authority_index,
+        randomness_source
+    };
+
+    PUSH(block_header.digest, ENCODE(claim));
+}
+

The ClaimData instance is SCALE encoded and pushed as the second-to-last +element of the header digest log.

+

6.8.4. Block Seal

+

A block is finally sealed as follows:

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

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

+

The seal object is a VrfSignature, 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 SCALE decoded as +a VrfSignature object. The unsealed header is then SCALE encoded in order to be +verified.

+

The next entry is extracted from the header digest log, and is SCALE decoded as +a ClaimData object.

+

The validity of the two signatures is assessed using as the authority public key +corresponding to the authority_index found in the ClaimData, together with +the VRF input (which depends on primary/secondary method) and additional data +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.authority_index);
+
+    // Verify seal signature
+    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);
+
+    // Verify per-block entropy source 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 in the +associated TicketBody.

+

6.9.2. Secondary Method

+

If the slot doesn't have any associated ticket then the authority_index contained in +the ClaimData must match the one returned 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);  
+    randomness_buffer[0] = BLAKE2(CONCAT(randomness_buffer[0], fresh_randomness));
+}
+

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 SRS

+ +

12.4. Anonymous Submission of Tickets.

+

(source)

Table of Contents