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?

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

[["

]

]

]

] 1, // GitHub User ID #"Alice", // GitHub username1, // 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

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!