17 May 2020

Treasure hunts using static sites

When I was at SSC we hosted an online treasure hunt called Orfik as part of the computer science society. It was all the rage those days to have these kinds of hunts.

We built the site using django at the time and @Ratik96 and I had quiet a lot of fun doing it. Eventually we hosted it on compsocssc.pythonanywhere.com and it was beautiful!

The rest of us were busy designing questions and what not, while Ratik did a lot of work on the site to get it to run correctly. I kind of buggered off and went into another event while this was going on so I didn't realize how much effort had gone into this treasure hunt.

With Covid19 forcing us all indoors, orfik could be a pretty good way to pass time. So I tried to come up with a way to run this treasure hunt online with as little cost as possible. The challenge was to see if the actual treasure hunt could be done using a simple static site. If it could, then server costs would plummet!

The rules

The solution

Let's say that we have 3 levels. We want people to start at 1, then go to 2, before reaching the final level of 3.

What we do, is that we take the possible answer list of level 1 and create sha256 hashes for them. Then, we make the url of level 2 available at /level/<hash of previous answer>.html.

Let's build out that structure for 3 levels for now.

import hashlib


q_and_a = [
    ("what year is it", ("2020",)),
    ("what month is it?", ("may", "May", "5")),
    ("what is this event called?", ("orfik",)),
]

url_to_qno = {}
last_answer_hshes = ["index"]
for qno, (q, answers) in enumerate(q_and_a):
    for hsh in last_answer_hshes:
        url_to_qno[f"/{hsh}.html"] = qno
    last_answer_hshes = []
    for a in answers:
        last_answer_hshes.append(hashlib.sha256(a.encode()).hexdigest())

for url, qno in url_to_qno.items():
    print(qno, "\t", url)
0 	 /index.html
1 	 /73a2af8864fc500fa49048bf3003776c19938f360e56bd03663866fb3087884a.html
2 	 /ee4d988c65de860fabbfbcd27f73d50bbebe3fba37fe419284f4811389c30bdc.html
2 	 /8c78fe5b9936488c111733d36f3da4b246a4d206159efe5cd64cdb229c38f069.html
2 	 /ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d.html

Now that we have urls for our three levels, we need to figure out how a participant will go from level 0 to level 1.

The locked door

Whenever a participant submits an answer, we simply hash it with sha256 in javascript and check if that url exists. If it exists, the answer is right and if it does not it's wrong. The one way trapdoor nature of the hash function ensures that people won't be able to find the next question without actually finding the answer.

The book keeper

To identify participants in order to generate a leaderboard there was a little work to be done.

  1. A participant needs to create a username-password combo
  2. The password is meant to be a secret.
  3. The javascript hashes the combo and stores it in a cookie. This way, all requests to the server contain this hash
  4. Whenever a user visits a question page, the javascript code on that page pings an API which records the url of the page, the timestamp, and the hash submitted.

With this in place, we have a way of identifying every separate user. In order to validate the winner, we simply need to check their hash and assume that they have not shared their password.

Conclusion

This hashing based linking thing reminded me of merkle trees and blockchains ans so I thought I'd blog about it.

The leaderboard was a little tricky to get to work. Check out the code at https://github.com/theSage21/orfik. The idea is live on https://orfik.compsoc.club/ so go try it out if you get the time.