mirror of
https://github.com/pezkuwichain/pezkuwi-fellows.git
synced 2026-05-30 10:31:02 +00:00
1204 lines
74 KiB
HTML
1204 lines
74 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/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">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/0046-metadata-for-offline-signers.html">RFC-0046: Metadata for Offline Signers</a></li><li class="chapter-item expanded "><a href="../proposed/0073-referedum-deposit-track.html">RFC-0073: Decision Deposit Referendum Track</a></li><li class="chapter-item expanded "><a href="../proposed/0074-stateful-multisig-pallet.html">RFC-0074: Stateful Multisig Pallet</a></li><li class="chapter-item expanded "><a href="../proposed/0076-increase-max-length-of-identity-raw-data-values.html">RFC-0076: Increase maximum length of identity raw data values from 32 bytes</a></li><li class="chapter-item expanded "><a href="../proposed/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="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/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="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/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/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/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><li class="chapter-item expanded "><a href="../stale/0066-add-smartcontracts-to-assethub.html">RFC-0066: Add EVM+ink! Contracts Pallets to Asset Hub for Polkadot</a></li><li class="chapter-item expanded "><a href="../stale/0070-x-track-kusamanetwork.html">RFC-0070: X Track for @kusamanetwork</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<n></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<n></code>, it returns the little-endian encoding of the integer
|
|
<code>U<n></code> as <code>n/8</code> octets.</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<p><code>U<n>(x: OCTET_STRING)</code>: returns a <code>U<n></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) -> 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) -> 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) -> 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) -> 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
|
|
) -> 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
|
|
) -> 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
|
|
) -> 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,
|
|
) -> 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,
|
|
) -> 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("SASS")</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("sassafras-ticket-v1.0"),
|
|
[
|
|
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 < s] ≤ e^(-s/21)</code>.</p>
|
|
<p>For instance, with <code>s = 600</code> this results in <code>Pr[X < s] < 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("sassafras-revealed-v1.0"),
|
|
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("sassafras-ticket-body-v1.0"),
|
|
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("sassafras-randomness-v1.0"),
|
|
data: [
|
|
curr_epoch.randomness,
|
|
BYTES(curr_epoch.index),
|
|
BYTES(slot)
|
|
]
|
|
);
|
|
|
|
revealed_vrf_input = vrf_input_from_items(
|
|
domain: BYTES("sassafras-revealed-v1.0"),
|
|
data: [
|
|
curr_epoch.randomness,
|
|
BYTES(curr_epoch.index),
|
|
BYTES(ticket_body.attempt_index)
|
|
]
|
|
);
|
|
|
|
sign_data = vrf_signature_data(
|
|
transcript_label: BYTES("sassafras-claim-v1.0"),
|
|
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("sassafras-claim-v1.0"),
|
|
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("sassafras-revealed-v1.0"),
|
|
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("sassafras-randomness-v1.0"),
|
|
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="../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>
|