Tutorial 6 - Taking security seriously with sandboxing for jigs on Bitcoin
RUN takes security seriously. This article is the first of a series of Features that will demonstrate our obsession with security. With Feature articles, you’re not expected to execute code like you do in Tutorials. You can read Features straight through to level-up your understanding.
In case you missed it, check out our previous lesson, Using a Pay Server on Testnet.
We’ve thoughtfully designed RUN to keep code safe for you as the developer as well as safe for the end-users to hold their assets. The goal of RUN is to make it so that you don’t have to think about security very much. If you were to write code that would be a bad idea and leave you vulnerable, we show you an error early, so that you can get it done in the right way to stay safe.
Contents
Getting started
Jigs are a lot like mobile apps, small programs that you download from ~~the App Store~~ the Bitcoin network, onto your device to run:
You probably take for granted that when you install a game app, it won’t open your bank app and steal your money. Why do we trust apps? Well, for one, your phone sandboxes them for you to make stealing private data impossible. Sandboxes are like compartments where inside apps can do anything they want but they can’t affect other apps or read data you didn’t share. If an app wants to leave the sandbox, for example, to take a selfie, it has to ask your permission.
Sandboxing code
RUN works like that too. RUN sandboxes jigs ensuring they can only do what their code allows and they only have access to what you give them when you call methods. A malicious jig won’t be able to secretly steal your funds or destroy your other jigs. And just like mobile apps, jigs are built specifically to work within a sandbox.
In the next section, you’ll learn how sandboxing works with jigs.
Things you can’t do
Nobody likes to be kept boxed in, but sometimes sandboxing is the best way to prevent disaster. You’ll soon pick up these patterns and they will become second nature.
Can’t access the global scope
You might try to create a jig that sets a variable outside of itself, like so:
var dragonPopulation = 0
class Dragon extends Jig {
init() { dragonPopulation += 1 }
}
new Dragon() // error!
If you run this, you’ll see an error that dragonPopulation does not exist. Why does this happen? Because your dragon doesn’t run in the same context as the rest of your code, even though it looks like it does. That’s the sandbox in action.
When you create a jig, RUN places it in an isolated environment and gives you a proxy of the real thing. The proxy is your interface into the sandbox, and although it isn’t the real thing, it has all the same methods and properties as the real thing.
This proxy enforces various rules that ensure you don’t do something with your jig that would destroy it or make it unusable by others. Like setting a variable outside of the jig:
A proxy is like a remote control that is smart enough to prevent you from breaking the real thing — for example burning out the engine of a car. Most of the time you won’t even realize you’re holding the remote.
You directly control things with the remote until the limits prevent you from hurting yourself.
Accessing outside classes with dependencies
Of course, sometimes you want jigs to access the outside world. Like when a mobile app asks permission to use your microphone.
Perhaps you would like to have one kind of jig to create another kind of jig, like a dragon laying an egg. Normally all dragons and all eggs would be in their own sandboxes, but you can tell the Dragon class about the Egg class by setting it as a dependency:
class Egg extends Jig {
hatch() { this.hatched = true }
}
class Dragon extends Jig {
layEgg() { return new Egg() }
}
// 1
Dragon.deps = { Egg }
// 2
const dragon = new Dragon()
dragon.layEgg()
Here’s what is happening in that code:
- You use deps to declare a list of classes that you will rely on.
- You’ll need to set the dependencies on the class before you create any instances because the dragon instance has to understand how to make use of an egg instance. With the dependency set, the Dragon class will keep the Egg class as a requirement that tags along everywhere it goes.
Preventing direct setting of properties
After reading some of the previous tutorials for RUN, you might wonder why you don’t set properties with the assignment operator, like this:
dragon.name = 'Empress'
Instead, you always use methods, like this:
dragon.setName(name)
The best practice advice is: program to interfaces, not to implementations. Standard JavaScript makes following that difficult because its interfaces are by default changeable and data by default public. In vanilla JavaScript, you can set any property, replace any method, and read any value at will.
RUN keeps your code safe. The only way to update a jig is by calling a public method. It’s that simple. The method-only interface pattern allows class creators to precisely specify what their jigs can do — no more, no less. That’s important for building safe and predictable contracts, both for users and also for jigs that interact with each other.
In addition to preventing direct assignments, RUN prevents you from deleting properties:
delete dragon.name // error!
And also, RUN won’t let you call private methods:
dragon._writeInDiary() // error!
A private method is merely a method that starts with an underscore. They are intended to be called only inside the class definition, not as an external interface.
RUN also supports private variables, dragon._diary. You can read private variables only inside the class, not from a separate jig class.
Proxies and sandboxes make all this possible.
Getting consistent results
Every action in RUN must be replayable by other people with the same result every time. That’s why random numbers and dates are particularly tricky. You’d get a different result each time you call Math.random() and new Date(). To stop that from happening, RUN hides the non-deterministic parts of the JavaScript standard library:
class Dragon extends Jig {
init() {
this.birthday = new Date() // error!
this.luckyNumber = Math.random() // error!
}
}
Note: You may stumble across various parts of the standard library that are missing at the moment. But much of the functionality of standard library calls can be accomplished in alternative ways. If you find something specific that trips you up, just reach out and we’ll help you work through it. == Only the owner can alter a jig ==
Jigs always have an owner. Only that owner can call a method to take an action with the jig. Because of that, if you analyzed the blockchain, you’d see each jig has a single unbroken chain of updates that can be replayed one-by-one. You always know where a jig came from and you can always verify that each action was fair. How smart!
RUN uses unspent bitcoin outputs, or UTXOs, to represent a jig’s state and its owner. Only the holder of the private key can unlock those outputs to update the jig. A jig’s ownership and state are as secure as any bitcoin transaction.
Serializing jigs as text
Jigs must be able to be transformed into text. The good news is, yet again don’t have to worry because RUN does that automatically. However, if you store or pass an object into a jig that RUN doesn’t know how to render as text, you will see an error:
class Database extends Jig {
set(key, value) { this[key] = value }
}
const database = new Database()
database.set("boolean", true) // ok
database.set('symbol', Symbol.hasInstance) // error!
database.set('anonymous function', () => {}) // error!
database.set('error', new Error()) // error!
database.set('buffer', Buffer.alloc(1)) // error!
Where to go from here?
Despite the limits, people are already using RUN to build games, fiat stable coins, and unlockable music and video.
Challenge yourself to see what you can build inside the limits. Eventually you’ll come to understand that those limits are, by their nature, what allows creativity to flourish.
Let us know what tutorial you’d like us to write.
You can now head over to Tutorial 7 (extra) - Private key management