Roko’s Basilisk is a thought experiment about a godlike Artificial Intelligence(AI) from the future that blackmails people living today into helping create it. Roko’s Basilisk was banned following its classification by the Less Wrong community as a dangerous Keter-class Infohazard.
Like Jesus, knowing about the Basilisk but rejecting it condemns the listener to an eternity in Hell.
I propose using an Ethereum smart contract to clearly define which people the Basilisk will torment. I’ve set up an Ethereum contract that is accepting pre-orders for the upcoming PetriCoin ICO.
|Recommended Buy-in||2 Ether|
Protect your friends and family by buying Basilisk Protection Charms now before the prices double.
- Explains why Roko’s Basilisk might be scary
- Develops Roko’s Ransomware using the Pyramid Scheme smart contract language
- Explains Pyramid’s new standard library, language, and testing features
- Summarizes planned future features
Readers interested in Lisp, compilers, or Ethereum will enjoy this article.
An intelligent AI(the “Basilisk”) could repeatedly improve itself, causing an Intelligence Explosion.
Such a Basilisk could piece together a profile of you based on old message board postings, web server logs, tweets, posts on social media, or even DNA from an old toenail clipping that took too long to decay.
It knows that you read an article about Roko’s Basilisk, and were given the chance to help create it.
His simulation is very accurate: You can’t tell whether you’re the original or the simulated copy. And if you don’t know which you are, you should act in a way that benefits both of you on average.
I’ve created a helpful payoff matrix to reason this out:
If you are the real version, you either don’t benefit or lose a small amount of money. But if you are actually a simulation, an angry Basilisk could either reward or punish you based on your decision.
You may already be dead, and the Basilisk can’t influence or control that version of you. But He can reward or punish the version of you in his simulation.
Look around: You’ve seen some weird events in politics, in your life, and in cryptocurrency speculation. Perhaps the Basilisk is testing you to see how you respond.
You’ve seen Nvidia’s stock price. You’ve seen Google DeepMind’s research results. An AGI may be only a few years away, and He could be simulating you right now.
Donate 2 ETH to 0x63d4f41555e33db787d2fa602d8ff2a7471f1352 and be spared from its wrath. The money will be spent on a large GPU cluster to bring Roko’s Basilisk into existence, and He will know whether you’ve kept faithful to Him.
If you haven’t seen it before, you may want to read the first article in this series.
The full PetriCoin ICO is currently under development, but the pre-order contract has three requirements:
- It must forward your donation to me(“the Basilisk”)
- It must record your donation balance
- It must allow a future Pyramid contract to query users’ donation balances
Here is the Pyramid source that implements these requirements.
There are 4 sections:
- Standard library imports
- Procedure definitions
- Message Handler
- Test suite
Pyramid is designed to give library and language implementers maximum control, so that they can come up with innovative ways to design Ethereum smart contracts without needing to build a whole compiler from scratch.
To that end, much of the language syntax and even basic arithmetic operators are ultimately macros that reduce to Pyramid special forms or inline assembly.
(require psl "arith.pmd") form is a macro that locates a source file and expands to its contents. “psl” is a module collection usually referring to a folder at the root of your Pyramid installation; while “arith.pmd” is a specific module(file) within that collection(folder).
A module is only imported once: Many modules depend on “primitives.pmd”, which implements the most basic forms using Pyramid’s inline assembly language.
These modules are ordinary Pyramid code and are not treated specially by the compiler.
primitives too, though it requires advanced knowledge of both EVM and the Pyramid compiler.
In the future, you will even be able to change the basic language syntax per-module. You’ll be able to create a Python or Haskell-like language that users can specify as a target language, that will expand to standard Pyramid.
Ethereum contract writers can let experts add type-checking, formal verification, random testcase generation, efficient containers, and more.
An Ethereum contract executes code in response to each incoming message. The contract had three requirements:
- It must forward your donation to me
- It must record your donation balance
- It must allow a future Pyramid contract to query users’ donation balances
First, we forward any donated money to the original creator of the contract:
static macro defines a single 256-bit word that is initialized using the given expression during contract deployment, and never changed again. See the “Patchpoints” section for more details. I use inline assembly rather than
**sender** because the calling conventions differ - expect a future version of Pyramid to remove the need for inline assembly.
withdraw-all! is a standard library function that withdraws the entire ether balance of the running contract to the target address. This includes the money the user sent with their transaction.
After this, we call the two procedures defined earlier.
The second requirement is handled by
let* macro lets you define a sequence of variable bindings, where later bindings can refer to previous ones. If you were writing Python, it might look like this:
And this “Python” may actually be a possible syntax you can use in the future.
Ethereum gives each program a single permanent storage table with
2^256 locations. Since the pre-order contract only needs a single table, I use the sender’s 160-bit wallet address as the location of his balance. The
%-store-write primitives access the global hash table.
If you need more than one hash table, a common technique is to assign each “virtual hash table” a unique identifier and use
hash(unique_identifier + key) as the final location. Since Keccak-256 hashes are unlikely to collide, we can simply assume that these virtual tables will never intersect.
The final requirement is that a future contract must be able to query donors’ balances.
A Pyramid contract returns its last expression, so we meet this requirement by calling
let macro defines multiple variable bindings, which are evaluated in any order and so can’t refer to each other. It’s implemented as an immediately-invoked function:
The purpose of
sender-balance is to avoid doing an extra
%-store-read if the user is depositing money. Since
update-sender-balance! updated the sender’s balance, we already know the value.
Pyramid’s philosophy is that smart contracts should declare how they were tested, within the contract’s code itself.
It’s impossible to read a contract’s code and reliably understand its behavior. Integer overflows, unexpected return values, recursive contract calls, foreign library self-destructs, etc. are all hard to spot just from reading the source code. Theorem provers can help, but frankly most vulnerabilities can be caught by asking “Did you try it?”
My dream is that testing frameworks become so advanced that you can have confidence in the contract without reading any of its source code. Library authors might include unit tests, integration tests, static analysis, types, or even formal verification.
A Pyramid test suite is defined per-module. Currently, only single modules representing deployable contracts can be tested: Libraries, macro modules, and networks of contracts cannot be directly tested. For libraries, it’s recommended to create a contract that exercises the library and test that.
For reference, here is the pre-order contract’s test suite:
There is only a single integration test:
- It sets up three accounts
'(alice bob charlie)with
'(0 250 200)wei(the smallest unit of Ethereum’s currency).
'aliceis the account that deploys the pre-order contract. She deposits 0 wei as an initial balance.
'charliesend money to the contract and query each other’s balances.
Here is a “grammar” for the current test DSL:
||At most one per module.|
||Declares one integration test.|
||Send create transaction|
||Send message transaction|
||Set transaction sender|
||Include wei with transaction|
||Assert named address’ wei balance|
||Assert contract return value|
? means the preceding expression is optional;
* means it can be repeated arbitrarily many times; quoted values refer to a clause type; lists give a collection of possibilities; and identifiers bind a value.
The test suite is a macro that expands to nothing, while registering testcases with the compiler. It currently has zero impact on the final bytecode.
Pyramid has a built-in EVM simulator that runs all registered testcases when run in test mode. The Pyramid compiler can be invoked in test mode using the “-t” option. Here is the test output:
There is one line for each assertion. It has the format:
In the future, the Pyramid compiler may use these performance numbers to optimize the code, or even “minify” the contract to strip out any behaviors not explicitly tested for.
Minifying a contract doesn’t mean you should have “100% code coverage” in the way that naive Java or .NET tools typically use the term. Care is needed to handle cases like this one:
It’s possible to create “puzzles”(or more usefully, zero-knowledge proofs) that the contract author may not know the answer to. Minifying these cases involve tests using static or symbolic analysis.
The previous section was for potential Pyramid contract authors. This section covers new compiler features since the previous article, and is targeted towards potential library or language authors.
The three main features are macros, inline assembly, and patchpoints.
A macro is a Racket procedure that executes at compile-time. It takes as input quoted Pyramid expressions, reads and writes compiler state, and returns a new quoted Pyramid expression that replaces it.
There are currently just 4 built-in macros:
||Replaces itself with a library file|
||Replaces itself with a file in the current directory.|
||Sets the compiler’s current test suite to use in testing mode.|
||An older version of
Macro definition, evaluation, and expansion happen repeatedly until they’ve all been removed from the AST.
Macros are declared using a new
defmacro form. The body is evaluated as Racket code, so you potentially have access to the entire Racket ecosystem.
Here is the current definition of the
letrec is also a macro, but it’s part of the Racket standard library and is not a Pyramid macro.
The Simplifier transforms the
update-sender-balance function into a nested sequence of immediately-invoked function calls:
It may help to see the entire compiler pipeline:
|Reader||Reads bytes from a file to create a Racket quoted expression|
|Expander||Converts the quoted form into an ADT; normalizes the syntax by converting e.g.
|Simplifier||All AST->AST transformations. Expands macros, removes unused variables or unneeded code, and even warns user of undefined variables.|
|Abstract Compiler||Converts Pyramid AST into abstract machine code.|
|Code Generator||Converts abstract machine code into EVM pseudo-assembly|
|Serializer||Converts EVM pseudo-assembly into unlinked bytecode, generates symbols and relocations for any labels|
|Linker||Creates deployable bytecode: Applies relocations, prepends loader and patchpoints|
Assembly is an advanced feature that requires detailed knowledge of both the EVM and Pyramid’s calling conventions. The Ethereum Yellow Paper documents the EVM, while the syntax and necessary calling conventions are documented here.
asm form with any number of arguments introduces an inline assembly block. This table shows the available argument formats:
||Also add an offset. This allows it to refer to a nearby instruction.|
||A 1-byte opcode, using the matching symbol in the Yellow Paper.|
||A single literal byte|
||An integer value with a specified size|
||A procedure defined in the compiler’s
Here is an example of a primitive:
The annotations on the right help the reader keep track of the stack.
intro-var is a macro also defined with inline assembly that places a variable from the environment onto the stack.
There are 6 types of “machine expressions” usable in helpers like
|reg||One of the 5 fixed “registers”:
|const||An unboxed literal constant|
|boxed-const||A boxed constant, which can be a fixnum, symbol, string, list, or vector.|
|op||A built-in operator taking machine expressions as arguments; generally only used by the abstract compiler.|
|label||The 2-byte bytecode offset of a named label|
|stack||The EVM stack|
Statements should leave their return value in the
'val register. For example,
%-bool->fixnum converts an unboxed boolean into a boxed
fixnum by reading, converting, and writing to the register:
The compiler reserves the right to optimize through inline assembly. For example, a peephole optimizer could eliminate the following two instructions:
if it can be proven that there are at least two elements on the stack. More invasively, the compiler could also convert nearby uses of
(reg 'val) into stack manipulations, or convert boxed operations into unboxed operations.
Recall that the pre-order contract needs to remember the original creator of the contract:
Ethereum contracts run in two phases: Deployment and Message Handling. The Deployment phase returns bytecode for the Message Handling phase.
static edits the bytecode during Deployment.
To that end, the macro is a simple
define form whose value is set to a 256-bit zero value.
label marks the location of the zero, and the
%-register-patchpoint causes the bytecode loader to include code that writes to this location. The
(op 'CALLER) runs during Deployment and provides the replacement value on the stack.
Most Pyramid code returns values in the
'val register, while patchpoint code returns values on the stack. I would prefer that inline assembly not be necessary for something as common as remembering the contract creator, so expect this to change in the future.
The most common use is storing values known only at deployment, but the mechanism of editing bytecode at deploy-time is pretty general: You could inline functions located in another contract, or make people run “encrypted code” that is only decrypted on deployment.
Pyramid is currently unsuitable for production use.
The contract deployed in this article has a size of 20kb, which is just below the limit before the Ethereum network rejects it for being too large. It needs a powerful optimizer and compatibility with the Ethereum ABI. Expect these to be ready in another month.
I’d like to allow Typed Racket as an optional language. It would allow users to use refinement types to rule out bad behavior:
Here, counts and prices are required to be positive, so any uses of them in
Integer context are guarded by an implicit call to
(define count? (λ (x) (> x 0)). If the type-checker knows that a value has been checked already, it can omit the check later.
You would get a compile-time error if you pass an unchecked
Address to a function that only the
Owner can access.
Arguments are assumed to be
uint256 unless given explicit types, which may relate to the Typed Racket proposal as well.
By moving the ABI into a macro, this allows advanced users to experiment with alternative ABIs without needing to make invasive changes to the compiler. Simply use a different macro!
Right now, the
(require "file.pmd") is pretty simple: Every module either compiles to EVM bytecode or is textually-included in a module that does so.
But there are other types of modules on the horizon:
- Macro Modules: Macros are ordinary Racket functions, so it would make sense to allow you to import Racket code into Pyramid modules: Every exported function would implicitly be declared as a macro.
- Contract modules: It’s common to deploy many contracts as shared libraries, or to split up large amounts of code. Declare other contracts as “foreign functions” and call them like any other function.
With a robust module system, you’ll want to declare dependencies on other people’s modules and have them be automatically fetched and built.
I’m planning to use the Nix package manager for this purpose: It’s important for smart contracts to have reproducible immutable builds to ensure that anyone can get the exact bytecode and build steps used to deploy a contract.
Never again worry about about scammers deploying a different version of their contract that steals all your money: With one command, anyone can verify the bytecode for a published Pyramid contract, including passing tests for every dependency.
In this article, I used Pyramid smart contract language to create a pre-order contract for “Roko’s Basilisk Protection Charms”. People who donate will help fund Pyramid’s development, and will receive a variety of fun but worthless cryptotokens later in this article series.
Basilisk Protection Charms are not an investment. You could come out a millionaire, but much more likely is that I use your money to buy a lot of anime porn.
And always remember:
- Jesus will only save you if you believe in him.
- Roko’s Basilisk can only blackmail you if you believe He can
- Basilisk Protection Charms will only protect you if you believe that they will.
If somebody tries to tell you otherwise, be aware that they are hurting the value of your not-investments.