Understanding the Handshake Airdrop and Reserved Names

0
3
A literal airdrop: But Handshake isn’t conducting psychological warfare, just giving away free stuff to bootstrap the network

A deep dive into the cryptographic process of giving away free stuff.

Money & Names

There are two types of assets stored in the Handshake blockchain: money and names.

Money is encoded in Handshake the same way it is in Bitcoin: there is a set of valid transaction outputs (UTXOs or “coins”) that each have their own value and locking script. Unlock a coin, and you can replace it with new coins of equal or lesser value.

Names are encoded in Handshake in the same way. Once a name has been won in an auction, it is “owned” by a UTXO. That UTXO has a value that can never be spent (it is permanently locked, or “burned”) and it has a locking script. Unlock that script, and you can add DNS records to the name or even transfer the name to a new UTXO with a new locking script.

Free Stuff

A fundamental principle of the Handshake project is to benefit the open-source software community and the overall infrastructure of the internet. From the whitepaper:

Much as capitalism creates a competitive game between participants in which
competitive self-interest reduces the price of goods in non-monopolistic
commodity environments, the handshake mechanism is a project exploring a similar concurrent game to maximize ownership for FOSS developers and the public.
No single producer reduces the prices of their own good for altruism in
capitalist marketplaces, it is done through self-interested competitive
incentives, "I make more money when I lower my prices". Similarly, the
handshake mechanism is experimenting with a process whereby "I make more money the more is gifted to FOSS developers and the whole of humanity".

To that end, 70% of the coin supply will be distributed to open-source developers and nonprofits. DNS names listed in the Alexa top 100,000 sites will be reserved for their current owners, who can claim those names on-chain using their Handshake wallet. So when the chain launches, lots of community members will have free money, and lots of major internet resource providers will have their own names. This scheme will also prevent names like “Facebook” from being used maliciously or squatted on.

Collecting Keys

Cryptocurrency airdrops in the past have usually involved taking a snapshot of a current network’s UTXO set and generating a new (incompatible) blockchain from there. Whoever held coins on the original chain can use the same keys to unlock the copy of those coins on the new chain. The Handshake project does not fund other cryptocurrency stakeholders, however, it funds open-source developers — so how do we get them their coins? We don’t know their public keys… or do we?

$ curl https://api.github.com/users/pinheadmz/gpg_keys
[
{
...
"public_key": "xsFNBFtrHssBEADlPQQ+eaXmIg/lDxCUuwJDnF0BXVoTKeqDIrDLx/2qFA6Dj6AcUWsrVNPxvvSMr2i5LgMFrFvjbrXoo7jsZO5a4AUuXragJzI6UKaKD5HxQR/6L6zXsOmGHhLMCAUwaIyl7wqOUrDaJbAbmr/S38yEhEkztVuxN8YbrZ6WpgEVMPfZsNdyUPEOEHUcRSwJGVGnn2CEPdUavFYIGYgGFxJBhy/xcRtgDrKyMhXa8qdj5IPksfGiSGC69PkbG2vT6Dy+Lmc2QhSv3cRn+rYnY8jdN9ADKY7um4XKwvlzkVld6MGDYJG/EB53Q+rAu+qbUhPrGOKRdC7hnangsALi6dwEq1XDk1n/Snp4/WR3oADFxo5WAGls+i+vxtQul3U+h67xyPRBaW/FZjgN8gNL97nIqMPa7HHDxBsobmrVYnbNnhiA84qXHDc/vgcSFlMjJ9il05m6JIvwpMCN+QaR2s5G0MW6lhY6qmBezBMwyy7eSMvRiAGpUg/KDTzqKj+x87VkYPb6YMoa/FHkV+dne/1Alb1Kp9+tvGe/FSjPBOPbXK+F1uRMC46O2lOpsAPo0DntI85c7/RlXUBlXk8En+FYies/a+vBNgEtZsmBVxGD77i+ThM7MOk+oGY8yON25OQOYXIoBT8FLGzUofM/LF5yERtcIf5lm48XNBOWhQr+xQARAQAB"
}
]

The GitHub API returns public PGP and SSH keys for users that uploaded them. However, GitHub’s API docs clearly state:

The data returned in the public_key response field is not a GPG formatted key. When a user uploads a GPG key, it is parsed and the cryptographic public key is extracted and stored. This cryptographic key is what is returned by the APIs on this page. This key is not suitable to be used directly by programs like GPG.

But I’ll let you in on a little secret. OpenPGP message and packet formats are specified in RFC4880 and can be decoded by bcrypto:

$ node
> const {PGPMessage} = require('bcrypto/lib/pgp')
> PGPMessage.fromBase64('xsFNBFtrHssBEADlPQQ+eaXmIg/lDxCUuwJD...')
{ packets: [ { type: 'PUBLIC_KEY', body: [Object] } ] }

In this way, tens of thousands of public keys from popular open-source contributors were collected for the airdrop.

There was also a faucet website where anyone could submit an application for airdrop coins by providing a mainnet Handshake address generated by the faucet-tool. Email addresses were required, and users who already received an airdrop to their GitHub keys were removed from the faucet.

The Money Tree

For this demonstration, we are going to modify a branch of the hs-airdrop repository. This repository contains the scripts that recipients can use to collect their coins on-chain, but it also contains the scripts that the Handshake developers use to insert all the public keys they collected into the blockchain’s consensus mechanism before mainnet launches.

To make a long story short, all the recipient public keys are loaded into a Merkle tree with a consensus-critical root that is stored in the hsd client. Redeeming airdropped coins involves proving that a public key exists in the Merkle tree, and then signing a special type of transaction with it. There is also an extremely cool cryptographic anonymity layer involved that prevents airdrop transactions from being linked directly to the GitHub user called Goosig.

Now to make a short story LONG, we are going to create brand-new GPG keys, make our own tree, insert that root into hsd in regtest mode, and redeem an airdrop!

Let’s generate a fresh GPG private key, and export it into local files:

$ gpg --quick-generate-key Alice rsa4096
$ gpg --armor --output alice_pub.asc --export Alice
$ gpg --armor --output alice_prv.asc --export-secret-keys Alice

Next we’ll use the PGP module in bcrypto to strip out the raw public key from the ASCII-encoded file and give us back a base64 string, just like GitHub’s user API:

$ ./extract-publickey alice_pub.asc
Key ID:
351d404712461946
Public key (base64):
BF1keZABEAD2++nDebPDr9M4HpJB+cf5Sn6GEXOej8PZ6D3k3wY3MQxlNkBW6caADsMVsDT1hSNJPzUVY+p+cRA5Aou34NWvRd+OtDDJyHfXy9tbMCMxjV0FDweMfGWinbhfV3WKTl6xecKNBapBzgyKz5ZJK048U/zPXd8cEihmEPsnMiHno8m3uH2XQSusWp6SqYKvBh6Bi3zuWIx7uzuRyS1pwOOe7Go76bU189O7OA9S2rh/6xhX2Fp0WyJiGds7L2Fy1+e43YHB529HhYhFw8jPmPHc1HVP19OSW5YT4BnZDfkhydPLICZwrwYpNxnB/mj7mEu447+/ahFBV7YnH2KBmk/4azePkk1ItqvVP9I1TEYrF7H5wtYsSmqK7/LAG/18sUI17d8F40ZlsPvWq5yr67AikbMaItrSq+eoedbZPWB+AQk1kKCXG1MEVLH3zejHboxXVzqAOen7shrG0TGfjn6N48pLJnkCqOtmI5XMkSibKT1JWH1Vgmm0Z36XwSaeG+d4ryCnZDsPhb3d3UPvvvAy3BHGAyyV8VqCWnmQaqKbLgVnltY8omDIwkz9AYs/d7rhP8Ld6DGbrkcZMNbxu8idwjokhiHr+VBGgkWJxQdfT/ndwIbWedpSAF+19MuhfM9x2VuIEpAYGmA1PEKt6r4+XM3qnA4D+/39n1UAYRFIuQARAQAB

We can generate a faucet address as well. The faucet addresses and airdrop keys are embedded into two separate trees but they are linked, since duplicate users are removed. Both tree roots are committed to in the hsd client.

$ faucet-tool createaddress -n regtest
...

Address:
rs1qk2hh6xrmmnpsc8pt08tr8uwdqdxp0yuyv6hzsu

OK! This is enough to get started. Keep in mind a few other sources were queried for keys to include in the airdrop, including the Web of Trust “strong set” and Hacker News users. SSH keys are scraped from GitHub in addition to GPG keys.

To build a Handshake airdrop tree, we need to create a handful of json files. A few we will just leave empty, but they are still required or the script will throw:

$ echo "[]" > sponsors.json
$ echo "[]" > creators.json
$ echo "[]" > hn-keys.json
$ echo "" > strongset.asc

We don’t have any SSH keys but we still need to fill an entry for our GitHub user “Alice” who does have a GPG key:

github-ssh.json

[
[
1, // GitHub User ID #
"Alice", // GitHub username
[] // No SSH keys for Alice
]
]

And finally we can do something with our actual key:

github-pgp.json

[
[
1, // GitHub User ID #
"Alice", // GitHub username
[
[
1, // User ID #
-1, // Parent Key ID
"351d404712461946", // Key ID
"BF1keZABEAD2++nDebPDr9M4Hp...", // Raw key as PGP packet
[["alice@alice.com", 1]] // Email, verified (bool)
]
]
]
]

Finally, we’ll add the faucet address we generated, and assign it to a different username and email so it doesn’t get de-duped.

faucet.json

[
{
"email": "bob@bob.com",
"github": "Bob"
"address": "rs1qk2hh6xrmmnpsc8pt08tr8uwdqdxp0yuyv6hzsu",
"shares": 1
}
]

Hash Hash Hashy Hash

Let’s run the scripts that will generate the trees and give us back the roots! This is a process that will only ever need to be run once for mainnet. The scripts require, as an argument, the directory in which the json files live. We start with the airdrop tree:

$ scripts/merkelize-airdrop regtest/json
Valid github users: 1
Valid github keys: 2
Invalid github users: 0
Invalid github keys: 0
Valid strongset members: 0
Invalid strongset members: 0
Valid hackernews users: 0
Invalid hackernews users: 0
Wrote buckets (size=0.0004901885986328125mb).
Wrote merkle tree with 2 keys and 1 leaves.
Checksum: fb92f890ab91ad36dda17f28f114f771ecb76b0cae0b154f2415a4a2b1be68b7
Tree Root: ee8bff349dec24a8378f38fbed35bba5cd0ebde2d5f256101ffbadac93920744
Leaves: 1
Keys: 2
Max Keys: 2
Depth: 0
Subdepth: 1
Faucet: 1
Shares: 1
Reward: 476000000000000

Notice how this script also generated some output in build/ and etc/. The file etc/tree.json will actually be imported by the merkelize-faucet script, and otherwise contains checksums for the 256 nonce files that are output in build/nonces. These 256 “buckets” will eventually contain encrypted nonces for all 80,000 or so scraped keys. The nonces are used in the Goosig construction to create the zero-knowledge proofs that keep airdrop redemptions anonymous.

Now let’s generate the faucet tree root:

$ scripts/merkelize-faucet regtest/json
Valid sponsor addresses: 0
Valid creator addresses: 0
Valid participant addresses: 1
Wrote merkle tree with 1 leaves.
Checksum: 06d55ac651d4c5b2264ad62c60c9633079709c9bbfff1ffd6e0172616964be32
Tree Root: 1d592d73c49f0aa50b2ef857c343700be0d00ebe13167a7cebd633d3bae682d8
Leaves: 1
Depth: 0
Participants: 1
Faucet Total: 476000000000000
Shares: 1
Sponsors: 0
Creators: 0
External Total: 0

This script will also output a file build/proof.json. You can see the mainnet version of this file on GitHub.

Prove It

Now it’s going to get fun. We have two fields labeled Tree Root from the above json output. Let’s insert those roots into an hsd client and run it in regtest mode (locally only) so we can redeem the airdrops. Keep in mind, this is a modification to CONSENSUS-CRITICAL parameters! Once we make this change, our client will not sync on any other network. We also need to modify the hs-airdrop script just to create the proofs from our new data.

This is how a faucet recipient would claim their coins. First, generate an airdrop proof for the faucet address:

$ bin/hs-airdrop rs1qk2hh6xrmmnpsc8pt08tr8uwdqdxp0yuyv6hzsu
Attempting to create proof.
This may take a bit...
Creating proof from leaf...
JSON:
{
"index": 0,
"proof": [],
"subindex": 0,
"subproof": [],
"key": {
"type": "ADDRESS",
"version": 0,
"address": "b2af7d187bdcc30c1c2b79d633f1cd034c179384",
"value": 476000000000000,
"sponsor": false
},
"version": 0,
"address": "b2af7d187bdcc30c1c2b79d633f1cd034c179384",
"fee": 100000000,
"signature": ""
}
Base64 (pass this to $ hsd-rpc sendrawairdrop):
AAAAAAAAACAEABSyr30Ye9zDDBwredYz8c0DTBeThADA6WLrsAEAAAAUsq99GHvcwwwcK3nWM/HNA0wXk4T+AOH1BQA=

And redeem!

$ hsd-rpc --network=regtest sendrawairdrop AAAAAAAAACAEABSyr30Ye9zDDBwredYz8c0DTBeThADA6WLrsAEAAAAUsq99GHvcwwwcK3nWM/HNA0wXk4T+AOH1BQA=
9a2771031b305533b1015f2b1860079daa6243c873a51abf4735621c4c16007e

Now we’ll generate an airdrop proof for the “Alice” GPG key. This call is more complicated, and requires the redeemer to specify their GPG private key file, their own Handshake wallet address, and a miner fee. Notice that the faucet redemptions have hard-coded fee rates… can you guess why? 🤔

$ bin/hs-airdrop regtest/pgp/alice_prv.asc 351d404712461946 rs1qy9uplxpt5cur32rw3zmyf8e7tp87w8slly2fms 0.5
Attempting to create proof.
This may take a bit...
Decrypting nonce...
Finding merkle leaf...
Creating proof from leaf...
JSON:
{
"index": 0,
"proof": [],
"subindex": 1,
"subproof": [
"9de514887afa96c585e8a27161c7bba420703e25a280aa67c57ffc8980816311"
],
"key": {
"type": "GOO",
"C1": "0f6d9..."
},
"version": 0,
"address": "21781f982ba63838a86e88b6449f3e584fe71e1f",
"fee": 500000,
"signature": "4933fd82aa8aa7fb900a2..."
}
Base64 (pass this to $ hsd-rpc sendrawairdrop):
AAAAAAABAZ3lFIh6+pbFheiicWHHu6QgcD4looCqZ8V//ImAgWMR/QEBA...

Redeem it!

$ hsd-rpc --network=regtest sendrawairdrop AAAAAAABAZ3lFIh6+pb...

Confirm

The last quirky thing to notice about airdrop proofs is that they are not really transactions. They get broadcasted and relayed and stored in the miners’ mempools, but not in the same way that normal transactions are. Airdrop proofs are, in fact, added to a block’s coinbase transaction as extra inputs (containing proof data in the witness) and outputs. Now that we’ve “sent” two airdrop proofs, let’s generate a block and verify that we have indeed created money out of thin air:

$ hsd-rpc --network=regtest generatetoaddress 1 rs1q30ppv5gyrwpy4wyk0v6uzawxygdtvrpux8yrg2
[
"022882f6566671742c524cb485109a4d8ef51861e09f286d651d927cfe6630ee"
]
$ hsd-cli --network=regtest block 022882f6566671742c524cb485109a4d8ef51861e09f286d651d927cfe6630ee
...
"outputs": [
{
"value": 2100500000,
"address": "rs1q30ppv5gyrwpy4wyk0v6uzawxygdtvrpux8yrg2",
"covenant": {
"type": 0,
"action": "NONE",
"items": []
}
},
{
"value": 475999900000000,
"address": "rs1qk2hh6xrmmnpsc8pt08tr8uwdqdxp0yuyv6hzsu",
"covenant": {
"type": 0,
"action": "NONE",
"items": []
}
},
{
"value": 4370322008,
"address": "rs1qy9uplxpt5cur32rw3zmyf8e7tp87w8slly2fms",
"covenant": {
"type": 0,
"action": "NONE",
"items": []
}
}
]
...

Look at that! A coinbase transaction with three outputs! The first is the miner’s subsidy and reward, and the other two are free money airdrop outputs to members of the open-source community!

Reserved Names

Remember that names on Handshake are stored in UTXOs just like coins. So in order for the top 100,000 websites to acquire their name on the Handshake blockchain, they need to associate it with a Handshake wallet address they control. Then they must submit a proof of ownership to the network that will allow them to generate a UTXO that locks their name to their private key (or redeem script).

This proof of ownership is based on a subset of DNS records called DNSSEC. DNSSEC records aren’t the usualname -> IP address type of DNS record. Instead, they store data like public keys for a zone. Those keys can then be used to sign and verify other records in that zone. They can also be used to sign public keys belonging to child zones. Do you see where this is going?

DNSSEC starts with a single, central key that is generated by ICANN in an elaborate, ultra-formal, and highly scrutinized ceremony (this is precisely what Handshake aims to subvert and decentralize!). That key is the ROOT ZONE KEY SIGNING KEY, and we should all tremble before its awesome power. The ICANN KSK is widely known and is stored in our own DNS resolver package, bns.

ICANN uses this key to sign the root zone file, which includes a commitment to the KSK for top-level domains like .com. In turn, the managers of the .com domain use their key to sign KSKs for domains like google.com and bitcoin.com. Finally, those domain managers sign records in their own zones, including nameserver, address, and text records.

So if the owner of bitcoin.com wants to claim their name on Handshake, we expect to see this chain of signatures: “The well-knownroot zone key signs a .com zone key, which signs abitcoin.com key, which signs a Handshake address.”

Normally, the Handshake name claim script would make actual DNS queries to obtain the chain of keys needed to generate a proof. But, since we are going to hack our own DNSSEC chain together, we’re going to have to be more… creative… with the method.

Look at Me, I’m the ICANN Now

For this next demonstration, we’re going to use BIND, which I was able to install on OSX with brew install bind. This will give us three utilities we need to proceed: dnssec-keygen, dnssec-dsfromkey, and dnssec-signzone.

First, we’ll use the Handshake wallet to generate a claim for the domain name bitcoin. This command will generate a TXT record that includes a wallet address and some other metadata:

$ hsw-rpc --network=regtest createclaim bitcoin
{
"name": "bitcoin",
"target": "bitcoin.com.",
"value": 566471548,
"size": 5120,
"fee": 25600,
"address": "rs1q7ywfylsgt3fj4n6fz7q8k8j0wz43lcws2ee2ya",
"txt": "hns-regtest:aakpchespyefyuzkz5erpad3dzhxbky74hip2adei6z7736u6qcacthqpeg2ncko7nnwpepv76f6sn3ywjlerdorw7dacaaaadrg5xku"
}

Now we’ll create the bitcoin.com zone file. Start by generating two keys: a zone-signing key (ZSK), and a key-signing key (KSK). These commands will output public and private key files wherever the command is run:

$ dnssec-keygen -f KSK -a RSASHA256 -n ZONE bitcoin.com
Generating key pair...
Kbitcoin.com.+008+37177
$ dnssec-keygen -a RSASHA256 -n ZONE bitcoin.com
Generating key pair...
Kbitcoin.com.+008+27510

Now create a zone file template, including the keys and the Handshake proof:

bitcoin.com.zone

$ORIGIN bitcoin.com.
$TTL 172800
@ IN SOA com. admin.email ( 2007120710 1d 2h 4w 1h )
$INCLUDE /work/guide/keys/Kbitcoin.com.+008+37177.key
$INCLUDE /work/guide/keys/Kbitcoin.com.+008+27510.key
bitcoin.com. IN TXT "hns-regtest:aakpchespyefyuzkz5erpad3dzhxbky74hip2adei6z7736u6qcacthqpeg2ncko7nnwpepv76f6sn3ywjlerdorw7dacaaaadrg5xku"

Sign the zone file:

$ dnssec-signzone -o bitcoin.com bitcoin.com.zone

This will generate a new file bitcoin.com.zone.signed which contains signed records, like this:

172800 TXT "hns-regtest:aakpchespyefyuzkz5erpad3dzhxbky74hip2adei6z7736u6qcacthqpeg2ncko7nnwpepv76f6sn3ywjlerdorw7dacaaaadrg5xku"
172800 RRSIG TXT 8 2 172800 (
20190926203106 20190827203106 17421 bitcoin.com.
wE/Ky5yH9bG2DAmOp1d0oSOA2OpsrmxQjv8w
OvnNkYC65Vo38eBDj64wSU2x1tgO91TBepj1
rLj3Df2owMYv3A+Ciu/qXXCRSM/2krcltXis
guV7tLltBQLsiPtVs1wEr8vCWcaZP4yd1lo4
1Bmvi0YajOsxV7NNWJloTPJfdNM= )

Now we need to create two more zone files (for .com and root) in a similar way. Each parent zone needs to include a DS record of the KSK of its child zone (i.e., the .com zone needs a DS record of the bitcoin.com key). We create that record like so, and add it to the com.zone template file:

$ dnssec-dsfromkey -2 Kbitcoin.com.+008+37177.key
bitcoin.com. IN DS 17421 8 2 78D60AF13E97693AC2F32B591FD3D60D34E151016F8692475617FBCD03CDCBE1

We’ll repeat the process of generating keys, adding them to the zone files, and signing the files for both .com and root zone files.

Finally, we’ll smash all three zone files together. We don’t need to include all the records from each zone like SOA and NSEC, just the DS, DNSKEY, TXT, and RRSIG. You can see my completed ownership proof here.

For any of this to be valid, of course, we need to insert our unceremoniously homemade root zone KSK into Handshake. We do that by generating a DS record and adding it to the trust anchors in bns (bns is a dependency of hsd, so it’s in our node_modules directory).

Claim That Name

OK, are you ready to own the name bitcoin on Handshake? We just have one more small hack to pull off: We need to compile the proof to a base64 string. It can be done with two lines in the nodejs REPL using bns:

$ node
> const bns = require('bns')
undefined
> bns.Ownership.Proof.fromString(
fs.readFileSync('/work/guide/zones/ownership-bitcoin.zone')
.toString('ascii')
).toBase64()
'AwMAADAAAQACowAAiAEAAwgDAQABnt9Z4x3v0DvnkBY0Hx17RTi3Ac...'

Now we can use that huge base64 blob (containing the entire series of DNSSEC signatures) to claim our reserved name on-chain:

hsd-rpc --network=regtest sendrawclaim AwMAADAAAQACowAAiAEAAwgD...

Confirm

Like the airdrop redemptions, reserved name claims are included in the coinbase transaction of a block:

$ hsd-rpc --network=regtest generatetoaddress 1 rs1q30ppv5gyrwpy4wyk0v6uzawxygdtvrpux8yrg2
[
"908eb35fab448ec928126dc7b451b27798b5f611bc50b5bfc081a7d18add04e0"
]
$ hsd-cli --network=regtest block 908eb35fab448ec928126dc7b451b27798b5f611bc50b5bfc081a7d18add04e0
...
"outputs": [
{
"value": 2000025600,
"address": "rs1q30ppv5gyrwpy4wyk0v6uzawxygdtvrpux8yrg2",
"covenant": {
"type": 0,
"action": "NONE",
"items": []
}
},
{
"value": 566445948,
"address": "rs1q7ywfylsgt3fj4n6fz7q8k8j0wz43lcws2ee2ya",
"covenant": {
"type": 1,
"action": "CLAIM",
"items": [
"f82f54fe3a9daa86316dc706e74b31d57ce6b21a12104cdfbd3f90b627847105",
"0c000000",
"626974636f696e",
"01", "47b3ffefd4f404014cf0790da6894efb5b6791f5ff8be93778b256488dd1b7c6",
"01000000"
]
}
}
]
...

VOILÀ! That second transaction output is our CLAIM, and it assigns control of the bitcoin name to the wallet address we generated. We were able to accomplish this by forging keys for the .com and root zones, and inserting them maliciously into our own hsd client in regtest mode.

🍺 Conclusion🍺

Did you ever figure out why faucet proofs have hard-coded fees? Be the first to email the correct answer to me at matthew@purse.io and I’ll send you $5 in Bitcoin. If I don’t get the right answer until after Handshake mainnet launches, I can send you $5 in dollarydoos!

Files used & modified for this demonstration, and their originals:

hs-airdrop: (original) (modified)
Used to generate airdrop trees and create proofs for key-holders and faucet recipients to claim their coins on the blockchain.

hsd: (original) (modified)
The Handshake reference client. Validates the blockchain and audits all transactions for protocol compliance.

bns: (original) (modified)
A recursive DNS server and resolver for node.js, included as dependency for hsd and required to process DNSSEC.

hsd-regtest-claim: (original used only for this guide)
Keys and zone files used to create “valid” DNSSEC proof for a reserved name claim.

faucet-tool: (original)
Generates a BIP39 mnemonic phrase, private key, and mainnet hns address. Used to apply for faucet airdrop.


Understanding the Handshake Airdrop and Reserved Names was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.