Recursively Smelted Smart Contracts

Part I — Basic Theory

“The basic idea of smart contracts is that many kinds of contractual clauses (such as liens, bonding, delineation of property rights, etc.) can be embedded in the hardware and software we deal with, in such a way as to make breach of contract expensive (if desired, sometimes prohibitively so) for the breacher.” — Nick Szabo

Introduction

The seven main goals we wish to achieve with Recursive Smelting’s smart contract system are stated as follows:

Expressive — the smart contract system should allow for a wide range of applications. Verifiable — all users should be able to verify a contract has been executed correctly. Dual-tiered — the execution of the smart contracts does not fall on the users rather the “executors”. Low risk — breach of contracts should be strongly disincentivised. Segregated — smart contracts are allowed to live in distinct economies/ecosystems to one another. Small footprint — the amount of data stored on the blockchain should be minimised without sacrificing expressiveness. Private — the smart contracts state is not apriori public.

Due to Ethereum’s success we use it as an example throughout. One can see that Ethereum handsomely meets three of these goals: Turing completeness is as expressive as it gets, the miners execute the smart contracts rather than the typical user (dual-tiered), risk is certainly low due to the decentralised nature of mining and the consensus mechanisms in place.

However, what would it take to truly verify that a smart contract executed correctly? An Ethereum node — the entry requirement is too high for the average user. One might rightly argue that the “everyone needs to verify everything” level of paranoia is unwarranted given the game theory of mining but still the fact remains, Ethereum smart contracts are, in the strict sense of the world verifiable, but not in the practical sense of the word for most users (this recent paper addresses this).

Ethereum is certainly not segregated — users bid to get their transactions into a finite block and hence bid against each other to execute their smart contract. This auction is Ethereum-wide and, if a particular smart contract gains considerable attention (e.g. the dreaded “Cryptokitties”), the other users will have to bid (and wait) accordingly. In part, this is what Plasma fixes — contracts are temporarily segregated into an off-chain container, using the main chain as an arbitrator during “Plasma exits”.

Ethereum’s footprint is certainly not small — the blockchain, at the time of writing, is ~100GB. While manageable for miners and perhaps not having adverse consequences for centralisation, the storage overhead is implicit in every fee you pay to the miners.

Nomenclature

We take a quick tour of the notation and definitions needed. I imagine much the audience will be familiar with much of this section. Skim as you please!

Graph Labelling

A graph is a collection of vertices, V, and edges, E.

An vertex labelling of G is a function f : V → S where S is a collection of labels. Similarly, an edge labelling of G is a function f : E → S’.

2.1 A graph labelling

Directed Acyclic Graphs

A directed graph G is a collection of vertices V and edges E⊆V×V.

2.2 A directed labelled graph

We denote the edges (v, u) entering into a vertex u as in(u) and edges (v, u) exiting v as out(v).

A directed acyclic graph (DAG) is a graph where directed edges don’t make a closed cycle, that is, you cannot walk back to where you came from following the directions of the edges.

2.3 A directed acyclic labelled graph

Support of a Labelling

We describe a pair (f, g), where f is a vertex labelling and g an edge labelling, as a labelling.

Suppose G is a DAG and G’ is a subgraph. We say a labelling (f, g) of G has support on a DAG G’ if f and g are mapped to ⊥ everywhere else, we denote this as supp(f) = G’.

2.4 A labelling supported on subgraph

It will turn out that vertices within the support are token transactions and those outside are regular transactions.

Logic and Order Theory

The wedge symbol ∧ is used here to denote logical conjunction and, in a similar way to how

are used to denote the sum and product over a set X, we denote

as the logical conjunction over X.

Suppose x and y are vertices in a DAG. We say that x ⋖ y if there is a directed edge from x to y. We say that x < y if there is a sequence x ⋖…⋖y; that is, there is a directed path from x to y. Similarly, x ≤ y is x < y or x = y.

We denote the collection of all vertices c such that x ≤ c ≤ y as [x, y], the collection of all vertices c such that x ≤ c as ↑x and c ≤ y as ↓y and the {c ∈ G : ∄y s.t. c ⋖ y} as end(G).

The DAG of Bitcoin Transactions

As you probably guessed by now, the collection of Bitcoin transactions fits the definition of a DAG — transactions are vertices and edges are outputs redeeming inputs.

For transactions t and t’ we see that t ⋖ t’ implies that t’ spends one of t’s outputs and say that t is a parent of t’. If t < t’ we say that t is in the history of t’.

Note that x < y in G may not mean x < y in some subgraph G’. We assume that all order notation is assuming order given by the Bitcoin transaction DAG.

One can imagine various ways of labelling this DAG — one example is labelling each vertex with their transaction ID and the edges with their output scripts. Another possible edge labelling that is relevant to the topic is: label each edge with the value (quantity) of the output. We now see an interesting property emerge — the summation over the input labels equals the summation over the output labels. This is conservation of bitcoin quantity and is enforced by the Bitcoin miners.

A simple example of this might be: each transaction, t, contains an OP_RETURN specifying the type of transaction (genesis, minting or transfer) and the various quantities for each output (defining the edge labelling on out(t)). Specifically, f(t) = (transfer, (3, 4, 2)), and g(t, t1) = 3, g(t, t2)=4 and g(t, t3) = 2.

Examples employing this method include: Coloured Coins, SLP and BitcoinToken. They provide a nice labelling of the Bitcoin DAG edges but there is no apriori reason to expect the same conservation of (token) quantity holds as it did above — miner’s don’t enforce it. This is where and why user consensus is injected, which we will visit next.

To summarise, DAG tokens piggy back on the miner enforced topology of the Bitcoin transaction graph and add an overlay to it by supplementing transactions with data defining vertex and edge labelling. The vertex and edge labelling encodes the data structure needed to construct a user consensus.

User Consensus

Local Checks

A local check C : V → {true, false} is a function of the form

where c is Boolean valued computable function and c(⊥, …) = false. A local check is basically a check performed by analysing a vertex’s label and its in-going and out-going edge labels.

As we saw above, if we had a labelling, (f, g) with f(t) = (transfer, (3, 4, 2)) and g(t, t’)=3, g(t, t’)=4, …, then we see that the conservation of quantity fits the definition of a local check:

DAG Tokens — Part II

How would one create a consensus rule from local checks? We have now returned to the topic of DAG tokens.

A DAG token is defined by its local check C which has the following restrictions: If all t’ ⋖ t are such that C(t’) = false then

C(t) = false, or t=gen and C(gen) = true where gen is a distinguished genesis transaction.

The process to determine whether t is in the token’s ledger L is described by the following mechanism

5.1 DAG consensus mechanism

Therefore, to check that t is a valid transaction (in the ledger) we first perform a local check on t and then check whether all parent transactions in the supp(f) are in L. This defines a traversal backwards through [gen, t’] ∩ supp(f), finally terminating at the genesis transaction which by definition has no parents in supp(f).

The first most immediate observation should be that, if f(t) = ⊥ we have that t is not in the ledger. We can see this by noting that t fails the local check by definition and hence the conjunction in (5.1) returns false.

The second more subtle observation should be that the ledger must be a connected subgraph: Suppose that it wasn’t and we had a component separate to gen. We then have a transaction t in L where for all t’ ⋖ t we have that C(t’) = false. By the property of DAG’s we see that either t is gen or t is not in L — a contradiction.

Recursive Smelting

The Seven Goals and DAG Tokens

Expressive vs Small Footprint: If we want the smart contracts to be expressive then we need to support large (possibly growing) states recorded in the transactions and hence the blockchain footprint will grow accordingly?

Dual-Tiered, Verifiable and Low-Risk: What if the graph becomes too big and the execution becomes unwieldy? The users will fail to verify the chain of transactions resulting in either an increased chance of fraud or the reliance on trusted third parties. How can we outsource execution of a smart contract in a way which keeps risk low?

Private: Suppose we wanted a smart contract to produce statistics from sensitive data without exposing the individuals data points. Example: Medical records, elections etc. How can we keep the inner workings of such a smart contract private but at the same time keep it verifiable?

States and Transitions

At a (very) high-level, Ethereum smart contracts are coupled to an address. The address points to the contracts current state and rules concerning its operation. A contracts state is updated when the address receives inputs and, if necessary, produces outputs to other addresses.

How can we mirror this in a DAG token? Ethereum’s contract structure maps almost 1–1 to what we’ve defined so far, heuristically:

Inputs and outputs with data attached are edge labellings. Contract states are vertex labellings. The rules concerning how to update a state are encoded by local checks.

This is the approach taken by the great work by Clemens Lay on his token BitcoinToken and the approach we build on. We’d like to note this is not a case of blatant plagiarism — this predates the release of BitcoinToken.

Recursion and Traversal

Recall the DAG consensus mechanism (5.1)

What we can do with SNARKs is compress the right hand term into something more manageable rather than the rather unwieldy traversal and evaluation of t’s entire history back to the genesis transaction.

zk-SNARKs

Ignoring subtleties, zk-SNARK scheme typically includes two functions:

The prover

where S is a statement (taking x and y as arguments), x is the public input, y is the private input and π is the proof attesting to S(x, y).

The verifier

which checks the proof of s(x, y) when the public inputs x are given and returns true or false appropriately.

SNARKs take milliseconds to verify and are typically less than a kilobyte in size.

The Crux

This will be rather mind-bending so please take time with the following sections.

At a high level, in Recursive Smelting the sender embeds a proof, πt, of

within the vertex label f(t) and provides users a way of coupling each t’ described in the proof with a t in the real transaction graph.

We then construct C(t) in a way which checks πt and hence, by (5.1) the local check becomes sufficient to check whether t is in the ledger!

We approach this with three attempts, the first two of which have serious flaws and the final one being the specification for Recursive Smelting.

First Attempt — Non-Recursive

Let C(t) be the following statement:

The smart contract executed correctly in t. t contains a proof, π, proving a statement of the form S specified below. V(π) = true.

Let S be the statement:

This is a nice prototype for using SNARKs. If a user wants to verify a transaction t they simply check that the smart contract executed properly, that the transaction has a appropriate proof π attached and then verify that the proof is correct. We may conclude that (5.1) holds!

Alas, it can’t be that easy. We’ve made life simple for the verifiers of transactions, but to send a transaction you need to generate a proof, and to generate a proof you need every single transaction in [gen, t’] ∩ supp(f). This means that to send a transaction you’d need to traverse the graph anyway to collect all the inputs for the proof — not to mention the enormous overhead of feeding potentially thousands of transactions into the prover. Back to the drawing board.

Second Attempt — Recursive

Let C(t) be the following statement:

The smart contract executed correctly in t. t contains a proof, πt, proving a statement of the form S below. V(πt)=true.

Let S, taking as private inputs a list (t1, …, tn), be the statement:

C(ti) is true for all ti.

Take time here to notice the recursion. Each token transaction t will contain a proof πt which is generated by the sender via the execution of P(S, -, (t1, …, tn)) where (t1, …, tn) are the transactions ti ⋖ t.

On verifying πt we know that C(ti) holds for all ti ⋖ t. That is, for every ti ⋖ t we have: a SNARK with correct form is contained, the smart contract executed correctly, and for each tj ⋖ ti such that tj in supp(f) we have that C(tj) is true. This recursion continues until we reach the genesis transaction.

Great! One checks C(t) by checking smart contract execution in t and that V(πt)=true then concludes that t is in L!

Alas, it can’t be that easy. On closer inspection there exists a catastrophic attack vector:

Alice creates a fraudulent token transaction tf. The outputs favour Alice by incorrectly executing the smart contract or breaking consensus rules in some way. Alice creates a second token transaction, t, spending, tf. This time she executes the smart contract correctly but as her inputs are from her own invalid token transaction she reaps the benefits of the fraud. Now instead generating her own proof πt, she inserts a proof πt’ copied from a valid token transaction t’ elsewhere in the ledger. Bob wants to verify t is in the ledger — he finds that the smart contract executed correctly, that the statement S proved in πt has the correct form and that V(πt)=true. Bob believes that t is a valid token transaction when, in reality, it spends an invalid token transaction and hence is itself invalid.

What went wrong here? By making the parent transactions of t private inputs we shrouded their true nature — Alice was able to fool the verifier/receiver by using a proof which actually gave credence to t’ and used it, erroneously, to give credence to t. Notice this attack is even more obvious in the first attempt.

Can we make the parent transactions of t public rather than private inputs, you might ask? The answer is no — if they were public inputs for πt then they’d be public inputs for every proof and so, like our first attempt, we’d have to pass all transactions in [gen, t’] ∩ supp(f) into the verifier in some way.

This is the subject of coupling — we need to ensure that the proof we verify is indeed relating to the transaction it lies within while keeping transactions privately hard-coded within proofs. We return to using public inputs but like the ham-handed way in our second attempt.

Third Attempt — Recursive and Coupled

Let C(t) be the following statement:

The smart contract executed correctly in t. t contains a proof, πt, proving a statement of the form S below. V(πt, iht)=true for iht = hash(txid1 || txid2 ||.. || txidn) where the txidi are the transaction ids given in t’s inputs.

Let S, taking a public input ih and private inputs a list of transactions (t1, …, tn), be the statement:

hash(id(t1) || id(t2) ||.. || id(tn) ) = ih where id(ti) is the calculated transaction id of ti. C(ti) is true for all ti.

One should mull over this for some time and absorb its implications.

Reiterating: If Alice wants to create a transaction t she produces and then embeds the proof generated by P(S, ih, (t1,…, tn)) where ih is hash(id(t1) || id(t2) ||.. || id(tn) ) and t1, …, tn are the parent token transactions of t. If Bob wants to verify the transaction t is in the ledger he checks the smart contract executed correctly in t, checks the statement in πt has the correct form and evaluates V(πt, ih).

Let’s revisit the attack we discovered in the third attempt.

Alice creates a fraudulent token transaction tf. Its outputs favour Alice by breaking consensus rules. Alice creates a second token transaction, t, spending tf. She executes the smart contract correctly and inserts a proof copied from a valid token transaction t’. Bob wants to verify t is in the ledger — he finds that the smart contract executed correctly, he calculates iht = h(id(tf)) and he finds that V(πt’, iht)=false. This is because the inputs to t’ and inputs to t are not identical and if they were we either have a double-spend or a hash collision on our hands. Bob rejects t2.

Ok, but what about if Alice buries the fraud deeper?

… … Alice creates a third token transaction, t2, spending, t, thus reaping the benefits of the fraud (in tf). Again, she executes the smart contract correctly and then attempts to create a proof πt2 to bury her crimes deeper within the transaction graph. To do so, she needs to first calculate iht2 = h(id(t)) and then execute P(S, iht2, t). πt2 isn’t generated because S isn’t true with private input t2 and public input iht2. Why? It contains term V(πt’, iht) which we saw from the attack attempt above was false.

Awesome! This looks like a good candidate.

Concluding Remarks

Lets recap what we’ve achieved here:

We laid out useful notation and expressions. We have discovered a good candidate scheme for creating zk-SNARK infused DAG smart contracts. We have looked at a couple of attack vectors.

We still have some work ahead of us, however:

Highlight subtleties, fill in caveats, and address additional attack vectors. Show that this smart contract system fulfils the seven goals we outlined. Give example applications and implementation details.

These will be the topics of the articles to come. Stay tuned!