Files
pezkuwi-fellows/proposed/0026-sassafras-consensus.html
T
2024-01-08 00:54:12 +00:00

1204 lines
73 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">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/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/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/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="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Newly Proposed</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/0004-remove-unnecessary-allocator-usage.html">RFC-0004: Remove the host-side runtime memory allocator</a></li><li class="chapter-item expanded "><a href="../proposed/0026-sassafras-consensus.html" class="active">RFC-0026: Sassafras Consensus Protocol</a></li><li class="chapter-item expanded "><a href="../proposed/0034-xcm-absolute-location-account-derivation.html">RFC-34: XCM Absolute Location Account Derivation</a></li><li class="chapter-item expanded "><a href="../proposed/0042-extrinsics-state-version.html">RFC-0042: Add System version that replaces StateVersion on RuntimeVersion</a></li><li class="chapter-item expanded "><a href="../proposed/0044-rent-based-registration.html">RFC-0044: Rent based registration model</a></li><li class="chapter-item expanded "><a href="../proposed/0046-metadata-for-offline-signers.html">RFC-0000: Metadata for offline signers</a></li><li class="chapter-item expanded "><a href="../proposed/0047-assignment-of-availability-chunks.html">RFC-0047: Assignment of availability chunks to validators</a></li><li class="chapter-item expanded "><a href="../proposed/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="../proposed/0061-allocator-inside-of-runtime.html">RFC-0061: Support allocator inside of runtime</a></li><li class="chapter-item expanded "><a href="../proposed/0062-lowering-existential-deposit-on-assethub.html">RFC-0062: Lowering Existential Deposit on Asset Hub for Polkadot</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/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/000x-assethub.html">RFC-0000: Lowering NFT Deposits on Polkadot and Kusama Asset Hubs</a></li><li class="chapter-item expanded "><a href="../stale/0010-burn-coretime-revenue.html">RFC-0010: Burn Coretime Revenue</a></li><li class="chapter-item expanded "><a href="../stale/0011-add-new-path-to-account-creation-on-asset-hubs.html">RFC-0011: Add New Path to Account Creation on Asset Hubs</a></li><li class="chapter-item expanded "><a href="../stale/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html">RFC-0013: Prepare BlockBuilder and Core runtime APIs for MBMs</a></li><li class="chapter-item expanded "><a href="../stale/0015-market-design-revisit.html">RFC-0015: Market Design Revisit</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/0035-conviction-voting-delegation-modifications.html"> RFC-0035: Conviction Voting Delegation Modifications</a></li><li class="chapter-item expanded "><a href="../stale/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="../stale/0048-session-keys-runtime-api.html">RFC-0048: Generate ownership proof for SessionKeys</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></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-and-convention">3. Notation and Convention</a>
<ul>
<li><a href="#31-data-structures-definitions-and-encoding">3.1. Data Structures Definitions and Encoding</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>
<li><a href="#45-validation-of-ticket-ownership">4.5. Validation 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-vrf-input">5.1. VRF Input</a></li>
<li><a href="#52-vrf-preoutput">5.2. VRF PreOutput</a></li>
<li><a href="#53-vrf-signature-data">5.3. VRF Signature Data</a></li>
<li><a href="#54-vrf-signature">5.4. VRF Signature</a></li>
</ul>
</li>
<li><a href="#6-sassafras-protocol">6. Sassafras Protocol</a>
<ul>
<li><a href="#61-epochs-first-block">6.1. Epoch's First Block</a></li>
<li><a href="#62-creation-and-submission-of-candidate-tickets">6.2. Creation and Submission of Candidate Tickets</a></li>
<li><a href="#63-validation-of-candidate-tickets">6.3. Validation of candidate tickets</a></li>
<li><a href="#64-ticket-slot-binding">6.4. Ticket-Slot Binding</a></li>
<li><a href="#65-slot-claim-production">6.5. Slot Claim Production</a></li>
<li><a href="#66-slot-claim-verification">6.6. Slot Claim Verification</a></li>
<li><a href="#661-primary-method">6.6.1. Primary Method</a></li>
<li><a href="#67-randomness-accumulator">6.7. 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-srs-initialization">12.3. ZK-SNARK SRS 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 description and structures</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-and-convention"><a class="header" href="#3-notation-and-convention">3. Notation and Convention</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-and-encoding"><a class="header" href="#31-data-structures-definitions-and-encoding">3.1. Data Structures Definitions and Encoding</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>
<ul>
<li>Fixed width integer types are not explicitly defined by ASN.1 standard.
Within this document, <code>U&lt;n&gt;</code> denotes a <code>n</code>-bit unsigned integer.</li>
</ul>
<p>Unless explicitly noted, all types must be serialized using
<a href="https://github.com/paritytech/parity-scale-codec">SCALE</a> codec.</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-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>
<ul>
<li>
<p><code>BYTES(x: T)</code>: returns an <code>OCTET_STRING</code> that represents the raw byte array of
the object x with type T.</p>
<ul>
<li>If <code>T</code> is a <code>VisibleString</code> (ASCII string), it returns the sequence
of octets of its ASCII representation.</li>
<li>If <code>T</code> is <code>U&lt;n&gt;</code>, it returns the little-endian encoding of the integer
<code>U&lt;n&gt;</code> as <code>n/8</code> octets.</li>
</ul>
</li>
<li>
<p><code>U&lt;n&gt;(x: OCTET_STRING)</code>: returns a <code>U&lt;n&gt;</code> interpreting <code>x</code> as the
little-endian encoding of a <code>n</code> bits unsigned integer.</p>
</li>
<li>
<p><code>SCALE(x: T)</code>: returns an <code>OCTET_STRING</code> representing the SCALE encoding of
<code>x</code> with type <code>T</code>.</p>
</li>
<li>
<p><code>BLAKE2(n: U32, x: OCTET_STRING)</code>: returns the standard <em>Blake2b</em> <code>n</code>
bytes hash of <code>x</code> as an <code>OCTET_STRING</code> (note this is not equivalent to the
truncation of the full 64 bytes <em>Blake2b</em> hash).</p>
</li>
<li>
<p><code>CONCAT(x₀: OCTET_STRING, ..., xₖ: OCTET_STRING)</code>: returns the concatenation
of the inputs as an <code>OCTET_STRING</code>.</p>
</li>
<li>
<p><code>LENGTH(x: OCTET_STRING)</code>: returns the number of octets in <code>x</code> as an <code>U32</code>.</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>
<p>We find this approach more agile, especially given that the set of types used is
not overly complex.</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>
<h3 id="45-validation-of-ticket-ownership"><a class="header" href="#45-validation-of-ticket-ownership">4.5. Validation of Ticket Ownership</a></h3>
<p>During block verification, the claim of ticket ownership is validated.</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>This chapter provides a high-level overview of the Bandersnatch VRF primitive as
it relates to the Sassafras protocol.</p>
<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 Ring-VRF
<a href="https://eprint.iacr.org/2023/002.pdf">paper</a> authored by the Web3 foundation
research team.</p>
<h3 id="51-vrf-input"><a class="header" href="#51-vrf-input">5.1. VRF Input</a></h3>
<p>The VRF Input, denoted as <code>VrfInput</code>, is constructed by combining a domain
identifier with arbitrary data through the <code>vrf_input</code> function:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_input(domain: OCTET_STRING, data: OCTET_STRING) -&gt; VrfInput;
<span class="boring">}</span></code></pre></pre>
<p>The specific implementation details of this function are intentionally omitted.
A reference implementation is provided by the
<a href="https://github.com/w3f/ring-vrf/tree/master/bandersnatch_vrfs"><code>bandersnatch_vrfs</code></a>
project.</p>
<TODO>
The above link points to some temporary code (Transcript label set to "TemporaryDoNotDeploy").
Also replace with docs.rs link once published to crates.io.
</TODO>
<p>Helper function to construct a <code>VrfInput</code> from a sequence of <code>data</code> items:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_input_from_items(domain: OCTET_STRING, items: SEQUENCE_OF OCTET_STRING) -&gt; VrfInput {
let data = OCTET_STRING(SIZE = 0); // empty octet string
for item in items {
data.append(item);
data.append(LENGTH(item) as U8);
}
return vrf_input(domain, data);
}
<span class="boring">}</span></code></pre></pre>
<p>Note that each item length is safely casted to an <code>U8</code> as:</p>
<ol>
<li>In the context of this protocol all items lengths are less than 256.</li>
<li>The function is internal and not designed for generic use.</li>
</ol>
<h3 id="52-vrf-preoutput"><a class="header" href="#52-vrf-preoutput">5.2. VRF PreOutput</a></h3>
<p>Functionally, the <code>VrfPreOutput</code> can be considered as a <em>seed</em> for a PRNG to
produce an arbitrary number of output bytes.</p>
<p>It is computed as function of a <code>VrfInput</code> and a <code>BandersnatchSecretKey</code>.</p>
<p>Two different approaches can be used to generate it: as a standalone object
or as part of a signature. While the resulting <code>VrfPreOutput</code> is identical
in both cases, the legitimacy of the latter can be confirmed by verifying the
signature using the <code>BandersnatchPublicKey</code> of the expected signer.</p>
<p>When constructed as a standalone object, <code>VrfPreOutput</code> is primarily employed
in situations where the secret key owner needs to check if the generated output
bytes fulfill some context specific criteria before applying the signature.</p>
<p>To facilitate the construction, the following helper function is provided:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_pre_output(secret: BandernatchSecretKey, input: VrfInput) -&gt; VrfPreOutput;
<span class="boring">}</span></code></pre></pre>
<p>An additional helper function is provided for producing an arbitrary number of
output bytes from <code>VrfInput</code> and <code>VrfPreOutput</code>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_bytes(len: U32, input: VrfInput, pre_output: VrfPreOuput) -&gt; OCTET_STRING;
<span class="boring">}</span></code></pre></pre>
<p>Similar to the <code>vrf_input</code> function, the details about the implementation
of these functions is omitted. Reference implementations are provided by the
<a href="https://github.com/w3f/ring-vrf/tree/master/dleq_vrfs"><code>dleq_vrfs</code></a> project</p>
<ul>
<li><a href="https://docs.rs/dleq_vrf/0.0.1/dleq_vrf/keys/struct.SecretKey.html#method.vrf_preout"><code>vrf_pre_output</code></a></li>
<li><a href="https://docs.rs/dleq_vrf/0.0.1/dleq_vrf/vrf/struct.VrfInOut.html#method.vrf_preoutput_bytes"><code>vrf_bytes</code></a></li>
</ul>
<h3 id="53-vrf-signature-data"><a class="header" href="#53-vrf-signature-data">5.3. VRF Signature Data</a></h3>
<p>This section outlines the data to be signed utilizing the VRF primitive:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> VrfSignatureData ::= SEQUENCE {
transcript: Transcript,
inputs: SEQUENCE_OF VrfInput
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>transcript</code>: a <a href="https://docs.rs/ark-transcript/0.0.1/ark_transcript/struct.Transcript.html"><code>Transcript</code></a>
instance. In practice, this is a <em>special</em> hash of some protocol-specific data
to sign which doesn't influence the <code>VrfPreOutput</code>.</li>
<li><code>inputs</code>: sequence of <code>VrfInputs</code> to be signed.</li>
</ul>
<p>To simplify the construction of <code>VrfSignatureData</code> objects, a helper function is defined:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> fn vrf_signature_data(
transcript_label: OCTET_STRING,
transcript_data: SEQUENCE_OF OCTET_STRING,
inputs: SEQUENCE_OF VrfInput
) -&gt; VrfSignatureData {
let mut transcript = Transcript::new_labeled(transcript_label);
for data in transcript_data {
transcript.append(data);
}
VrfSignatureData { transcript, inputs }
}
<span class="boring">}</span></code></pre></pre>
<h3 id="54-vrf-signature"><a class="header" href="#54-vrf-signature">5.4. VRF Signature</a></h3>
<p>Bandersnatch VRF offers two signature flavors:</p>
<ul>
<li><em>plain</em> signature: much like a traditional <em>Schnorr</em> signature,</li>
<li><em>ring</em> signature: leverages a <em>zk-SNARK</em> to allows for anonymous signatures
using a key from a predefined set of enabled keys, known as the ring.</li>
</ul>
<h4 id="541-plain-vrf-signature"><a class="header" href="#541-plain-vrf-signature">5.4.1. Plain VRF Signature</a></h4>
<p>This section describes the signature process for <code>VrfSignatureData</code> using the
<em>plain</em> signature flavor.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> PlainSignature ::= OCTET_STRING;
VrfSignature ::= SEQUENCE {
signature: PlainSignature,
pre_outputs: SEQUENCE-OF VrfPreOutput
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>signature</code>: the actual plain signature.</li>
<li><code>pre_outputs</code>: sequence of <code>VrfPreOutput</code>s corresponding to the <code>VrfInput</code>s
found within the <code>VrfSignatureData</code>.</li>
</ul>
<p>Helper function to construct <code>VrfPlainSignature</code> from <code>VrfSignatureData</code>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> BandersnatchSecretKey ::= OCTET_STRING;
fn vrf_sign(
secret: BandernatchSecretKey,
signature_data: VrfSignatureData
) -&gt; VrfSignature
<span class="boring">}</span></code></pre></pre>
<p>Helper function for signature verification returning a <code>BOOLEAN</code> value
indicating the validity of the signature (<code>true</code> on success):</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> BandersnatchPublicKey ::= OCTET_STRING;
fn vrf_verify(
public: BandersnatchPublicKey,
signature: VrfSignature
) -&gt; BOOLEAN;
<span class="boring">}</span></code></pre></pre>
<p>In this document, the types <code>BandersnatchSecretKey</code>, <code>BandersnatchPublicKey</code>
and <code>PlainSignature</code> are intentionally left undefined. Their definitions can be
found in the <code>bandersnatch_vrfs</code> reference implementation.</p>
<h4 id="542-ring-vrf-signature"><a class="header" href="#542-ring-vrf-signature">5.4.2. Ring VRF Signature</a></h4>
<p>This section describes the signature process for <code>VrfSignatureData</code> using the
<em>ring</em> signature flavor.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> RingSignature ::= OCTET_STRING;
RingVrfSignature ::= SEQUENCE {
signature: RingSignature,
pre_outputs: SEQUENCE_OF VrfPreOutput
}
<span class="boring">}</span></code></pre></pre>
<ul>
<li><code>signature</code>: the actual ring signature.</li>
<li><code>pre_outputs</code>: sequence of <code>VrfPreOutput</code>s corresponding to the <code>VrfInput</code>s
found within the <code>VrfSignatureData</code>.</li>
</ul>
<p>Helper function to construct <code>RingVrfSignature</code> from <code>VrfSignatureData</code>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> BandersnatchRingProverKey ::= OCTET_STRING;
fn ring_vrf_sign(
secret: BandersnatchRingProverKey,
signature_data: VrfSignatureData,
) -&gt; RingVrfSignature;
<span class="boring">}</span></code></pre></pre>
<p>Helper function for signature verification returning a <code>BOOLEAN</code> value
indicating the validity of the signature (<code>true</code> on success).</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> BandersnatchRingVerifierKey ::= OCTET_STRING;
fn ring_vrf_verify(
verifier: BandersnatchRingVerifierKey,
signature: RingVrfSignature,
) -&gt; BOOLEAN;
<span class="boring">}</span></code></pre></pre>
<p>Note that this function doesn't require the signer's public key.</p>
<p>In this document, the types <code>BandersnatchRingProverKey</code>,
<code>BandersnatchRingVerifierKey</code>, and <code>RingSignature</code> are intentionally left
undefined. Their definitions can be found in the <code>bandersnatch_vrfs</code> reference
implementation.</p>
<h2 id="6-sassafras-protocol"><a class="header" href="#6-sassafras-protocol">6. Sassafras Protocol</a></h2>
<h3 id="61-epochs-first-block"><a class="header" href="#61-epochs-first-block">6.1. Epoch's First Block</a></h3>
<p>For epoch <code>N</code>, the first block produced 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: OCTET_STRING(SIZE(32)),
authorities: SEQUENCE_OF BandersnatchPublicKey,
configuration: ProtocolConfiguration OPTIONAL
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>randomness</code>: 32-bytes pseudo random value.</li>
<li><code>authorities</code>: list of authorities.</li>
<li><code>configuration</code>: optional protocol configuration.</li>
</ul>
<p>This descriptor must be encoded using the <code>SCALE</code> encoding system and embedded
in the block header's digest log. The identifier for the digest element is
<code>BYTES(&quot;SASS&quot;)</code>.</p>
<p>A special case arises for the first block for 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="#613-startup-parameters">6.1.3</a>.</p>
<h4 id="611-epoch-randomness"><a class="header" href="#611-epoch-randomness">6.1.1. Epoch Randomness</a></h4>
<p>The randomness in the <code>NextEpochDescriptor</code> <code>randomness</code> is computed as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> randomness = BLAKE2(32, CONCAT(randomness_accumulator, BYTES(next_epoch.index)));
<span class="boring">}</span></code></pre></pre>
<p>Here, <code>randomness_accumulator</code> refers to a 32-byte <code>OCTET_STRING</code> stored
on-chain and computed through a process that incorporates verifiable random
elements from all previously imported blocks. The exact procedure is described
in section <a href="#67-randomness-accumulator">6.7</a>.</p>
<h4 id="612-protocol-configuration"><a class="header" href="#612-protocol-configuration">6.1.2. Protocol Configuration</a></h4>
<p>The <code>ProtocolConfiguration</code> 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 {
attempts_number: U32,
redundancy_factor: U32
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>attempts_number</code>: maximum number of tickets that each authority 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>
<p><code>ProtocolConfiguration</code> values can be adjusted via a dedicated on-chain call
which should have origin set to <code>Root</code>. Any proposed changes to
<code>ProtocolConfiguration</code> that are submitted in epoch <code>K</code> will be included in the
<code>NextEpochDescriptor</code> at the start of epoch <code>K+1</code> and will come into effect in
epoch <code>K+2</code>.</p>
<h4 id="613-startup-parameters"><a class="header" href="#613-startup-parameters">6.1.3. 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_OF BandersnatchPublicKey,
configuration: ProtocolConfiguration,
}
<span class="boring">}</span></code></pre></pre>
<p>The on-chain randomness accumulator is initialized only <strong>after</strong> the genesis
block is produced. It starts with the hash of the genesis block:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> randomness_accumulator = genesis_hash
<span class="boring">}</span></code></pre></pre>
<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>
<li><code>configuration</code>: not set (i.e., <code>None</code>), implying the reuse of the
one found in the genesis configuration.</li>
</ul>
<h3 id="62-creation-and-submission-of-candidate-tickets"><a class="header" href="#62-creation-and-submission-of-candidate-tickets">6.2. Creation and Submission of Candidate Tickets</a></h3>
<p>After the beginning of a new epoch <code>N</code>, each validator associated to the next
epoch (<code>N+1</code>) constructs a set of tickets which may be eligible (<a href="#622-tickets-threshold">6.2.2</a>)
to be submitted on-chain. These tickets aim to secure ownership of one or more
slots in the upcoming epoch <code>N+1</code>.</p>
<p>Each validator is allowed to submit a maximum number of tickets, as specified by
the <code>attempts_number</code> field in the <code>ProtocolConfiguration</code> for the next epoch.</p>
<p>The ideal timing for a validator to start creating the tickets is subject to
strategy. A recommended approach is to initiate tickets creation once the block
containing the <code>NextEpochDescriptor</code> is either probabilistically or, preferably,
deterministically finalized. This timing is suggested to prevent to waste
resources on tickets that might become obsolete if a different chain branch
is finally chosen as the best one by the distributed system.</p>
<p>However, validators are also advised to avoid submitting tickets too late,
as tickets submitted during the second half of the epoch must be discarded.</p>
<h4 id="621-ticket-identifier-value"><a class="header" href="#621-ticket-identifier-value">6.2.1. Ticket Identifier Value</a></h4>
<p>Each ticket has an associated 128-bit unique 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 ::= U128;
<span class="boring">}</span></code></pre></pre>
<p>The value of the <code>TicketId</code> is determined by the output of the Bandersnatch VRF
with the following input:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> ticket_id_vrf_input = vrf_input_from_items(
BYTES(&quot;sassafras-ticket-v1.0&quot;),
[
next_epoch.randomness,
BYTES(next_epoch.index),
BYTES(attempt_index)
]
);
ticket_id_vrf_pre_output = vrf_pre_output(AUTHORITY_SECRET_KEY, ticket_id_vrf_input);
ticket_bytes = vrf_bytes(16, ticket_id_vrf_input, ticket_id_vrf_pre_output);
ticket_id = U128(ticket_bytes);
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>next_epoch.randomness</code>: randomness associated to the target epoch.</li>
<li><code>next_epoch.index</code>: index of the target epoch as a <code>U64</code>.</li>
<li><code>attempt_index</code>: value going from <code>0</code> to <code>attempts_number</code> as a <code>U32</code>.</li>
</ul>
<h4 id="622-tickets-threshold"><a class="header" href="#622-tickets-threshold">6.2.2. Tickets Threshold</a></h4>
<p>A <code>TicketId</code> value is valid if its value is less than the ticket threshold:</p>
<pre><code>T = (r·s)/(a·v)
</code></pre>
<p>Where:</p>
<ul>
<li><code>v</code>: epoch's authorities (aka 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>
<h5 id="6221-formula-derivation"><a class="header" href="#6221-formula-derivation">6.2.2.1 Formula Derivation</a></h5>
<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="623-ticket-body"><a class="header" href="#623-ticket-body">6.2.3. Ticket Body</a></h4>
<p>Every candidate ticket identifier 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: U32,
erased_pub: Ed25519PublicKey,
revealed_pub: Ed25519PublicKey
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>attempt_index</code>: attempt index used to generate the associated <code>TicketId</code>.</li>
<li><code>erased_pub</code>: Ed25519 ephemeral public key which gets erased as soon as the
ticket is claimed. This key can be used to encrypt data for the validator.</li>
<li><code>revealed_pub</code>: Ed25519 ephemeral public key which gets exposed as soon as the
ticket is claimed.</li>
</ul>
<p>The process of generating an erased key pair is intentionally left undefined,
allowing the implementor the freedom to choose the most suitable strategy.</p>
<p>Revealed key pair is generated using the bytes produced by the VRF with input
parameters equal to those employed in <code>TicketId</code> generation, only the label
is different.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> revealed_vrf_input = vrf_input_from_items(
domain: BYTES(&quot;sassafras-revealed-v1.0&quot;),
data: [
next_epoch.randomness,
BYTES(next_epoch.index),
BYTES(attempt_index)
]
);
revealed_vrf_pre_output = vrf_pre_output(AUTHORITY_SECRET_KEY, revealed_vrf_input);
revealed_seed = vrf_bytes(32, revealed_vrf_input, revealed_vrf_pre_output);
revealed_pub = ed25519_secret_from_seed(revealed_seed).public();
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>next_epoch.randomness</code>: randomness associated to the target epoch.</li>
<li><code>next_epoch.index</code>: index of the target epoch as a <code>U64</code>.</li>
<li><code>attempt_index</code>: value going from <code>0</code> to <code>attempts_number</code> as a <code>U32</code>.</li>
</ul>
<p>The ephemeral public keys are also used for claiming the tickets on block production.
Refer to section <a href="#65-slot-claim-production">6.5</a> for details.</p>
<h4 id="624-ring-signature-production"><a class="header" href="#624-ring-signature-production">6.2.4. Ring Signature Production</a></h4>
<p><code>TicketBody</code> must be signed using the Bandersnatch ring VRF flavor (<a href="#542-ring-vrf-signature">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> sign_data = vrf_signature_data(
transcript_label: BYTES(&quot;sassafras-ticket-body-v1.0&quot;),
transcript_data: [
SCALE(ticket_body)
],
inputs: [
ticket_id_vrf_input
]
)
ring_signature = ring_vrf_sign(AUTHORITY_SECRET_KEY, RING_PROVER_KEY, sign_data)
<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 authorities and the <em>zk-SNARK</em> context parameters
(for more details refer to the
<a href="https://github.com/w3f/ring-vrf/blob/18614458ca4cb335c88d4e710c13906a76f51e43/bandersnatch_vrfs/src/ring.rs#L91-L93">bandersnatch_vrfs</a>
reference implementation).</p>
<p>The body and the ring signature are combined in the <code>TicketEnvelope</code> structure:</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>
<p>All the envelopes corresponding to valid tickets can be submitted on-chain via a
dedicated on-chain call (extrinsic).</p>
<h3 id="63-validation-of-candidate-tickets"><a class="header" href="#63-validation-of-candidate-tickets">6.3. Validation of candidate tickets</a></h3>
<p>All the actions in the steps described by this paragraph are executed by
on-chain code.</p>
<p>Validation rules:</p>
<ul>
<li>Tickets submissions must occur within a block part of the first half of the epoch.</li>
<li>Ring signature is verified using the on-chain <code>RING_VERIFIER_KEY</code>.</li>
<li>Ticket identifier is locally (re)computed from the <code>VrfPreOutput</code> contained in the
<code>RingVrfSignature</code> and its value is checked to be less than the tickets' threshold.</li>
</ul>
<p>Valid tickets bodies are all persisted on-chain.</p>
<h3 id="64-ticket-slot-binding"><a class="header" href="#64-ticket-slot-binding">6.4. Ticket-Slot Binding</a></h3>
<p>Before the beginning of the next 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>The assignment process happens in the second half of the submission epoch and
follows these steps:</p>
<ul>
<li>Sorting: The complete list of tickets is sorted based on their <code>TicketId</code>
value, with smaller values coming first.</li>
<li>Trimming: In scenarios where there are more tickets than available slots, the
list is trimmed to fit the epoch's slots by removing the larger value.</li>
<li>Assignment: Tickets are assigned to the epoch's slots following an
<em>outside-in</em> strategy.</li>
</ul>
<h4 id="641-outside-in-assignment"><a class="header" href="#641-outside-in-assignment">6.4.1. Outside-In Assignment</a></h4>
<p>Given an ordered sequence of tickets <code>[t0, t1, t2, ..., tk]</code> to be assigned to
<code>n</code> slots, where <code>n ≥ k</code>, the tickets are allocated according to the following
strategy:</p>
<pre><code> slot-index : [ 0, 1, 2, ............ , n ]
tickets : [ t1, t3, t5, ... , t4, t2, t0 ]
</code></pre>
<p>Here <code>slot-index</code> is a relative value computed as:</p>
<pre><code>slot-index = absolute_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 authority to claim the corresponding slot. This
information is known only to the author of the ticket.</p>
<p>In case the number of available tickets is less than the number of epoch slots,
some <em>orphan</em> slots in the middle of the epoch will remain unbounded to any
ticket. For claiming strategy refer to <a href="#652-secondary-method">6.5.2</a>.</p>
<h3 id="65-slot-claim-production"><a class="header" href="#65-slot-claim-production">6.5. Slot Claim Production</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="651-primary-method"><a class="header" href="#651-primary-method">6.5.1. Primary Method</a></h4>
<p>Let <code>ticket_body</code> be the <code>TicketBody</code> that has been committed to the on-chain
state, <code>curr_epoch</code> denote an object containing information about the current
epoch, and <code>slot</code> represent the slot number (absolute).</p>
<p>Follows the construction of <code>VrfSignatureData</code>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> randomness_vrf_input = vrf_input_from_items(
domain: BYTES(&quot;sassafras-randomness-v1.0&quot;),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.index),
BYTES(slot)
]
);
revealed_vrf_input = vrf_input_from_items(
domain: BYTES(&quot;sassafras-revealed-v1.0&quot;),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.index),
BYTES(ticket_body.attempt_index)
]
);
sign_data = vrf_signature_data(
transcript_label: BYTES(&quot;sassafras-claim-v1.0&quot;),
transcript_data: [
SCALE(ticket_body)
],
inputs: [
randomness_vrf_input,
revealed_vrf_input
]
);
<span class="boring">}</span></code></pre></pre>
<h5 id="6511-ephemeral-key-claim"><a class="header" href="#6511-ephemeral-key-claim">6.5.1.1. Ephemeral Key Claim</a></h5>
<p><em>Fiat-Shamir</em> transform is used to obtain a 32-byte challenge associated with
the <code>VrfSignData</code> transcript.</p>
<p>Validators employ the secret key associated with <code>erased_pub</code>, which has been
committed in the <code>TicketBody</code>, to sign the challenge.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> challenge = sign_data.transcript.challenge();
erased_signature = ed25519_sign(ERASED_SECRET_KEY, challenge);
<span class="boring">}</span></code></pre></pre>
<p>As ticket's ownership can be claimed by reconstructing the <code>revealed_pub</code> entry
of the committed <code>TicketBody</code>, this step is considered optional.</p>
<TODO>
Is this step really necessary?
- Isn't better to keep it simple if this step doesn't offer any extra security?
- We already have a strong method to claim ticket ownership using the vrf output
- What if a validator provides both the proofs?
More weight for the branch (i.e. used to decide what is the best branch by validators)?
E.g.
- primary method + ed25519 erased signature => score 2
- primary method => score 1
- fallback method => score 0
</TODO>
<h4 id="652-secondary-method"><a class="header" href="#652-secondary-method">6.5.2. Secondary Method</a></h4>
<p>By noting that the authorities registered on-chain are kept in an ordered list,
the index of the authority which has the privilege to claim an <em>orphan</em> slot is:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> index_bytes = BLAKE2(4, CONCAT(epoch_randomness, BYTES(slot)));
index = U32(index_bytes) mod authorities_number;
<span class="boring">}</span></code></pre></pre>
<p>Given <code>randomness_vrf_input</code> constructed as shown for the primary method (<a href="#651-primary-method">6.5.1</a>),
the <code>VrfSignatureData</code> is constructed as:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> sign_data = vrf_signature_data(
transcript_label: BYTES(&quot;sassafras-claim-v1.0&quot;),
transcript_data: [ ],
inputs: [
randomness_vrf_input
]
)
<span class="boring">}</span></code></pre></pre>
<h4 id="653-slot-claim-object"><a class="header" href="#653-slot-claim-object">6.5.3. Slot Claim Object</a></h4>
<p>The <code>SlotClaim</code> structure is used to contain all the necessary information to
assess ownership of a slot.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> SlotClaim ::= SEQUENCE {
authority_index: U32,
slot: U64,
signature: VrfSignature,
erased_signature: Ed25519Signature OPTIONAL
}
<span class="boring">}</span></code></pre></pre>
<p>The claim is constructed as follows:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> signature = vrf_sign(AUTHORITY_SECRET_KEY, sign_data);
claim = SlotClaim {
authority_index,
slot,
signature,
erased_signature
}
<span class="boring">}</span></code></pre></pre>
<p>Where:</p>
<ul>
<li><code>authority_index</code>: index of the block author in the on-chain authorities list.</li>
<li><code>slot</code>: slot number (absolute, not relative to the epoch start)</li>
<li><code>signature</code>: signature relative to the <code>sign_data</code> constructed via the
primary <a href="#651-primary-method">6.5.1</a> or secondary (<a href="#652-secondary-method">6.5.2</a>) method.</li>
<li><code>erased_signature</code>: optional signature providing an additional proof of ticket
ownership (<a href="#6511-ephemeral-key-claim">6.5.1.1</a>).</li>
</ul>
<p>The signature includes one or two <code>VrfPreOutputs</code>.</p>
<ul>
<li>The first is always present and is used to generate per-block randomness
to feed the randomness accumulator (<a href="#67-randomness-accumulator">6.7</a>).</li>
<li>The second is included if the slot is bound to a ticket. This is relevant to
claim ticket ownership (<a href="#661-primary-method">6.6.1</a>).</li>
</ul>
<p>The <code>claim</code> object is <em>SCALE</em> encoded and sent in the block's header digest log.</p>
<h3 id="66-slot-claim-verification"><a class="header" href="#66-slot-claim-verification">6.6. Slot Claim Verification</a></h3>
<p>The signature within the <code>SlotClaim</code> is verified using a <code>VrfSignData</code>
constructed as specified in <a href="#65-slot-claim-production">6.5</a>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> public_key = authorities[claim.authority_index];
result = vrf_verify(public_key, sign_data, claim.signature);
assert(result == true);
<span class="boring">}</span></code></pre></pre>
<p>With:</p>
<ul>
<li><code>authorities</code>: list of authorities for the epoch, as recorded on-chain.</li>
<li><code>sign_data</code>: data that has been signed, constructed as specified in <a href="#65-slot-claim-production">6.5</a>.</li>
</ul>
<p>If signature verification is successful, the validation process then diverges
based on whether the slot is associated with a ticket according to the on-chain
state.</p>
<p>For slots tied to a ticket, the primary verification method is employed. Otherwise,
the secondary method is utilized.</p>
<h3 id="661-primary-method"><a class="header" href="#661-primary-method">6.6.1. Primary Method</a></h3>
<p>This method verifies ticket ownership using the second <code>VrfPreOutput</code> from the
<code>SlotClaim</code> signature</p>
<p>The process involves comparing the <code>revealed_pub</code> key from the committed
<code>TicketBody</code> with a reconstructed key using the <code>VrfPreOutput</code> and the expected
<code>VrfInput</code>. A mismatch indicates an illegitimate claim.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> revealed_vrf_input = vrf_input_from_items(
domain: BYTES(&quot;sassafras-revealed-v1.0&quot;),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.index),
BYTES(ticket_body.attempt_index)
]
);
reveled_vrf_pre_output = claim.signature.pre_outputs[1];
revealed_seed = vrf_bytes(32, revealed_vrf_input, revealed_vrf_pre_output);
revealed_pub = ed25519_secret_from_seed(revealed_seed).public();
assert(revealed_pub == ticket_body.revealed_pub);
<span class="boring">}</span></code></pre></pre>
<h5 id="6611-ephemeral-key-signature-check"><a class="header" href="#6611-ephemeral-key-signature-check">6.6.1.1. Ephemeral Key Signature Check</a></h5>
<p>If the <code>erased_signature</code> is present in <code>SlotClaim</code>, the <code>erased_pub</code> within the
committed <code>TicketBody</code> key is used to verify it.</p>
<p>The signed challenge is generated as outlined in section <a href="#6511-ephemeral-key-claim">6.5.1.1</a>.</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> challenge = sign_data.transcript.challenge();
result = ed25519_verify(ticket_body.erased_pub, challenge, claim.erased_signature);
assert(result == true);
<span class="boring">}</span></code></pre></pre>
<h4 id="662-secondary-method"><a class="header" href="#662-secondary-method">6.6.2. Secondary Method</a></h4>
<p>If the slot doesn't have any associated ticket then the validator index contained in
the claim should match the one given by the rule outlined in section <a href="#652-secondary-method">6.5.2</a>.</p>
<h3 id="67-randomness-accumulator"><a class="header" href="#67-randomness-accumulator">6.7. Randomness Accumulator</a></h3>
<p>The first <code>VrfPreOutput</code> which ships within the block's <code>SlotClaim</code> signature
is mandatory and must be used as entropy source for the randomness which gets
accumulated on-chain <strong>after</strong> block transactions execution.</p>
<p>Given <code>claim</code> the instance of <code>SlotClaim</code> found within the block header, and
<code>randomness_accumulator</code> the current value for the randomness accumulator, the
<code>randomness_accumulator</code> value is updated as follows:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span> randomness_vrf_input = vrf_input_from_items(
domain: BYTES(&quot;sassafras-randomness-v1.0&quot;),
data: [
curr_epoch.randomness,
BYTES(curr_epoch.index),
BYTES(slot)
]
);
randomness_vrf_pre_output = claim.signature.pre_outputs[0];
randomness = vrf_bytes(32, randomness_vrf_input, randomness_vrf_pre_output);
randomness_accumulator = BLAKE2(32, CONCAT(randomness_accumulator, randomness));
<span class="boring">}</span></code></pre></pre>
<p>The <code>randomness_accumulator</code> never resets and is a continuously evolving value.
It primarily serves as a basis for calculating the randomness associated to the
epochs as outlined on section <a href="#61-epochs-first-block">6.1</a>, but custom usages
from the user are not excluded.</p>
<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">Web3 Foundation research page</a></li>
<li><a href="https://eprint.iacr.org/2023/031.pdf">Sassafras research paper</a></li>
<li><a href="https://eprint.iacr.org/2023/002.pdf">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>
<li><a href="https://github.com/w3f/ring-vrf/tree/master/bandersnatch_vrfs">Bandersnatch VRFS reference implementation</a></li>
</ul>
<TODO replace bandersnatch-vrfs with docs.rs link once published />
<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.
These include:</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 code, 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 should focus on
deployment strategies to facilitate a smooth transition.</li>
</ul>
<h3 id="123-zk-snark-srs-initialization"><a class="header" href="#123-zk-snark-srs-initialization">12.3. ZK-SNARK SRS Initialization</a></h3>
<ul>
<li>
<p><strong>Procedure</strong>: Determining the procedure for the <em>zk-SNARK</em> SRS (Structured
Reference String) initialization. Future RFCs should 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).</p>
</li>
<li>
<p><strong>Sharing with Para-chains</strong>: Considering the complexity of the process, we
must understand whether the SRS is shared with system para-chains or
maintained independently.</p>
</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 can pose a risk of
potential deanonymization through traffic analysis. Subsequent RFCs should
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="../proposed/0004-remove-unnecessary-allocator-usage.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="../proposed/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="../proposed/0004-remove-unnecessary-allocator-usage.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="../proposed/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>