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.

Maximilian Hoffmann
Aug 12 · 7 min read

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.

When we started implementing the first games we’ve decided to do things differently compared to the agencies that our client hired to built the previous games. For example we chose to not use any JavaScript game frameworks to keep the file size as small as possible and build the games in a functional way for the ease of maintainability. And the results were great! The size of our games were just a fraction of our client’s old games, they were easy to change even for junior developers and they reached 60 fps on older devices too.

We grew with the task and the complexity of games therefore increased as well. Around that time our team also started to learn Elm. Some of us had already experienced the ease of refactoring and reliability of functional JavaScript software projects and wanted to take these advantages even further. We’ve decided to hire an experienced Elm developer to speed up this process. This is how Erkal joined our team and it didn’t take long for him to start building our first games in Elm.

One example of the games Erkal built in Elm.

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

With a lot of discipline, you could build something like our recording feature in JavaScript or TypeScript by only writing pure functions, not relying on any global state, building a system that manages side effects instead of executing them immediately etc. But due to Elm being a pure functional programming language with managed effects and a great type system, we have something better than discipline: guarantees.

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!

Conclusion

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! 🛰

diesdas.direct

Thoughts, observations and learnings from Berlin-based…