The idea is rather simple, really.
Generate a temporary ethereum wallet for every github account that gets rewarded, then encrypt each temporary wallet private key for all ssh pub keys that the user has in GitHub (eg https://github.com/folex.keys). We do encryption with the tool called age
that allows to encrypt data for SSH pub keys, and decrypt with private SSH keys.
So, on a first step, we get a map of unencrypted ethereum wallets: alice => 0xaliceTmpWalletPrivateKey bob => 0xbobTmpWalletPrivateKey
Then, after encryption, we get map of encrypted ethereum wallets: alice => [0xabc, 0x123, 0x321] bob => [0xaaa, 0x333]
Here Alice has 3 ssh keys on github, and bob has 2 ssh keys on github.
Now, this map is saved as metadata.json
. Anyone can download it, but only holder of Alice's SSH Private Keys can decrypt Alice's Temporary Ethereum Wallet.
Apart from metadata.json
, we also generate Merkle Tree with addresses of all the Temporary Wallets. And put it on chain. This is to make it possible for Smart Contract to tell whether certain Wallet belongs to the set of Temporary Wallets or not.
When Alice wants to claim, she first looks into metadata.json
to see if it contains her GitHub handle. Then, she peeks each of the enrypted Temporary Ethereum Wallets, and tries to decrypt it with her SSH key of choice. So from [0xabc, 0x123, 0x321]
she gets 0xaliceTmpWalletPrivateKey
.
Now, Alice has to decide on which Ethereum Address she wants to receive the reward. Let it be 0xAliceMainWallet
.
Alice then signs 0xAliceMainWallet
address with 0xaliceTmpWalletPrivateKey
, and builds a Merkle Proof for the address of the 0xaliceTmpWalletPrivateKey
wallet. This information is enough to prove that:
- Alice has access to SSH key
- Alice has intention to send reward to
0xAliceMainWallet
and nowhere else - Wallet
0xaliceTmpWalletPrivateKey
belongs to the Temporary Wallets set
This defines the proof format as: https://github.com/fluencelabs/dev-rewards/blob/main/proof-sh/proof.sh#L228
## userId, tmpEthAddr, signatureHex, merkleProofHex
echo "${USER_ID},${TMP_ETH_ADDR},${SIGNATURE_HEX},${MERKLE_PROOF}"
Where USER_ID
is just an index in the metadata.json
, which we use to check if this user has already claimed the tokens: https://github.com/fluencelabs/dao/blob/main/contracts/contracts/DevRewardDistributor.sol#L252
Now, Alice will send the proof ${USER_ID},${TMP_ETH_ADDR},${SIGNATURE_HEX},${MERKLE_PROOF}
to the Smart Contract. Either manually, or through the claim website
- Check if this USER_ID has already claimed: https://github.com/fluencelabs/dao/blob/main/contracts/contracts/DevRewardDistributor.sol#L204
- Check if this TMP_ETH_ADDR belongs to the Temporary Wallets set https://github.com/fluencelabs/dao/blob/main/contracts/contracts/DevRewardDistributor.sol#L214
- Check if SIGNATURE_HEX is correct, and was made with intention to send money to
msg.sender
https://github.com/fluencelabs/dao/blob/main/contracts/contracts/DevRewardDistributor.sol#L216-L219
This writeup resulted in a blog post: https://blog.fluence.network/how-we-built-a-truly-trustless-flt-rewards-claiming-process/