Devising a way to ensure a provably fair Twitter giveaway
2020-11-12 • 4 min read
As per our roadmap, we started our journey with a Big Duck giveaway to celebrate our launch. All Big Duck giveaways will be run in a provably fair manner - after all, you don't want to waste your time entering a giveaway where the owner just chooses one of their friends to win! You will be able to verify that the announced winner(s) was / were fairly selected, instead of just taking our word for it.
The first hurdle is that Twitter's API only returns the 100 most recent retweets for a given tweet, with no other way to get older retweets. This limitation is also present on the website / app, so you will only ever be able to see at most 100 retweets. In fact, most of the online retweet picker services are aware of the limitation - and so it's relatively easy to "hack" these giveaways, by simply being one of (or all of) the last 100 retweeters.
One solution to get a complete list of retweets could be to poll the retweet lookup endpoint on Twitters API, but this endpoint is rate-limited to 75 requests per 15 minutes, which is 1 request every 12 seconds. Whilst this works for small accounts where the giveaway tweet is unlikely to get more than 100 retweets in 12 seconds, this solution isn't ideal as there is no guarentee for that to be the case.
Thankfully, Twitter offers a follows lookup endpoint that returns up to 1,000 followers per request, as well as providing a way to paginate - meaning we are able to retrieve a list of all our followers. So, instead of first picking a winner from a list of retweets (which we cannot reliably get) and checking if they have liked and followed, we can pick a winner from the list of followers, then determine if they are eligible by checking if they have liked and retweeted.
In order to conduct a provably fair selection, we can use the bitcoin network as a source of randomness and timestamp. To do this, we take the merkle root of the first bitcoin block mined after the end date of the giveaway as a source of randomness, with the requirement that the block's timestamp must be after the giveaway's closing date to be considered valid.
Next, we sort the user IDs of the entrants in ascending order, and concatenate them with a single hyphen
- with no newline characters, then hash the resultant string using SHA256 to form the participants hash. The merkle root (m) and the participants hash (p) is the concatenated and hashed to retrieve the drawing hash, in the format SHA256(m || p). The first 8 bytes of the drawing hash will be converted to a little endian unsigned integer. This integer modulus the number of participants will be the index of the winner in the list of participants (sorted by user IDs in ascending order).
The winning account will then be checked manually to ensure it has liked and retweeted the giveaway tweet. If the winning entry is deemed ineligible, the drawing hash is passed through passed through another iteration of SHA256 and the process is repeated until a valid winner is selected / the correct number of winners are selected.
Prior to releasing the results of the giveaway, we will wait for the next bitcoin block to the mined to ensure the block used in the drawing is not orphaned and thus available publicly for provable fairness.
As the transactions and nonce used in the next mined bitcoin block are unpredictable and resistant to manipulation, it is impossible to know the winner beforehand until the block is mined.