Files
pezkuwi-fellows/stale/0026-sassafras-consensus.html
T
2024-06-10 00:57:20 +00:00

1082 lines
65 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="polkadot" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>RFC-0026: Sassafras Consensus Protocol - Polkadot Fellowship RFCs</title>
<!-- Custom HTML head -->
<meta name="description" content="An online book of RFCs approved or proposed within the Polkadot Fellowship.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="../theme/polkadot.css">
</head>
<body class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
var path_to_root = "../";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "polkadot" : "polkadot";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('polkadot')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded affix "><a href="../introduction.html">Introduction</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Newly Proposed</li><li class="chapter-item expanded "><a href="../new/00xx-secondary-marketplace-for-regions.html">RFC-0001: Secondary Market for Regions</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Proposed</li><li class="chapter-item expanded "><a href="../proposed/0015-market-design-revisit.html">RFC-0015: Market Design Revisit</a></li><li class="chapter-item expanded "><a href="../proposed/0089-flexible-inflation.html">RFC-0089: Flexible Inflation</a></li><li class="chapter-item expanded "><a href="../proposed/0091-dht-record-creation-time.html">RFC-0091: DHT Authority discovery record creation time</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Approved</li><li class="chapter-item expanded "><a href="../approved/0001-agile-coretime.html">RFC-1: Agile Coretime</a></li><li class="chapter-item expanded "><a href="../approved/0005-coretime-interface.html">RFC-5: Coretime Interface</a></li><li class="chapter-item expanded "><a href="../approved/0007-system-collator-selection.html">RFC-0007: System Collator Selection</a></li><li class="chapter-item expanded "><a href="../approved/0008-parachain-bootnodes-dht.html">RFC-0008: Store parachain bootnodes in relay chain DHT</a></li><li class="chapter-item expanded "><a href="../approved/0010-burn-coretime-revenue.html">RFC-0010: Burn Coretime Revenue</a></li><li class="chapter-item expanded "><a href="../approved/0012-process-for-adding-new-collectives.html">RFC-0012: Process for Adding New System Collectives</a></li><li class="chapter-item expanded "><a href="../approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html">RFC-0013: Prepare Core runtime API for MBMs</a></li><li class="chapter-item expanded "><a href="../approved/0014-improve-locking-mechanism-for-parachains.html">RFC-0014: Improve locking mechanism for parachains</a></li><li class="chapter-item expanded "><a href="../approved/0022-adopt-encointer-runtime.html">RFC-0022: Adopt Encointer Runtime</a></li><li class="chapter-item expanded "><a href="../approved/0032-minimal-relay.html">RFC-0032: Minimal Relay</a></li><li class="chapter-item expanded "><a href="../approved/0042-extrinsics-state-version.html">RFC-0042: Add System version that replaces StateVersion on RuntimeVersion</a></li><li class="chapter-item expanded "><a href="../approved/0043-storage-proof-size-hostfunction.html">RFC-0043: Introduce storage_proof_size Host Function for Improved Parachain Block Utilization</a></li><li class="chapter-item expanded "><a href="../approved/0045-nft-deposits-asset-hub.html">RFC-0045: Lowering NFT Deposits on Asset Hub</a></li><li class="chapter-item expanded "><a href="../approved/0047-assignment-of-availability-chunks.html">RFC-0047: Assignment of availability chunks to validators</a></li><li class="chapter-item expanded "><a href="../approved/0048-session-keys-runtime-api.html">RFC-0048: Generate ownership proof for SessionKeys</a></li><li class="chapter-item expanded "><a href="../approved/0050-fellowship-salaries.html">RFC-0050: Fellowship Salaries</a></li><li class="chapter-item expanded "><a href="../approved/0056-one-transaction-per-notification.html">RFC-0056: Enforce only one transaction per notification</a></li><li class="chapter-item expanded "><a href="../approved/0059-nodes-capabilities-discovery.html">RFC-0059: Add a discovery mechanism for nodes based on their capabilities</a></li><li class="chapter-item expanded "><a href="../approved/0078-merkleized-metadata.html">RFC-0078: Merkleized Metadata</a></li><li class="chapter-item expanded "><a href="../approved/0084-general-transaction-extrinsic-format.html">RFC-0084: General transactions in extrinsic format</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Stale</li><li class="chapter-item expanded "><a href="../stale/0004-remove-unnecessary-allocator-usage.html">RFC-0004: Remove the host-side runtime memory allocator</a></li><li class="chapter-item expanded "><a href="../stale/0006-dynamic-pricing-for-bulk-coretime-sales.html">RFC-0006: Dynamic Pricing for Bulk Coretime Sales</a></li><li class="chapter-item expanded "><a href="../stale/0009-improved-net-light-client-requests.html">RFC-0009: Improved light client requests networking protocol</a></li><li class="chapter-item expanded "><a href="../stale/0020-treasurer-track-confirmation-period-duration-modification.html">RFC-0020: Treasurer Track Confirmation Period Duration Modification</a></li><li class="chapter-item expanded "><a href="../stale/0026-sassafras-consensus.html" class="active">RFC-0026: Sassafras Consensus Protocol</a></li><li class="chapter-item expanded "><a href="../stale/0034-xcm-absolute-location-account-derivation.html">RFC-34: XCM Absolute Location Account Derivation</a></li><li class="chapter-item expanded "><a href="../stale/0035-conviction-voting-delegation-modifications.html"> RFC-0035: Conviction Voting Delegation Modifications</a></li><li class="chapter-item expanded "><a href="../stale/0044-rent-based-registration.html">RFC-0044: Rent based registration model</a></li><li class="chapter-item expanded "><a href="../stale/0054-remove-heap-pages.html">RFC-0054: Remove the concept of "heap pages" from the client</a></li><li class="chapter-item expanded "><a href="../stale/0070-x-track-kusamanetwork.html">RFC-0070: X Track for @kusamanetwork</a></li><li class="chapter-item expanded "><a href="../stale/0073-referedum-deposit-track.html">RFC-0073: Decision Deposit Referendum Track</a></li><li class="chapter-item expanded "><a href="../stale/0074-stateful-multisig-pallet.html">RFC-0074: Stateful Multisig Pallet</a></li><li class="chapter-item expanded "><a href="../stale/0077-increase-max-length-of-identity-pgp-fingerprint-value.html">RFC-0077: Increase maximum length of identity PGP fingerprint values from 20 bytes</a></li><li class="chapter-item expanded "><a href="../stale/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html">RFC-0088: Add slashable locked deposit, purchaser reputation, and reserved cores for on-chain identities to broker pallet</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="polkadot">Polkadot</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Polkadot Fellowship RFCs</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<p><a href="https://github.com/polkadot-fellows/RFCs/pull/26">(source)</a></p>
<p><strong>Table of Contents</strong></p>
<ul>
<li><a href="#rfc-0026-sassafras-consensus-protocol">RFC-0026: Sassafras Consensus Protocol</a>
<ul>
<li><a href="#abstract">Abstract</a></li>
<li><a href="#1-motivation">1. Motivation</a>
<ul>
<li><a href="#11-relevance-to-implementors">1.1. Relevance to Implementors</a></li>
<li><a href="#12-supporting-sassafras-for-polkadot">1.2. Supporting Sassafras for Polkadot</a></li>
</ul>
</li>
<li><a href="#2-stakeholders">2. Stakeholders</a>
<ul>
<li><a href="#21-blockchain-developers">2.1. Blockchain Developers</a></li>
<li><a href="#22-polkadot-ecosystem-contributors">2.2. Polkadot Ecosystem Contributors</a></li>
</ul>
</li>
<li><a href="#3-notation">3. Notation</a>
<ul>
<li><a href="#31-data-structures-definitions">3.1. Data Structures Definitions</a></li>
<li><a href="#32-types-alias">3.2. Types Alias</a></li>
<li><a href="#32-pseudo-code">3.2. Pseudo-Code</a></li>
<li><a href="#33-incremental-introduction-of-types-and-functions">3.3. Incremental Introduction of Types and Functions</a></li>
</ul>
</li>
<li><a href="#4-protocol-introduction">4. Protocol Introduction</a>
<ul>
<li><a href="#41-submission-of-candidate-tickets">4.1. Submission of Candidate Tickets</a></li>
<li><a href="#42-validation-of-candidate-tickets">4.2. Validation of Candidate Tickets</a></li>
<li><a href="#43-tickets-and-slots-binding">4.3. Tickets and Slots Binding</a></li>
<li><a href="#44-claim-of-ticket-ownership">4.4. Claim of Ticket Ownership</a></li>
</ul>
</li>
<li><a href="#5-bandersnatch-vrfs-cryptographic-primitives">5. Bandersnatch VRFs Cryptographic Primitives</a>
<ul>
<li><a href="#51-plain-vrf-interface">5.1 Plain VRF Interface</a></li>
</ul>
</li>
<li><a href="#6-sassafras-protocol">6. Sassafras Protocol</a>
<ul>
<li><a href="#62-header-digest-log">6.2. Header Digest Log</a></li>
<li><a href="#63-on-chain-randomness">6.3. On-Chain Randomness</a></li>
<li><a href="#64-epochs-first-block">6.4. Epoch's First Block</a></li>
<li><a href="#65-offchain-tickets-creation-and-submission">6.5. Offchain Tickets Creation and Submission</a></li>
<li><a href="#66-onchain-tickets-validation">6.6. Onchain Tickets Validation</a></li>
<li><a href="#67-ticket-slot-binding">6.7. Ticket-Slot Binding</a></li>
<li><a href="#68-slot-claim">6.8. Slot Claim</a></li>
<li><a href="#69-slot-claim-verification">6.9. Slot Claim Verification</a></li>
<li><a href="#691-primary-method">6.9.1. Primary Method</a></li>
<li><a href="#610-randomness-accumulator">6.10. Randomness Accumulator</a></li>
</ul>
</li>
<li><a href="#7-drawbacks">7. Drawbacks</a></li>
<li><a href="#8-testing-security-and-privacy">8. Testing, Security, and Privacy</a></li>
<li><a href="#9-performance-ergonomics-and-compatibility">9. Performance, Ergonomics, and Compatibility</a>
<ul>
<li><a href="#91-performance">9.1. Performance</a></li>
<li><a href="#92-ergonomics">9.2. Ergonomics</a></li>
<li><a href="#93-compatibility">9.3. Compatibility</a></li>
</ul>
</li>
<li><a href="#10-prior-art-and-references">10. Prior Art and References</a></li>
<li><a href="#11-unresolved-questions">11. Unresolved Questions</a></li>
<li><a href="#12-future-directions-and-related-material">12. Future Directions and Related Material</a>
<ul>
<li><a href="#121-interactions-with-on-chain-code">12.1. Interactions with On-Chain Code</a></li>
<li><a href="#122-deployment-strategies">12.2. Deployment Strategies</a></li>
<li><a href="#123-zk-snark-urs-initialization">12.3. ZK-SNARK URS Initialization</a></li>
<li><a href="#124-anonymous-submission-of-tickets">12.4. Anonymous Submission of Tickets.</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="rfc-0026-sassafras-consensus-protocol"><a class="header" href="#rfc-0026-sassafras-consensus-protocol">RFC-0026: Sassafras Consensus Protocol</a></h1>
<div class="table-wrapper"><table><thead><tr><th></th><th></th></tr></thead><tbody>
<tr><td><strong>Start Date</strong></td><td>September 06, 2023</td></tr>
<tr><td><strong>Description</strong></td><td>Sassafras consensus protocol specification</td></tr>
<tr><td><strong>Authors</strong></td><td>Davide Galassi</td></tr>
</tbody></table>
</div>
<h2 id="abstract"><a class="header" href="#abstract">Abstract</a></h2>
<p>Sassafras is a novel consensus protocol designed to address the recurring
fork-related challenges encountered in other lottery-based protocols.</p>
<p>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.</p>
<h2 id="1-motivation"><a class="header" href="#1-motivation">1. Motivation</a></h2>
<p>Sassafras Protocol has been rigorously detailed in a comprehensive
<a href="https://eprint.iacr.org/2023/031.pdf">research paper</a> authored by the
<a href="https://web3.foundation">Web3 foundation</a> research team.</p>
<p>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.</p>
<h3 id="11-relevance-to-implementors"><a class="header" href="#11-relevance-to-implementors">1.1. Relevance to Implementors</a></h3>
<p>This RFC focuses on providing implementors with the necessary insights into the
protocol's operation.</p>
<p>In instances of inconsistency between this document and the research paper,
this RFC should be considered authoritative to eliminate ambiguities and ensure
interoperability.</p>
<h3 id="12-supporting-sassafras-for-polkadot"><a class="header" href="#12-supporting-sassafras-for-polkadot">1.2. Supporting Sassafras for Polkadot</a></h3>
<p>Beyond promoting interoperability, this RFC also aims to facilitate the
implementation of Sassafras within the Polkadot ecosystem.</p>
<p>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.</p>
<h2 id="2-stakeholders"><a class="header" href="#2-stakeholders">2. Stakeholders</a></h2>
<h3 id="21-blockchain-developers"><a class="header" href="#21-blockchain-developers">2.1. Blockchain Developers</a></h3>
<p>Developers responsible for creating blockchains who intend to leverage the
benefits offered by the Sassafras Protocol.</p>
<h3 id="22-polkadot-ecosystem-contributors"><a class="header" href="#22-polkadot-ecosystem-contributors">2.2. Polkadot Ecosystem Contributors</a></h3>
<p>Developers contributing to the Polkadot ecosystem, both relay-chain and
para-chains.</p>
<p>The protocol will have a central role in the next generation block authoring
consensus systems.</p>
<h2 id="3-notation"><a class="header" href="#3-notation">3. Notation</a></h2>
<p>This section outlines the notation and conventions adopted throughout this
document to ensure clarity and consistency.</p>
<h3 id="31-data-structures-definitions"><a class="header" href="#31-data-structures-definitions">3.1. Data Structures Definitions</a></h3>
<p>Data structures are primarily defined using standard <a href="https://en.wikipedia.org/wiki/ASN.1">ASN.1</a>,
syntax with few exceptions</p>
<p>To ensure interoperability of serialized structures, the order of the fields
must match the structures definitions found within this document.</p>
<h3 id="32-types-alias"><a class="header" href="#32-types-alias">3.2. Types Alias</a></h3>
<p>We define some type alias to make ASN.1 syntax more intuitive.</p>
<ul>
<li>Unsigned integer: <code>Unsigned ::= INTEGER (0..MAX)</code></li>
<li>n bits unsigned integer: <code>Unsigned&lt;n&gt; ::= INTEGER (0..2^n - 1)</code>
<ul>
<li>8 bits unsigned integer (octet) <code>Unsigned8 ::= Unsigned&lt;8&gt;</code></li>
<li>32 bits unsigned integer: <code>Unsigned32 ::= Unsigned&lt;32&gt;</code></li>
<li>64 bits unsigned integer: <code>Unsigned64 ::= Unsigned&lt;64&gt;</code></li>
</ul>
</li>
<li>Non-homogeneous sequence (struct/tuple): <code>Sequence ::= SEQUENCE</code></li>
<li>Homogeneous sequence (vector): <code>Sequence&lt;T&gt; ::= SEQUENCE OF T</code>
E.g. <code>Sequence&lt;Unsigned&gt; ::= SEQUENCE OF Unsigned</code></li>
<li>Fixed length homogeneous sequence: <code>Sequence&lt;T,n&gt; ::= Sequence&lt;T&gt; (SIZE(n))</code></li>
<li>Octet string alias: <code>OctetString ::= Sequence&lt;Unsigned8&gt;</code></li>
<li>Fixed length octet string: <code>OctetString&lt;n&gt; ::= Sequence&lt;Unsigned8, n&gt;</code></li>
<li>Optional value: <code>Option&lt;T&gt; ::= T OPTIONAL</code></li>
</ul>
<h3 id="32-pseudo-code"><a class="header" href="#32-pseudo-code">3.2. Pseudo-Code</a></h3>
<p>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
<em>Rust</em>, and can make use of the following set of predefined functions:</p>
<p>Syntax:</p>
<ul>
<li>
<p><code>ENCODE(x: T) -&gt; OctetString</code>: encodes <code>x</code> as an <code>OctetString</code> using
<a href="https://github.com/paritytech/parity-scale-codec">SCALE</a> codec.</p>
</li>
<li>
<p><code>DECODE&lt;T&gt;(x: OctetString) -&gt; T</code>: decodes <code>x</code> as a value with type <code>T</code> using
<a href="https://github.com/paritytech/parity-scale-codec">SCALE</a> codec.</p>
</li>
<li>
<p><code>BLAKE2(n: Unsigned, x: OctetString) -&gt; OctetString&lt;n&gt;</code>: standard <em>Blake2b</em> hash.</p>
</li>
<li>
<p><code>CONCAT(x₀: OctetString, ..., xₖ: OctetString) -&gt; OctetString</code>: concatenate the
inputs octets.</p>
</li>
<li>
<p><code>LENGTH(x: Sequence) -&gt; Unsigned</code>: returns the number of elements in <code>x</code>.</p>
</li>
<li>
<p><code>GET(seq: Sequence&lt;T&gt;, i: Unsigned) -&gt; T</code>: returns the i-th element of a sequence.</p>
</li>
<li>
<p><code>PUSH(seq: Sequence&lt;T&gt;, x: T)</code>: append <code>x</code> as the new last element of the sequence.</p>
</li>
<li>
<p><code>POP(seq: Sequence&lt;T&gt;) -&gt; T</code>: extract and returns the last element of a sequence.</p>
</li>
</ul>
<h3 id="33-incremental-introduction-of-types-and-functions"><a class="header" href="#33-incremental-introduction-of-types-and-functions">3.3. Incremental Introduction of Types and Functions</a></h3>
<p>More types and helper functions are introduced incrementally as they become
relevant within the document's context.</p>
<h2 id="4-protocol-introduction"><a class="header" href="#4-protocol-introduction">4. Protocol Introduction</a></h2>
<p>The timeline is segmented into a sequentially ordered sequence of <strong>slots</strong>.
This entire sequence of slots is then further partitioned into distinct segments
known as <strong>epochs</strong>.</p>
<p>The Sassafras protocol aims to map each slot within an epoch to the designated
validators for that epoch, utilizing a ticketing system.</p>
<p>The protocol operation can be roughly divided into five phases:</p>
<h3 id="41-submission-of-candidate-tickets"><a class="header" href="#41-submission-of-candidate-tickets">4.1. Submission of Candidate Tickets</a></h3>
<p>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.</p>
<h3 id="42-validation-of-candidate-tickets"><a class="header" href="#42-validation-of-candidate-tickets">4.2. Validation of Candidate Tickets</a></h3>
<p>Each candidate ticket undergoes a validation process for the associated validity
proof and compliance with other protocol-specific constraints.</p>
<h3 id="43-tickets-and-slots-binding"><a class="header" href="#43-tickets-and-slots-binding">4.3. Tickets and Slots Binding</a></h3>
<p>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.</p>
<h3 id="44-claim-of-ticket-ownership"><a class="header" href="#44-claim-of-ticket-ownership">4.4. Claim of Ticket Ownership</a></h3>
<p>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.</p>
<h2 id="5-bandersnatch-vrfs-cryptographic-primitives"><a class="header" href="#5-bandersnatch-vrfs-cryptographic-primitives">5. Bandersnatch VRFs Cryptographic Primitives</a></h2>
<p>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.</p>
<p>For an in-depth explanation, refer to the Bandersnatch VRF
<a href="https://github.com/davxy/bandersnatch-vrfs-spec">spec</a></p>
<p>Bandersnatch VRF can be used in two flavors:</p>
<ul>
<li><em>Bare</em> VRF: extends the IETF ECVRF <a href="https://datatracker.ietf.org/doc/rfc9381/">RFC 9381</a>,</li>
<li><em>Ring</em> VRF: provides anonymous signatures by leveraging a <em>zk-SNARK</em>.</li>
</ul>
<p>Together with the <em>input</em>, which determines the signed VRF <em>output</em>, both the
flavors offer the capability to sign some arbitrary additional data (<code>extra</code>)
which doesn't contribute to the VRF output.</p>
<h3 id="51-plain-vrf-interface"><a class="header" href="#51-plain-vrf-interface">5.1 Plain VRF Interface</a></h3>
<p>Function to construct a <code>VrfSignature</code>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_sign(
secret: BandernatchSecretKey,
input: OctetString,
extra: OctetString,
) -&gt; VrfSignature
<span class="boring">}</span></code></pre></pre>
<p>Function for signature verification returning a Boolean value indicating the
validity of the signature (<code>1</code> on success):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_verify(
public: PublicKey,
input: OctetString,
extra: OctetString,
signature: VrfSignature
) -&gt; Unsigned&lt;1&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Function to derive the VRF output from input and secret:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_output(
secret: BandernatchSecretKey,
input: OctetString,
) -&gt; OctetString&lt;32&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Function to derive the VRF output from a signature:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_signed_output(
signature: VrfSignature,
) -&gt; OctetString&lt;32&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Note that the following condition is always satisfied:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let signature = vrf_sign(secret, input, extra);
vrf_output(secret, input) == vrf_signed_output(signature)
<span class="boring">}</span></code></pre></pre>
<p>In this document, the types <code>SecretKey</code>, <code>PublicKey</code> and <code>VrfSignature</code> are
intentionally left undefined. Their definitions can be found in the Bandersnatch
VRF specification and related documents.</p>
<h4 id="542-ring-vrf-interface"><a class="header" href="#542-ring-vrf-interface">5.4.2. Ring VRF Interface</a></h4>
<p>Function to construct <code>RingVrfSignature</code>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn ring_vrf_sign(
secret: SecretKey,
prover: RingProverKey,
input: OctetString,
extra: OctetString,
) -&gt; RingVrfSignature;
<span class="boring">}</span></code></pre></pre>
<p>Function for signature verification returning a Boolean value
indicating the validity of the signature (<code>1</code> on success).
Note that this function doesn't require the signer's public key.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn ring_vrf_verify(
verifier: RingVerifierKey,
input: OctetString,
extra: OctetString,
signature: RingVrfSignature,
) -&gt; Unsigned&lt;1&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Function to derive the VRF output from a ring signature:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn ring_vrf_signed_output(
signature: RingVrfSignature,
) -&gt; OctetString&lt;32&gt;;
<span class="boring">}</span></code></pre></pre>
<p>Note that the following condition is always satisfied:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> 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);
<span class="boring">}</span></code></pre></pre>
<p>In this document, the types <code>RingProverKey</code>, <code>RingVerifierKey</code>, and
<code>RingSignature</code> are intentionally left undefined. Their definitions can be found
in the Bandersnatch VRF specification and related documents.</p>
<h2 id="6-sassafras-protocol"><a class="header" href="#6-sassafras-protocol">6. Sassafras Protocol</a></h2>
<h4 id="61-protocol-configuration"><a class="header" href="#61-protocol-configuration">6.1. Protocol Configuration</a></h4>
<p>The <code>ProtocolConfiguration</code> is constant and primarily influences certain checks
carried out during tickets validation. It is defined as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> ProtocolConfiguration ::= Sequence {
epoch_length: Unsigned32,
attempts_number: Unsigned8,
redundancy_factor: Unsigned8,
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>epoch_length</code>: number of slots for each epoch.</li>
<li><code>attempts_number</code>: maximum number of tickets that each validator for the next
epoch is allowed to submit.</li>
<li><code>redundancy_factor</code>: expected ratio between epoch's slots and the cumulative
number of tickets which can be submitted by the set of epoch validators.</li>
</ul>
<p>The <code>attempts_number</code> influences the anonymity of block producers. As all
published tickets have a <strong>public</strong> attempt number less than <code>attempts_number</code>,
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.</p>
<p>Details about how exactly these parameters drives the ticket validity
probability can be found in section <a href="#622-tickets-threshold">6.2.2</a>.</p>
<h3 id="62-header-digest-log"><a class="header" href="#62-header-digest-log">6.2. Header Digest Log</a></h3>
<p>Each block's header contains a <code>Digest</code>, which is a sequence of <code>DigestItems</code>
where the protocol is allowed to append any information required for correct
progress.</p>
<p>The structures are defined to be quite generic and usable by other subsystems:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> DigestItem ::= Sequence {
id: OctetString&lt;4&gt;,
data: OctetString
}
Digest ::= Sequence&lt;DigestItem&gt;
<span class="boring">}</span></code></pre></pre>
<p>For Sassafras related <code>DiegestItem</code>s the <code>id</code> is set to the constant ASCII string <code>&quot;SASS&quot;</code>.</p>
<h3 id="63-on-chain-randomness"><a class="header" href="#63-on-chain-randomness">6.3. On-Chain Randomness</a></h3>
<p>On-Chain, we maintain a sequence with four randomness entries.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> RandomnessBuffer ::= Sequence&lt;OctetString&lt;32&gt;, 4&gt;
<span class="boring">}</span></code></pre></pre>
<p>During epoch <code>N</code></p>
<ul>
<li>
<p>The first entry of the buffer is the current randomness accumulator value
and incorporates verifiable random elements from all previously executed
blocks. The exact accumulation procedure is described in section
<a href="#67-randomness-accumulator">6.7</a>.</p>
</li>
<li>
<p>The second entry of the buffer is the snapshot of the accumulator after the
execution of the last block of epoch <code>N-1</code>.</p>
</li>
<li>
<p>The third entry of the buffer is the snapshot of the accumulator after the
execution of the last block of epoch <code>N-2</code>.</p>
</li>
<li>
<p>The fourth entry of the buffer is the snapshot of the accumulator after the
execution of the last block of epoch <code>N-3</code>.</p>
</li>
</ul>
<p>The buffer is entries are updated <strong>after</strong> block execution.</p>
<h3 id="64-epochs-first-block"><a class="header" href="#64-epochs-first-block">6.4. Epoch's First Block</a></h3>
<p>The first block produced during an epoch <code>N</code> must include a descriptor for some
of the subsequent epoch (<code>N+1</code>) parameters. This descriptor is defined as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> NextEpochDescriptor ::= Sequence {
randomness: OctetString&lt;32&gt;,
authorities: Sequence&lt;PublicKey&gt;,
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>randomness</code>: last randomness accumulator snapshot, which must be equivalent
to <code>GET(RandomnessBuffer, 1)</code> <strong>after</strong> block execution.</li>
<li><code>authorities</code>: list of validators scheduled for next epoch.</li>
</ul>
<p>This descriptor is <code>SCALE</code> encoded and embedded in the block header's digest
log.</p>
<p>A special case arises for the first block of epoch <code>0</code>, which each node produces
independently during the genesis phase. In this case, the <code>NextEpochDescriptor</code>
relative to epoch <code>1</code> is shared within the second block, as outlined in section
<a href="#641-startup-parameters">6.4.1</a>.</p>
<h4 id="641-startup-parameters"><a class="header" href="#641-startup-parameters">6.4.1. Startup Parameters</a></h4>
<p>Some of the initial parameters for the first epoch, Epoch <code>#0</code>, are set through
the genesis configuration, which is defined as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> GenesisConfig ::= Sequence {
authorities: Sequence&lt;PublicKey&gt;,
}
<span class="boring">}</span></code></pre></pre>
<p>The on-chain randomness accumulator is initialized only <strong>after</strong> the genesis
block is produced, and its value is set to the hash of the genesis block.</p>
<p>Since block <code>#0</code> is generated locally by each node as part of the genesis
process, the first block that a validator explicitly produces for Epoch
<code>#0</code> is block <code>#1</code>. Therefore, block <code>#1</code> is required to contain the
<code>NextEpochDescriptor</code> for the following epoch, Epoch <code>#1</code>.</p>
<p>The <code>NextEpochDescriptor</code> for Epoch <code>#1</code>:</p>
<ul>
<li><code>randomness</code>: computed using the <code>randomness_accumulator</code> established
post-genesis, as mentioned above.</li>
<li><code>authorities</code>: the same as those specified in the genesis configuration.</li>
</ul>
<h3 id="65-offchain-tickets-creation-and-submission"><a class="header" href="#65-offchain-tickets-creation-and-submission">6.5. Offchain Tickets Creation and Submission</a></h3>
<p>During epoch <code>N</code>, each validator associated to epoch <code>N+2</code> constructs a set of
tickets which may be eligible (<a href="#652-tickets-threshold">6.5.2</a>) to be delivered
to on-chain proxies, which are the validators scheduled for epoch <code>N+1</code>.</p>
<p>These tickets are constructed using the on-chain randomness snapshot taken
<strong>after</strong> the execution of the last block of epoch <code>N-1</code> together with other
parameters and aims to secure ownership of one or more slots of epoch <code>N+2</code>.</p>
<p>Each validator is allowed to submit a maximum number of tickets, constrained by
<code>attempts_number</code> field of the <code>ProtocolConfiguration</code>.</p>
<p>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 <code>N-1</code> 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.</p>
<p>As said, proxies collect tickets during epoch <code>N</code> and when epoch <code>N+1</code> begins
the collected tickets are submitted on-chain.
TODO (inherents/ unsigned ext?).</p>
<h4 id="651-ticket-identifier"><a class="header" href="#651-ticket-identifier">6.5.1. Ticket Identifier</a></h4>
<p>Each ticket has an associated identifier defined as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> TicketId ::= OctetString&lt;32&gt;;
<span class="boring">}</span></code></pre></pre>
<p>The value of the <code>TicketId</code> is completely determined by the output of the
Bandersnatch VRF with the following <strong>unbiasable</strong> input:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let ticket_vrf_input = CONCAT(
BYTES(&quot;sassafras_ticket&quot;),
GET(randomness_buffer, 1),
BYTES(attempt_index)
);
let ticket_id = vrf_output(AUTHORITY_SECRET_KEY, ticket_vrf_input);
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>randomness_buffer</code>: on-chain <code>RandomnessBuffer</code> instance, in particular we
use the snapshot after the execution of previous epoch's last block.</li>
<li><code>attempt_index</code>: value going from <code>0</code> to the configuration <code>attempts_number - 1</code>.</li>
</ul>
<h4 id="652-tickets-threshold"><a class="header" href="#652-tickets-threshold">6.5.2. Tickets Threshold</a></h4>
<p>A <code>TicketId</code> 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 <code>[0..1]</code>,
is less than the ticket threshold computed as:</p>
<pre><code>T = (r·s)/(a·v)
</code></pre>
<p>Where:</p>
<ul>
<li><code>v</code>: epoch's validators number</li>
<li><code>s</code>: epoch's slots number</li>
<li><code>r</code>: redundancy factor</li>
<li><code>a</code>: attempts number</li>
<li><code>T</code>: ticket threshold value (<code>0 ≤ T ≤ 1</code>)</li>
</ul>
<p>In an epoch with <code>s</code> slots, the goal is to achieve an expected number of tickets
for block production equal to <code>r·s</code>.</p>
<p>It's crucial to ensure that the probability of having fewer than <code>s</code> winning
tickets is very low, even in scenarios where up to <code>1/3</code> of the authorities
might be offline.</p>
<p>To accomplish this, we first define the winning probability of a single ticket
as <code>T = (r·s)/(a·v)</code>.</p>
<p>Let <code>n</code> be the actual number of participating validators, where <code>v·2/3 ≤ n ≤ v</code>.</p>
<p>These <code>n</code> validators each make <code>a</code> attempts, for a total of <code>a·n</code> attempts.</p>
<p>Let <code>X</code> be the random variable associated to the number of winning tickets, then
its expected value is:</p>
<pre><code>E[X] = T·a·n = (r·s·n)/v
</code></pre>
<p>By setting <code>r = 2</code>, we get</p>
<pre><code>s·4/3 ≤ E[X] ≤ s·2
</code></pre>
<p>Using <em>Bernestein's inequality</em> we get <code>Pr[X &lt; s] ≤ e^(-s/21)</code>.</p>
<p>For instance, with <code>s = 600</code> this results in <code>Pr[X &lt; s] &lt; 4·10⁻¹³</code>.
Consequently, this approach offers considerable tolerance for offline nodes and
ensures that all slots are likely to be filled with tickets.</p>
<p>For more details about threshold formula please refer to the
<a href="https://research.web3.foundation/Polkadot/protocols/block-production/SASSAFRAS#probabilities-and-parameters">probabilities and parameters</a>
paragraph in the Web3 foundation description of the protocol.</p>
<h4 id="653-ticket-body"><a class="header" href="#653-ticket-body">6.5.3. Ticket Body</a></h4>
<p>Every ticket candidate has an associated body, defined as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> TicketBody ::= Sequence {
attempt_index: Unsigned8,
opaque: OctetString,
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>attempt_index</code>: index used to generate the associated <code>TicketId</code>.</li>
<li><code>opaque</code>: additional data for user-defined applications.</li>
</ul>
<h4 id="654-ticket-signature"><a class="header" href="#654-ticket-signature">6.5.4. Ticket Signature</a></h4>
<p><code>TicketBody</code> must be signed using the Bandersnatch Ring VRF flavor (<a href="#542-ring-vrf-interface">5.4.2</a>).</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let signature = ring_vrf_sign(
secret_key,
ring_prover_key
ticket_vrf_input,
ENCODE(ticket_body),
);
<span class="boring">}</span></code></pre></pre>
<p><code>ring_prover_key</code> object is constructed using the set of public keys which
belong to the target epoch's validators and the <em>zk-SNARK</em> context parameters
(for more details refer to the Bandersnatch VRFs specification).</p>
<p>Finally, the body and the ring signature are combined within the <code>TicketEnvelope</code>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> TicketEnvelope ::= Sequence {
ticket_body: TicketBody,
ring_signature: RingVrfSignature
}
<span class="boring">}</span></code></pre></pre>
<h3 id="66-onchain-tickets-validation"><a class="header" href="#66-onchain-tickets-validation">6.6. Onchain Tickets Validation</a></h3>
<p>All the actions in the steps described by this paragraph are executed by
on-chain code.</p>
<p>Validation rules:</p>
<ol>
<li>
<p>Ring signature is verified using the on-chain <code>ring_verifier_key</code> derived by the
static ring context parameters and the next epoch validators public keys.</p>
</li>
<li>
<p>Ticket identifier is locally recomputed from the <code>RingVrfSignature</code> and its value
is checked to be less than the tickets' threshold.</p>
</li>
<li>
<p>Tickets submissions can't occur within a block part of the <em>epoch's tail</em>, 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.</p>
</li>
<li>
<p>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. </p>
</li>
<li>
<p>No duplicates are allowed.</p>
</li>
</ol>
<p>If at least one of the checks fails then the block must be discarded.</p>
<p>Valid tickets bodies, together with the ticket identifiers, are all persisted on-chain
and kept incrementally sorted according to the <code>TicketId</code> interpreted as a 256-bit
big-endian unsigned integer.</p>
<p>Pseudo-code for ticket validation for steps 1 and 2:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let ticket_vrf_input = CONCAT(
BYTES(&quot;sassafras_ticket&quot;),
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 &lt; ticket_threshold);
<span class="boring">}</span></code></pre></pre>
<h3 id="67-ticket-slot-binding"><a class="header" href="#67-ticket-slot-binding">6.7. Ticket-Slot Binding</a></h3>
<p>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.</p>
<p>Given an ordered sequence of tickets <code>[t₀, t₁, ..., tₙ]</code> to be assigned to
<code>n</code> slots, the tickets are allocated according to the following <strong>outside-in</strong>
strategy:</p>
<pre><code> slot_index : [ 0, 1, 2, 3 , ... ]
tickets : [ t₀, tₙ, t₁, tₙ₋₁, ... ]
</code></pre>
<p>Here <code>slot-index</code> is a relative value computed as:</p>
<pre><code>slot_index = slot - epoch_start_slot
</code></pre>
<p>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.</p>
<p>If the number of published tickets is less than the number of epoch slots,
some <em>orphan</em> slots in the end of the epoch will remain unbounded to any ticket.
For claiming strategy refer to <a href="#682-secondary-method">6.8.2</a>.
Note that this situation always apply to the first epochs after genesis.</p>
<h3 id="68-slot-claim"><a class="header" href="#68-slot-claim">6.8. Slot Claim</a></h3>
<p>With tickets bound to epoch slots, every validator acquires information about
the slots for which they are supposed to produce a block.</p>
<p>The procedure for slot claiming depends on whether a given slot has an
associated ticket according to the on-chain state.</p>
<p>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.</p>
<h4 id="681-primary-method"><a class="header" href="#681-primary-method">6.8.1. Primary Method</a></h4>
<p>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.</p>
<p>Let <code>randomness_buffer</code> be the instance of <code>RandomnessBuffer</code> stored in the
chain state and <code>ticket_body</code> be the <code>TicketBody</code> that is associated to the
slot to claim, the VRF input for slot claiming is constructed as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let seal_vrf_input = CONCAT(
BYTES(&quot;sassafras_ticket&quot;),
GET(randomness_buffer, 3),
BYTES(ticket_body.attempt_index)
);
<span class="boring">}</span></code></pre></pre>
<p>This <code>seal_vrf_input</code>, when signed with the correct validator secret key must
generate the same <code>TicketId</code> associated on-chain to the target slot.</p>
<h4 id="682-secondary-method"><a class="header" href="#682-secondary-method">6.8.2. Secondary Method</a></h4>
<p>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 <em>orphan</em> slot
is given by the following procedure:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> 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&lt;Unsigned32&gt;(index_bytes) % LENGTH(authorities);
<span class="boring">}</span></code></pre></pre>
<p>With <code>relative_slot_index</code> the slot offset relative to the epoch's start and <code>authorities</code>
the <code>Sequence</code> of current epoch validators.</p>
<p>Let <code>randomness_buffer</code> be the instance of <code>RandomnessBuffer</code> stored in on-chain state
then the VRF input for slot claiming is constructed as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let seal_vrf_input = CONCAT(
BYTES(&quot;sassafras_fallback&quot;),
GET(randomness_buffer, 3),
);
<span class="boring">}</span></code></pre></pre>
<h4 id="683-claim-data"><a class="header" href="#683-claim-data">6.8.3. Claim Data</a></h4>
<p>The slot claim data is a digest entry which contains additional information
which is required by the protocol in order to verify the block:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> ClaimData ::= Sequence {
slot: Unsigned32,
validator_index: Unsigned32,
randomness_source: VrfSignature,
}
<span class="boring">}</span></code></pre></pre>
<ul>
<li><code>slot</code>: the slot number</li>
<li><code>validator_index</code>: block's author index relative to the on-chain validators sequence.</li>
<li><code>randomness_source</code>: VRF signature used to generate per-block randomness.</li>
</ul>
<p>Given the <code>seal_vrf_input</code> constructed using the primary or secondary method,
the claim is derived as follows:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let randomness_vrf_input = CONCAT(
BYTES(&quot;sassafras_randomness&quot;),
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,
}
<span class="boring">}</span></code></pre></pre>
<p>The <code>claim</code> object is <em>SCALE</em> encoded and pushed into the header digest log.</p>
<h4 id="684-block-seal"><a class="header" href="#684-block-seal">6.8.4. Block Seal</a></h4>
<p>A block is sealed as follows:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let unsealed_header_bytes = ENCODE(header);
let seal = vrf_sign(
AUTHORITY_SECRET_KEY,
seal_vrf_input,
unsealed_header_bytes
);
PUSH(header.digest, ENCODE(seal));
<span class="boring">}</span></code></pre></pre>
<p>With <code>header</code> the block's header without the seal digest log entry.</p>
<p>The <code>seal</code> object is a <code>VrfSignature</code> instance, which is <em>SCALE</em> encoded and
pushed as the last entry of the block's header digest log.</p>
<h3 id="69-slot-claim-verification"><a class="header" href="#69-slot-claim-verification">6.9. Slot Claim Verification</a></h3>
<p>The last entry is extracted from the header digest log, and is interpreted as
the seal <code>VrfSignature</code>. The unsealed header is then SCALE encoded in order to
be verified.</p>
<p>The next entry is extracted from the header digest log, and is interpreted as a
<code>ClaimData</code> instance.</p>
<p>The validity of the signatures is then verified using as the public key the
validator key corresponding to the <code>validator_index</code> found in the <code>ClaimData</code>,
together with the VRF input (which depends on primary/secondary method) and
additional data expected to have been used by the block author.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let seal_signature = DECODE&lt;VrfSignature&gt;(POP(header.digest));
let unsealed_header_bytes = ENCODE(header);
let claim_data = DECODE&lt;ClaimData&gt;(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);
<span class="boring">}</span></code></pre></pre>
<p>With:</p>
<ul>
<li><code>header</code>: the block's header.</li>
<li><code>authorities</code>: sequence of authorities for the epoch, as recorded on-chain.</li>
<li><code>seal_vrf_input</code>: VRF seal input data constructed as specified in <a href="#68-slot-claiming">6.8</a>.</li>
</ul>
<p>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.</p>
<h3 id="691-primary-method"><a class="header" href="#691-primary-method">6.9.1. Primary Method</a></h3>
<p>For slots tied to a ticket, the primary verification method is employed.
This method verifies ticket ownership using the <code>TicketId</code> associated to the slot.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> let ticket_id = vrf_signed_output(seal_signature);
assert(ticket_id == expected_ticket_id);
<span class="boring">}</span></code></pre></pre>
<p>With <code>expected_ticket_id</code> the ticket identifier committed on-chain together
with the associated <code>ticket_body</code>.</p>
<h4 id="692-secondary-method"><a class="header" href="#692-secondary-method">6.9.2. Secondary Method</a></h4>
<p>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
<a href="#682-secondary-method">6.8.2</a>.</p>
<h3 id="610-randomness-accumulator"><a class="header" href="#610-randomness-accumulator">6.10. Randomness Accumulator</a></h3>
<p>The randomness accumulator is updated using the <code>randomness_source</code> signature found
within the <code>ClaimData</code> object.</p>
<p>In particular, fresh randomness is derived and accumulated <strong>after</strong> block
execution as follows:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> 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);
<span class="boring">}</span></code></pre></pre>
<h2 id="7-drawbacks"><a class="header" href="#7-drawbacks">7. Drawbacks</a></h2>
<p>None</p>
<h2 id="8-testing-security-and-privacy"><a class="header" href="#8-testing-security-and-privacy">8. Testing, Security, and Privacy</a></h2>
<p>It is critical that implementations of this RFC undergo thorough testing on
test networks.</p>
<p>A security audit may be desirable to ensure the implementation does not
introduce unwanted side effects.</p>
<h2 id="9-performance-ergonomics-and-compatibility"><a class="header" href="#9-performance-ergonomics-and-compatibility">9. Performance, Ergonomics, and Compatibility</a></h2>
<h3 id="91-performance"><a class="header" href="#91-performance">9.1. Performance</a></h3>
<p>Adopting Sassafras consensus marks a significant improvement in reducing the
frequency of short-lived forks.</p>
<p>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.</p>
<h3 id="92-ergonomics"><a class="header" href="#92-ergonomics">9.2. Ergonomics</a></h3>
<p>No specific considerations.</p>
<h3 id="93-compatibility"><a class="header" href="#93-compatibility">9.3. Compatibility</a></h3>
<p>The adoption of Sassafras affects the native client and thus can't be introduced
just via a runtime upgrade.</p>
<p>A deployment strategy should be carefully engineered for live networks.</p>
<p>This subject is left open for a dedicated RFC.</p>
<h2 id="10-prior-art-and-references"><a class="header" href="#10-prior-art-and-references">10. Prior Art and References</a></h2>
<ul>
<li><a href="https://research.web3.foundation/Polkadot/protocols/block-production/SASSAFRAS">Sassafras layman introduction</a></li>
<li><a href="https://eprint.iacr.org/2023/031.pdf">Sassafras research paper</a></li>
<li><a href="https://github.com/davxy/bandersnatch-vrfs-spec">Bandersnatch VRFs specification</a>.</li>
<li><a href="https://github.com/davxy/ark-ec-vrfs">Bandersnatch VRFs reference implementation</a>.</li>
<li><a href="https://eprint.iacr.org/2023/002.pdf">W3F Ring VRF research paper</a></li>
<li><a href="https://github.com/paritytech/substrate/issues/11515">Sassafras reference implementation tracking issue</a></li>
<li><a href="https://github.com/paritytech/substrate/pull/11879">Sassafras reference implementation main PR</a></li>
</ul>
<h2 id="11-unresolved-questions"><a class="header" href="#11-unresolved-questions">11. Unresolved Questions</a></h2>
<p>None</p>
<h2 id="12-future-directions-and-related-material"><a class="header" href="#12-future-directions-and-related-material">12. Future Directions and Related Material</a></h2>
<p>While this RFC lays the groundwork and outlines the core aspects of the
protocol, several crucial topics remain to be addressed in future RFCs.</p>
<h3 id="121-interactions-with-on-chain-code"><a class="header" href="#121-interactions-with-on-chain-code">12.1. Interactions with On-Chain Code</a></h3>
<ul>
<li>
<p><strong>Outbound Interfaces</strong>: Interfaces that the host environment provides to the
on-chain code, typically known as <em>Host Functions</em>.</p>
</li>
<li>
<p><strong>Unrecorded Inbound Interfaces</strong>. Interfaces that the on-chain code provides
to the host environment, typically known as <em>Runtime APIs</em>.</p>
</li>
<li>
<p><strong>Transactional Inbound Interfaces</strong>. Interfaces that the on-chain code provides
to the world to alter the chain state, typically known as <em>Transactions</em>
(or <em>extrinsics</em> in the <em>Polkadot</em> ecosystem)</p>
</li>
</ul>
<h3 id="122-deployment-strategies"><a class="header" href="#122-deployment-strategies">12.2. Deployment Strategies</a></h3>
<ul>
<li><strong>Protocol Migration</strong>. Exploring how this protocol can seamlessly replace an
already operational instance of another protocol. Future RFCs may focus on
deployment strategies to facilitate a smooth transition.</li>
</ul>
<h3 id="123-zk-snark-urs-initialization"><a class="header" href="#123-zk-snark-urs-initialization">12.3. ZK-SNARK URS Initialization</a></h3>
<ul>
<li><strong>Procedure</strong>: Determining the procedure for the <em>zk-SNARK</em> URS (Universal
Reference String) initialization. Future RFCs may provide insights into
whether this process should include an ad-hoc initialization ceremony or if
we can reuse an SRS from another ecosystem (e.g. Zcash or Ethereum).</li>
</ul>
<h3 id="124-anonymous-submission-of-tickets"><a class="header" href="#124-anonymous-submission-of-tickets">12.4. Anonymous Submission of Tickets.</a></h3>
<ul>
<li><strong>Mixnet Integration</strong>: Submitting tickets directly to the relay/proxy can
pose a risk of potential deanonymization through traffic analysis. Subsequent
RFCs may investigate the potential for incorporating Mixnet protocol or
other privacy-enhancing mechanisms to address this concern.</li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../stale/0020-treasurer-track-confirmation-period-duration-modification.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../stale/0034-xcm-absolute-location-account-derivation.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../stale/0020-treasurer-track-confirmation-period-duration-modification.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../stale/0034-xcm-absolute-location-account-derivation.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>