mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
597 lines
23 KiB
597 lines
23 KiB
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<!-- Begin Jekyll SEO tag v2.5.0 -->
|
|
<title>How Atlas Works | Blockstack</title>
|
|
<meta name="generator" content="Jekyll v3.8.3" />
|
|
<meta property="og:title" content="How Atlas Works" />
|
|
<meta name="author" content="Blockstack" />
|
|
<meta property="og:locale" content="en_US" />
|
|
<meta name="description" content="How Atlas Works" />
|
|
<meta property="og:description" content="How Atlas Works" />
|
|
<link rel="canonical" href="https://zbabystack.netlify.com/core/auth/howitworks.html" />
|
|
<meta property="og:url" content="https://zbabystack.netlify.com/core/auth/howitworks.html" />
|
|
<meta property="og:site_name" content="Blockstack" />
|
|
<meta property="og:type" content="article" />
|
|
<meta property="article:published_time" content="2018-09-04T10:14:49-07:00" />
|
|
<script type="application/ld+json">
|
|
{"description":"How Atlas Works","author":{"@type":"Person","name":"Blockstack"},"@type":"BlogPosting","url":"https://zbabystack.netlify.com/core/auth/howitworks.html","headline":"How Atlas Works","dateModified":"2018-09-04T10:14:49-07:00","datePublished":"2018-09-04T10:14:49-07:00","mainEntityOfPage":{"@type":"WebPage","@id":"https://zbabystack.netlify.com/core/auth/howitworks.html"},"@context":"http://schema.org"}</script>
|
|
<!-- End Jekyll SEO tag -->
|
|
|
|
<!-- <meta property="og:image" content="https://zbabystack.netlify.com/assets/posts/logo.png"/> -->
|
|
<meta property="og:image" content="/assets/posts/logo.png"/>
|
|
<link rel="stylesheet" href="/assets/css/main.css">
|
|
<link rel="shortcut icon" type="image/png" href="/assets/img/favicon.png" >
|
|
<link rel="alternate" type="application/rss+xml" title="Blockstack" href="/feed.xml">
|
|
<script src="/assets/js/main.js"></script>
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
<header class="uk-background-secondary">
|
|
<div data-uk-sticky="sel-target: .uk-navbar-container; cls-active: uk-navbar-sticky" class="uk-sticky uk-sticky-fixed" style="position: fixed; top: 0px; width: 1904px;">
|
|
<nav class="uk-navbar-container">
|
|
<div class="uk-container">
|
|
<div data-uk-navbar>
|
|
<div class="uk-navbar-left">
|
|
|
|
<!-- <a class="uk-navbar-item uk-logo" href="/"><img src="https://zbabystack.netlify.com/assets/posts/logo.png" alt="Docs"></a> -->
|
|
<a class="uk-navbar-item uk-logo" href="/"><img src="/assets/posts/logo.png" alt="Docs"></a>
|
|
|
|
</div>
|
|
<div class="uk-navbar-right">
|
|
<ul class="uk-navbar-nav uk-visible@m">
|
|
|
|
|
|
|
|
|
|
<li><a href="https://blockstack.org" target="_blank" >Blockstack.org</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
<li><a href="https://forum.blockstack.org/" target="_blank" >Forums</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
<li><a href="https://github.com/blockstack" target="_blank" >GitHub</a></li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
<div>
|
|
<a class="uk-navbar-toggle" uk-search-icon href="#"></a>
|
|
<div class="uk-drop uk-background-default uk-border-rounded" uk-drop="mode: click; pos: left-center; offset: 0">
|
|
<form class="uk-search uk-search-navbar uk-width-1-1" onsubmit="return false;">
|
|
<input id="searchBox" class="uk-search-input" type="search" placeholder="Search..." autofocus>
|
|
</form>
|
|
<ul id="searchBox-results" class="uk-position-absolute uk-width-1-1 uk-list"></ul>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
SimpleJekyllSearch({
|
|
searchInput: document.getElementById('searchBox'),
|
|
resultsContainer: document.getElementById('searchBox-results'),
|
|
noResultsText: '<li>No results found</li>',
|
|
searchResultTemplate: '<li><a href="{url}">{title}</a></li>',
|
|
json: '/search.json'
|
|
});
|
|
</script>
|
|
|
|
|
|
<a class="uk-navbar-toggle uk-hidden@m" href="#offcanvas" data-uk-navbar-toggle-icon data-uk-toggle></a>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<div class="uk-section">
|
|
<div class="uk-container">
|
|
<div class="uk-grid-large" data-uk-grid>
|
|
|
|
<div class="sidebar-fixed-width uk-visible@m">
|
|
<div class="sidebar-docs uk-position-fixed">
|
|
<!-- -->
|
|
|
|
|
|
<ul class="uk-nav uk-nav-default doc-nav">
|
|
|
|
|
|
<!-- -->
|
|
|
|
<li class=""><a href="/core/auth/overview.html">Overview of the Atlas network</a></li>
|
|
|
|
|
|
<!-- -->
|
|
|
|
<li class="uk-active"><a href="/core/auth/howitworks.html">How Atlas Works</a></li>
|
|
|
|
|
|
<!-- -->
|
|
|
|
<li class=""><a href="/core/auth/howtouse.html">How to Use the Atlas Network</a></li>
|
|
|
|
</ul>
|
|
|
|
<!-- -->
|
|
</div>
|
|
</div>
|
|
|
|
<div class="uk-width-1-1 uk-width-expand@m">
|
|
|
|
<article class="uk-article">
|
|
|
|
<h1 class="uk-article-title">How Atlas Works</h1>
|
|
|
|
|
|
|
|
|
|
<div class="uk-article-meta uk-margin-top uk-margin-medium-bottom">
|
|
|
|
|
|
|
|
<!-- <img class="avatar avatar-small" alt="Blockstack" width="32" height="32" data-proofer-ignore="true" src="https://avatars2.githubusercontent.com/Blockstack?v=3&s=32" srcset="https://avatars2.githubusercontent.com/Blockstack?v=3&s=32 1x, https://avatars2.githubusercontent.com/Blockstack?v=3&s=64 2x, https://avatars2.githubusercontent.com/Blockstack?v=3&s=96 3x, https://avatars2.githubusercontent.com/Blockstack?v=3&s=128 4x" /> -->
|
|
|
|
|
|
|
|
<!-- Written by <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">Blockstack</span></span><br> -->
|
|
|
|
|
|
<time datetime="2018-09-04T10:14:49-07:00" itemprop="datePublished">
|
|
|
|
<a "target="_blank" href="https://github.com/moxiegirl/docs-new/blob/master/_core/auth/howitworks.md" class="btn btn-default githubEditButton" role="button"><span data-uk-icon="icon: pencil; ratio: 1.2"></span> Edit this page on Github</a>
|
|
<span style="font-family:Wingdings">w</span> Sep 4, 2018
|
|
</time>
|
|
|
|
</div>
|
|
|
|
<div class="article-content">
|
|
|
|
<p class="no_toc">Atlas was designed to overcome the structural weaknesses inherent to all
|
|
distributed hash tables. In particular, it uses an unstructured peer network to
|
|
maximize resilience against network link failure, and it uses the underlying
|
|
blockchain (through BNS) to rate-limit chunk announcements.</p>
|
|
|
|
<p>This section contains the following sections:</p>
|
|
|
|
<ul id="markdown-toc">
|
|
<li><a href="#peer-selection" id="markdown-toc-peer-selection">Peer Selection</a></li>
|
|
<li><a href="#comparison-to-dhts" id="markdown-toc-comparison-to-dhts">Comparison to DHTs</a> <ul>
|
|
<li><a href="#chunk-censorship" id="markdown-toc-chunk-censorship">Chunk Censorship</a></li>
|
|
<li><a href="#neighbor-censorship" id="markdown-toc-neighbor-censorship">Neighbor Censorship</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#chunk-propagation" id="markdown-toc-chunk-propagation">Chunk Propagation</a></li>
|
|
<li><a href="#querying-chunk-inventories" id="markdown-toc-querying-chunk-inventories">Querying Chunk Inventories</a></li>
|
|
</ul>
|
|
|
|
<h2 id="peer-selection">Peer Selection</h2>
|
|
|
|
<p>Atlas peers self-organize into an unstructured peer-to-peer network.
|
|
The Atlas peer network is a <a href="https://en.wikipedia.org/wiki/Random_regular_graph">random K-regular
|
|
graph</a>. Each node maintains
|
|
<em>K</em> neighbors chosen at random from the set of Atlas peers.</p>
|
|
|
|
<p>Atlas nodes select peers by carrying out an unbiased random walk of the peer
|
|
graph. When “visiting” a node <em>N</em>, it will ask for <em>N</em>’s neighbors and then
|
|
“step” to one of them with a probability dependent on <em>N</em>’s out-degree and the
|
|
neighbor’s in-degree.</p>
|
|
|
|
<p>The sampling algorithm is based on the Metropolis-Hastings (MH) random graph walk
|
|
algorithm, but with a couple key differences. In particular, the algorithm
|
|
attempts to calculate an unbiased peer graph sample that accounts for the fact
|
|
that most nodes will be short-lived or unreliable, while a few persistent nodes
|
|
will remain online for long periods of time. The sampling algorithm accounts
|
|
for this with the following tweaks:</p>
|
|
|
|
<ul>
|
|
<li>
|
|
<p>If the neighbors of the visited node <em>N</em> are all unresponsive, the random
|
|
walk resets to a randomly-chosen known neighbor. There is no back-tracking on
|
|
the peer graph in this case.</p>
|
|
</li>
|
|
<li>
|
|
<p>The transition probability from <em>N</em> to a live neighbor is <em>NOT</em> <code class="highlighter-rouge">min(1,
|
|
degree(neighbor)/degree(N))</code> like it is in the vanilla MH algorithm. Instead,
|
|
the transition probability discourages backtracking to the previous neighbor <em>N_prev</em>,
|
|
but in a way that still guarantees that the sampling will remain unbiased.</p>
|
|
</li>
|
|
<li>
|
|
<p>A peer does not report its entire neighbor set when queried,
|
|
but only reports a random subset of peers that have met a minimium health threshold.</p>
|
|
</li>
|
|
<li>
|
|
<p>A new neighbor is only selected if it belongs to the same <a href="blockstack_naming_service.md#bns-forks">BNS
|
|
fork-set</a> (i.e. it reports
|
|
as having a recent valid consensus hash).</p>
|
|
</li>
|
|
</ul>
|
|
|
|
<p>The algorithm was adapted from the work from <a href="https://arxiv.org/pdf/1204.4140.pdf">Lee, Xu, and
|
|
Eun</a> in the proceedings of
|
|
ACM SIGMETRICS 2012.</p>
|
|
|
|
<h2 id="comparison-to-dhts">Comparison to DHTs</h2>
|
|
|
|
<p>The reason Atlas uses an unstructured random peer network
|
|
instead of a <a href="https://en.wikipedia.org/wiki/Distributed_hash_table">distributed hash table</a>
|
|
(DHT) is that DHTs are susceptbile to Sybil attacks. An adaptive adversary can
|
|
insert malicious nodes into the DHT in order to stop victims from
|
|
resolving chunks or finding honest neighbors.</p>
|
|
|
|
<h3 id="chunk-censorship">Chunk Censorship</h3>
|
|
|
|
<p>In a DHT, an attacker can censor a chunk by inserting nodes into the peers’ routing tables
|
|
such that the attacker takes control over all of the chunk’s hash buckets.
|
|
It can do so at any point in time after the chunk was first stored,
|
|
because only the peers who maintain the chunk’s hash bucket have to store it.
|
|
This is a <em>fundamental</em> problem with structured overlay networks
|
|
that perform request routing based on content hash—they give the attacker
|
|
insight as to the path(s) the queries take through the peer graph, and thus
|
|
reduce the number of paths the attacker must disrupt in order to censor the
|
|
chunk.</p>
|
|
|
|
<p>Atlas uses an unstructured overlay network combined with a 100% chunk
|
|
replication strategy in order to maximize
|
|
the amount of work an adversary has to do to censor a chunk.
|
|
In Atlas, all peers replicate a chunk, and the paths the chunk take through the
|
|
network are <em>independent</em> of the content and <em>randomized</em> by the software
|
|
(so the paths cannot be predicted in advance). The attacker’s only
|
|
recourse is to quickly identify the nodes that can serve the chunk and partition them from
|
|
the rest of the network in order to carry out a censorship attack.
|
|
This requires them to have visibility into the vast majority of network links in
|
|
the Atlas network (which is extremely difficult to do, because in practice Atlas
|
|
peers maintain knowledge of up to 65536 neighbors and only report 10 random peers
|
|
when asked).</p>
|
|
|
|
<h3 id="neighbor-censorship">Neighbor Censorship</h3>
|
|
|
|
<p>Another problem with DHTs is that their overlay
|
|
network structure is determined by preferential attachment. Not every peer that
|
|
contacts a given DHT node has an equal chance of becoming its neighbor.
|
|
The node will instead rank a set of peers as being more or less ideal
|
|
for being neighbors. In DHTs, the degree of preference a node exhibits to
|
|
another node is usually a function of the node’s self-given node identifier
|
|
(e.g. a node might want to select neighbors based on proximity in the key
|
|
space).</p>
|
|
|
|
<p>The preferential attachment property means that an adaptive adversary can game the node’s
|
|
neighbor selection algorithm by inserting malicious nodes that do not
|
|
forward routing or lookup requests. The attacker does not even have to eclipse
|
|
the victim node—the victim node will simply prefer to talk to the attacker’s unhelpful nodes
|
|
instead of helpful honest nodes. In doing so, the attacker can prevent honest peers from discovering each
|
|
other and each other’s chunks.</p>
|
|
|
|
<p>Atlas’s neighbor selection strategy does not exhibit preferential attachment
|
|
based on any self-reported node properties. A
|
|
node is selected as a neighbor only if it is reached through an unbiased random graph
|
|
walk, and if it responds to queries correctly.
|
|
In doing so, an attacker is forced to completely eclipse a set of nodes
|
|
in order to cut them off from the rest of the network.</p>
|
|
|
|
<h2 id="chunk-propagation">Chunk Propagation</h2>
|
|
|
|
<p>Atlas nodes maintain an <em>inventory</em> of chunks that are known to exist. Each
|
|
node independently calculates the chunk inventory from its BNS database.
|
|
Because the history of name operations in BNS is linearized, each node can
|
|
construct a linearized sub-history of name operations that can set chunk
|
|
hashes as their name state. This gives them a linearized sequence of chunks,
|
|
and every Atlas peer will independently arrive at the same sequence by reading
|
|
the same blockchain.</p>
|
|
|
|
<p>Atlas peers keep track of which chunks are present and which are absent. They
|
|
each construct an <em>inventory vector</em> of chunks <em>V</em> such that <em>V[i]</em> is set to 1
|
|
if the node has the chunk whose hash is in the <em>i</em>th position in the chunk
|
|
sequence (and set to 0 if it is absent).</p>
|
|
|
|
<p>Atlas peers exchange their inventory vectors with their neighbors in order to
|
|
find out which chunks they each have. Atlas nodes download chunks from
|
|
neighbors in rarest-first order in order to prioritize data replication for the
|
|
chunks that are currently most at-risk for disappearing due to node failure.</p>
|
|
|
|
<div class="highlighter-rouge"><pre class="highlight"><code> Name operation | chunk hashes | chunk data | Inventory
|
|
history | as name state | | vector
|
|
|
|
+-------------------+
|
|
| NAME_PREORDER |
|
|
+-------------------+----------------+
|
|
| NAME_REGISTRATION | chunk hash | "0123abcde..." 1
|
|
+-------------------+----------------+
|
|
| NAME_UPDATE | chunk hash | (null) 0
|
|
+-------------------+----------------+
|
|
| NAME_TRANSFER |
|
|
+-------------------+
|
|
| NAME_PREORDER |
|
|
+-------------------+----------------+
|
|
| NAME_IMPORT | chunk hash | "4567fabcd..." 1
|
|
+-------------------+----------------+
|
|
| NAME_TRANSFER |
|
|
+-------------------|
|
|
. . .
|
|
|
|
|
|
Figure 2: Relationship between Atlas node chunk inventory and BNS name state.
|
|
Some name operations announce name state in the blockchain, which Atlas
|
|
interprets as a chunk hash. The Atlas node builds up a vector of which chunks
|
|
it has and which ones it does not, and announces it to other Atlas peers so
|
|
they can fetch chunks they are missing. In this example, the node's
|
|
inventory vector is [1, 0, 1], since the 0th and 2nd chunks are present
|
|
but the 1st chunk is missing.
|
|
</code></pre>
|
|
</div>
|
|
|
|
<h2 id="querying-chunk-inventories">Querying Chunk Inventories</h2>
|
|
|
|
<p>Developers can query a node’s inventory vector as follows:</p>
|
|
|
|
<div class="language-python highlighter-rouge"><pre class="highlight"><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">blockstack</span>
|
|
<span class="o">>>></span> <span class="n">result</span> <span class="o">=</span> <span class="n">blockstack</span><span class="o">.</span><span class="n">lib</span><span class="o">.</span><span class="n">client</span><span class="o">.</span><span class="n">get_zonefile_inventory</span><span class="p">(</span><span class="s">"https://node.blockstack.org:6263"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">524288</span><span class="p">)</span>
|
|
<span class="o">>>></span> <span class="k">print</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="s">'inv'</span><span class="p">])</span>
|
|
<span class="mi">11278</span>
|
|
<span class="o">>>></span>
|
|
</code></pre>
|
|
</div>
|
|
|
|
<p>The variable <code class="highlighter-rouge">result['inv']</code> here is a big-endian bit vector, where the <em>i</em>th
|
|
bit is set to 1 if the <em>i</em>th chunk in the chunk sequence is present. The bit at
|
|
<code class="highlighter-rouge">i=0</code> (the earliest chunk) refers to the leftmost bit.</p>
|
|
|
|
<p>A sample program that inspects a set of Atlas nodes’ inventory vectors and determines
|
|
which ones are missing which chunks can be found
|
|
<a href="https://github.com/blockstack/atlas/blob/master/atlas/atlas-test">here</a>.</p>
|
|
|
|
<div class="share uk-text-center">
|
|
<a href="https://twitter.com/intent/tweet?text=How Atlas Works&url=https://zbabystack.netlify.com/core/auth/howitworks.html&via=&related=" rel="nofollow" target="_blank" title="Share on Twitter" onclick="window.open(this.href, 'twitter', 'width=550,height=235');return false;"><span data-uk-icon="icon: twitter; ratio: 1.2"></span></a>
|
|
<a class="uk-margin-small-left" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fzbabystack.netlify.com%2Fcore%2Fauth%2Fhowitworks.html" rel="nofollow" target="_blank" title="Share on Facebook" onclick="window.open(this.href, 'facebook-share','width=580,height=296');return false;"><span data-uk-icon="icon: facebook; ratio: 1.2"></span></a>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<hr class="uk-margin-medium">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="uk-margin-large-top">
|
|
<h3>Related Articles</h3>
|
|
|
|
|
|
|
|
|
|
|
|
<ul class="uk-list">
|
|
|
|
</ul>
|
|
</div>
|
|
|
|
|
|
<form method="POST" action="https://api.staticman.net/v2/entry/moxiegirl/docs.blockstack/master/comments">
|
|
<input name="options[redirect]" type="hidden" value="https://zbabystack.netlify.com">
|
|
<!-- e.g. "2016-01-02-this-is-a-post" -->
|
|
<input name="options[slug]" type="hidden" value="howitworks">
|
|
<label><input name="fields[name]" type="text">Name</label>
|
|
<label><input name="fields[email]" type="email">E-mail</label>
|
|
<label><textarea name="fields[message]"></textarea>Message</label>
|
|
<button type="submit">Go!</button>
|
|
</form>
|
|
|
|
</article>
|
|
|
|
<script>
|
|
// Table of contents scroll to
|
|
UIkit.scroll('#markdown-toc a', {
|
|
duration: 400,
|
|
offset: 120
|
|
});
|
|
</script>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div id="offcanvas" data-uk-offcanvas="flip: true; overlay: true">
|
|
<div class="uk-offcanvas-bar">
|
|
|
|
<button class="uk-offcanvas-close" type="button" data-uk-close></button>
|
|
|
|
<ul class="uk-nav uk-nav-default">
|
|
<!-- <li><a class="uk-logo uk-margin-small-bottom" href="/"><img src="https://zbabystack.netlify.com/assets/posts/logo.png" alt="Docs"></a></li> -->
|
|
<li><a class="uk-logo uk-margin-small-bottom" href="/"><img src="/assets/posts/logo.png" alt="Docs"></a></li>
|
|
|
|
|
|
<li><a href="https://blockstack.org" target="_blank" >Blockstack.org</a></li>
|
|
|
|
|
|
<li><a href="https://forum.blockstack.org/" target="_blank" >Forums</a></li>
|
|
|
|
|
|
<li><a href="https://github.com/blockstack" target="_blank" >GitHub</a></li>
|
|
|
|
</ul>
|
|
|
|
<div class="uk-margin-small-top uk-text-center uk-text-muted uk-link-muted">
|
|
<div data-uk-grid class="uk-child-width-auto uk-grid-small uk-flex-center uk-grid">
|
|
|
|
<div class="uk-first-column">
|
|
<a href="https://twitter.com/" data-uk-icon="icon: twitter" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
<div>
|
|
<a href="https://www.facebook.com/" data-uk-icon="icon: facebook" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
<a href="https://www.instagram.com/" data-uk-icon="icon: instagram" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
<a href="https://vimeo.com/" data-uk-icon="icon: vimeo" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<footer class="uk-section uk-text-center uk-text-muted uk-link-muted">
|
|
<div class="uk-container uk-container-small">
|
|
|
|
<div>
|
|
<ul class="uk-subnav uk-flex-center">
|
|
|
|
|
|
|
|
|
|
<li><a href="https://blockstack.org" target="_blank" >Blockstack.org</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
<li><a href="https://forum.blockstack.org/" target="_blank" >Forums</a></li>
|
|
|
|
|
|
|
|
|
|
|
|
<li><a href="https://github.com/blockstack" target="_blank" >GitHub</a></li>
|
|
|
|
|
|
</ul>
|
|
</div>
|
|
<div class="uk-margin-medium">
|
|
<div data-uk-grid class="uk-child-width-auto uk-grid-small uk-flex-center uk-grid">
|
|
|
|
<div class="uk-first-column">
|
|
<a href="https://twitter.com/" data-uk-icon="icon: twitter" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
<div>
|
|
<a href="https://www.facebook.com/" data-uk-icon="icon: facebook" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
<a href="https://www.instagram.com/" data-uk-icon="icon: instagram" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
<a href="https://vimeo.com/" data-uk-icon="icon: vimeo" class="uk-icon-link uk-icon" target="_blank"></a>
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
</div>
|
|
<div class="uk-margin-medium uk-text-small copyright">Blockstack</div>
|
|
|
|
</div>
|
|
</footer>
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
/* Create a configuration object */
|
|
var ss360Config = {
|
|
/* Your site id */
|
|
siteId: 'blockstack',
|
|
/* A CSS selector that points to your search box */
|
|
searchBox: {selector: '#searchBox'}
|
|
};
|
|
</script>
|
|
<script src="https://cdn.sitesearch360.com/sitesearch360-v11.min.js" async></script>
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|