Making browser games more secure with Elm, Part One
How pure functions and managed side effects made cheating in our client side games a lot harder.
How we got into game development
A few years ago one of our clients asked us to build new mobile web games for them. They already had some on their website which they called “one button games”. An example for the complexity of these games is the offline dinosaur game in Chrome, when there’s no internet connection, but in our case designed for mobile devices. We were still a small team and most of us had almost no experience in building games, but the requirements looked manageable.
Choose any score
Once we started building more complex games our client also wanted to have high scores and leaderboards, so that people could compete against each other. Sometimes players with the highest score would even win a prize. As soon as prizes got involved, we pointed out that scores are not validated in any way. There is nothing in place to make sure somebody has actually played the game. But at that time our client didn’t care about security — yet.
Fast forward a couple months later, somebody posted instructions on a forum showing how easy it is to “hack” our client’s games. I’ve put hack in quotes, because submitting a score was literally sending an unauthenticated HTTP request with the score to the production endpoint. As expected, the client quickly changed their mind, security became a high priority and finally there was budget to design the security of the leaderboard properly.
Proof yourself to be human
Because we knew this was coming, we already had put some things in place that helped us resolve the situation quickly. When we designed the leaderboard backend service, we made sure that we can submit more than just a score. Every session could have arbitrary JSON data attached to it. But how does that help when the whole logic of the game is running in the browser? Everything that happens on the client side can be manipulated and therefore cannot not be trusted on the server side.
Our inspiration to solve this problem came from the Elm debugger. Maybe you’ve seen the Elm Mario game with the debugger, which allows you to scroll back in time and even change the code and go forward in time again to see how the game behaves with different settings. All of this is possible because every Elm application is a pure function with managed effects that moves forward in time via messages. That means as long as you store the initial state of the game/app and all the messages that happen over time, you have the guarantee that you can replay everything exactly as it happened.
You might wonder: Couldn’t you fake the initial state and all messages that happen during gameplay? The answer is yes, but that is a lot of work. Not only do you have to fake messages in such a way that they will be executed by the game code to reach a high score at the end (including every message for a frame/tick in our games), but you also have to fake messages in a way that the gameplay looks like an actual human has played the game — otherwise the human person at other end reviewing the replays of the highest scores can easily tell from playing back the session that these messages have been faked. You could still train an AI to play the games better than a human player, but if you do that, you have not only earned the prize, but also used a way that can pretty much overcome every security measurement.
Using Elm to its full potential
By creating a custom
main function, Erkal implemented a game framework that limits messages to a predefined set. Now the compiler prevents developers from using other messages/effects that cannot be recorded. Therefore every game using the framework is guaranteed to support the replay feature. As a game developer you don’t even have to think about it, since the framework handles everything about saving, uploading and playing back the recording. If it compiles, the replay feature works.
Restricting the type of messages also allowed us to encode them in a different format to make the size of recordings as small as possible. For example despite recording all messages including the frames/ticks that happen at least every 16ms, two minutes of gameplay usually are smaller than 10kb (encoded and compressed). Most users won’t even notice that the whole gameplay has been uploaded to the server when they submit their score.
When our client wants to validate the high scores, they can simply click on a link next to the score in the leaderboard. This opens a web interface that looks like a video player with a bar filling up which might be perceived as buffering a video. This is actually the progress of executing all the messages from the recording into the game code one after the other until all of them have been evaluated. Once that process is finished the software checks if the calculated score from the recorded messages reaches the same score as the one that was initially sent with the submission. If it’s not, a big warning appears. While the messages are executed one can also start playing back the gameplay to see it exactly as it happened on the user’s device. It’s also possible to scrub backward and forward to a specific point in time (to see when players collected a power up for example).
This is just the beginning
This solution works really well for us and our client. We have the guarantee that the replay function works no matter how experienced developers are in building games, since they can rely on Elm’s type system. We also save full gameplay recordings with very little data and provide a great user experience for people to validate the scores of the best players. Comparing the submitted score with the simulated one could even be automated with a node script that runs the messages through the compiled Elm code. This only makes sense though if we repeatedly have leaderboards where most of the high scores are invalid so that it takes a long time for to find the first valid one.
Since Erkal has a mathematics background, he also started building his own physics engines. For example he wrote a physics engine that lets a skier go down slopes and he built a physics engine for a pinball machine game. Despite physics calculations in both games recording the gameplay works as well.
With a recording functionality already built into the framework, some new features could be added quite easily. For example a ghost functionality, which allows sending your gameplay to a friend to compete with you while seeing your recorded gameplay as a ghost, was possible by adding just a few more lines to the existing games. For some games it was harder (when physics where involved or when power ups slow down time), but so far Erkal always managed to make it work reliably.
With Elm’s 3D libraries becoming more capable and Erkal digging further into them, we are also slowly moving into 3D territory. If you are active in the Elm community you might have seen some of his experiments on Elm Slack or twitter. Being able to replay recordings of 3D games or even using rewind as a core element inside of games makes us quite excited!
The guarantee of having reliable gameplay recordings, no matter how experienced or disciplined our developers are, is only possible due to Elm limiting where and how mutations and side effects happen. Its type system is the icing of the cake that improves the readability of the code thanks to custom types (union types) and provides the best error messages one can find in a programming language.
One might not consider functional programming to be a good paradigm for building games, but in our experience we neither had to sacrifice performance nor maintainability of the code. Quite the opposite: it’s really hard for us to imagine a better game development experience.
In the next blog post Erkal will go a lot deeper into the technical aspect of the recording feature and we’ll share a game for you to play plus the ability to try out the replay feature yourself.
diesdas.digital is a studio for strategy, design and code in Berlin, featuring a multidisciplinary team of designers, developers and strategists. We create tailor-made digital solutions with an agile mindset and a smile on our faces. Let’s work together!
Curious to learn more? We’re also on Twitter and Instagram and we highly recommend our Tumblr. You could also subscribe to this publication to get notified when new posts are published! That’s all. Over & out! 🛰