DocSpring Blog

BTC 2018 Solutions - Stage 1: PixelPerfect

This blog post contains the solution for Stage 1 of the 2018 Bitcoin Programming Challenge.

I had the idea for PixelPerfect a few months ago, but I never got around to finishing it. I thought it would be fun to get an MVP working and add a few CSS puzzles to the Bitcoin challenge.

PixelPerfect is hosted at https://pixelperfect.formapi.io. The source code has now been published on GitHub.

PixelPerfect Screenshot

Tools and Libraries

Here’s what I used to build PixelPerfect:

This was a truly amazing development experience. Ant Design’s React components are beautiful, easy to use, and they have awesome documentation. create-react-app gives you a production-ready webpack config out of the box, and it’s really easy to update. craco makes it really easy to extend this webpack config without “ejecting.” TypeScript caught lots of mistakes during development, and the VS Code integration is awesome. Prettier formatted my code whenever I saved a file, so I never had to think about alignment or spacing. It was also really easy to work with the HTML2Canvas and pixelmatch libraries.

I was able to build the whole app in a single file with less than 1,000 lines of code. (It’s messy, but it was really fun to hack together a prototype.)

I was so impressed by this experience that I’m going to rewrite the DocSpring web UI with Ant Design and switch from Flow to TypeScript.


Client-Side Validation And JavaScript Obfuscation

I didn’t want to have lots of people hammering a server with requests, so I built PixelPerfect as some static files that could be hosted with a CDN. Client-side validation is almost always a terrible idea, but javascript-obfuscator works pretty well. I was also rendering everything into a canvas, so you couldn’t use the developer tools to inspect any elements. I thought that reverse engineering the JavaScript might be just as difficult as solving the CSS puzzles, so either way would be fine. Unfortunately, I made a lot of mistakes:

  • I didn’t disable the React Dev Tools in production. (This is enabled by default.)
    • You could simply disable the debugger protection from javascript-obfuscator, open the React Dev Tools, and set the state to “completed”. This would let you skip all of the CSS puzzles.
    • I fixed the issue with this code. (Thanks @scriptex!)
  • I didn’t encrypt/obfuscate the puzzle solutions.
    • The solutions were visible in the compiled code. I had assumed that javascript-obfuscator would encrypt/obfuscate all of the strings in the source code, because this was working for other variables. I even tried searching for a few strings to make sure, but I didn’t see them because javascript-obfuscator did escape some characters. (Maybe it didn’t encrypt these strings because they were inside arrays and objects.)
    • To fix this, I set up an extra layer of AES encryption. I was already doing this to protect the Bitcoin private key, because I wanted to make sure that this was really difficult to reverse engineer. I should have done that for the puzzles as well.
  • I forgot to remove source maps for the React app CSS. Not a big deal, but the source maps included the path of my source code directory, and this path included my Mac username.



Puzzle 0

Set the correct height of 60px.

<div class="square"></div>
.square {
  width: 60px;
  height: 60px;
  background: #00aedb;
}



Puzzle 1

Find the correct values for margin-top and margin-left.

<div class="square"></div>
.square {
  width: 60px;
  height: 60px;
  margin-top: 20px;
  margin-left: 40px;
  background: #00aedb;
}



Puzzle 2

Make a circle by setting border-radius: 50% (or 30px.)

<div class="circle"></div>
.circle {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #d11141;
}



Puzzle 3

Figure out the correct width, height, and border width.

<div class="square"></div>
.square {
  width: 40px;
  height: 40px;
  border: 10px solid #00aedb;
}

This is where I ran into some rendering issues with the HTML2Canvas library. It doesn’t get the corners quite right, so the difference image is misleading:

Border Rendering Issue with HTML2Canvas



Puzzle 4

Rotate a square to get a diamond.

<div class="diamond"></div>
.diamond {
  width: 64px;
  height: 64px;
  margin: 20px;
  transform: rotate(45deg);
  background: #ff77aa;
}



Puzzle 5

Make a triangle by using borders.

<div class="triangle"></div>
.triangle {
  margin-left: 21px;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
  border-left: 50px solid #f37735;
}



Puzzle 6

Make two circles inside a square.

This was a little bit tricky, because you had to use a ::before or ::after pseudo-element. It’s easy to forget that you also need content: "";.

<div class="square">
  <div class="circle"></div>
</div>
.square {
  width: 110px;
  height: 110px;
  border: 5px solid #d11141;
  position: relative;
}
.circle {
  border: 5px solid #00b159;
  width: 55px;
  height: 55px;
  border-radius: 50%;
  position: absolute;
  top: 10px;
  left: 10px;
}
.circle::after {
  content: "";
  border: 5px solid #00b159;
  width: 55px;
  height: 55px;
  border-radius: 50%;
  position: absolute;
  top: 20px;
  left: 20px;
}



Puzzle 7 (Original)

The initial version of PixelPerfect ended with an extremely difficult puzzle that used gradients and transforms. A lot of people got stuck on the triangle, because the rotation transform was almost impossible to figure out. Sorry about that! I went a bit too crazy with this one.

<div class="container">
  <div class="triangle"></div>
  <div class="circle"></div>
</div>
.container {
  padding: 5px;
  position: relative;
  width: 220px;
  height: 120px;
  background: linear-gradient(#ee4444, #4444dd);
  margin-left: 32px;
  overflow: hidden;
}
.triangle {
  position: absolute;
  left: 55px;
  bottom: 29px;
  border-top: 40px solid transparent;
  border-bottom: 45px solid transparent;
  border-right: 50px solid #ff77aa;
  transform: rotate(81deg);
  z-index: 10;
}
.circle {
  position: absolute;
  bottom: -30px;
  right: 65px;
  background: linear-gradient(#ffe475, #ff77aa);
  width: 100px;
  height: 100px;
  border-radius: 50px;
  z-index: 0;
}



Puzzle 7 (Simplified)

After reading some comments on Hacker News, I decided to make this last puzzle a lot easier. By this time, someone had already solved the first stage and taken the Bitcoins.

<div class="container">
  <div class="triangle"></div>
  <div class="circle"></div>
</div>
.container {
  padding: 5px;
  position: relative;
  width: 220px;
  height: 120px;
  background: #660077;
  margin-left: 32px;
  overflow: hidden;
}
.triangle {
  position: absolute;
  left: 55px;
  bottom: 30px;
  border-top: 30px solid transparent;
  border-bottom: 30px solid transparent;
  border-right: 40px solid #ff88bb;
  z-index: 10;
}
.circle {
  position: absolute;
  bottom: -30px;
  right: 65px;
  background: #ffe475;
  width: 100px;
  height: 100px;
  border-radius: 50px;
  z-index: 0;
}



Once you solved all of the CSS puzzles, you would see this modal popup:

PixelPerfect Solved Modal

Clicking the link for stage 2 brings you to the Cubes puzzle.



I wasn’t sure if I should include these CSS puzzles, but it was a great excuse to finally finish this side project idea. I’m not sure if I’ll do anything with PixelPerfect in the future, but it might be fun to build a site where people can submit their own puzzles.


Solutions for the 2018 Bitcoin Programming Challenge: