Use sCrypt to implement stateful Bitcoin smart contracts

Bitcoin smart contract

The capabilities of Bitcoin scripts are generally considered to be very limited, making it impossible to implement complex smart contracts. An often criticized point is its inability to implement stateful smart contracts . One of the main reasons for the emergence of Ethereum is to overcome this problem.

Some contracts are indeed stateful, because these contracts require participants to interact with them in multiple stages of the contract and rely on state that changes over time, such as on-chain voting or games. Next we will show a general mechanism for managing state in Bitcoin smart contracts. We will also use the sCrypt language to implement a stateful contract. The sCrypt language is a high-level language that can be compiled into the Bitcoin scripting language.

Prerequisite knowledge: OP_PUSH_TX

Before studying how to manage state in Bitcoin smart contracts, let's review a powerful technology OP_PUSH_TX . You can think of it as a pseudo-opcode, which puts the current transaction on the stack so that it can be used at runtime. More precisely, it allows us to view the pre- hash data (preimage) used in the signature verification defined in BIP143 . The format of preimage is as follows:

sighashPreimage

Implement stateful contracts

Once we can access the current transaction context of the contract through OP_PUSH_TX technology, we can set arbitrary constraints on its inputs and outputs.

One way to implement state in a contract is to divide the locking script into two parts: data and code. The data part is the state. The code part contains the state transition rules, which is the business logic of the contract. The data part can be appended to the code by means of OP_RETURN <data>or OP_PUSHDATA <data> OP_DROP. Although the data part will not be executed, the code part before it will use it, so the data part will still affect the contract.

With OP_PUSH_TX, we can get the locked script content of the spent output from item 5, and the new output content from item 8. In order to manage the state, we require that the code part of the locked script cannot be changed (that is, the contract rules cannot be changed), and the change of the data (state) part must comply with the state transition rules specified in the code part.

code_and_data
This is similar to the concept of objects in object-oriented programming. The code part is the method of the object, and the data part is the member variable of the object. Object methods are immutable. Member variables are encapsulated, and they can only be changed through object methods. The object method is called by the unlocking script. The unlocking script selects which method to call and passes in the parameters of the corresponding method.

An example contract: counter

Let's look at a simple example of a stateful contract: a counter contract, which records how many times the increment() method of the contract is called. The contract code and comments are as follows:

import "util.scrypt";

contract Counter {
    
    
	public function increment(bytes txPreimage, int amount) {
    
    
        require(Tx.checkPreimage(txPreimage));

        bytes scriptCode = Util.scriptCode(txPreimage);
		int scriptLen = length(scriptCode);
		// last byte contains the state, i.e., counter
		int counter = unpack(scriptCode[scriptLen - 1 :]);
		// increment counter
		bytes scriptCode_ = scriptCode[: scriptLen - 1] + num2bin(counter + 1, 1);
		bytes hashOutputs = Util.hashOutputs(txPreimage);
		// output: amount + scriptlen + script
		Sha256 hashOutputs_ = hash256(num2bin(amount, 8) + Util.writeVarint(scriptCode_));
		// ensure output is expected: amount is same with specified
		// also output script is the same with scriptCode except counter incremented
		require(hashOutputs == hashOutputs_);
    }
}

First, make sure that preimage is indeed the current transaction:

require(Tx.checkPreimage(txPreimage));

Then, we get the content of the previous lock script, which is the scriptCode of preimage item 5 mentioned above :

bytes scriptCode = Util.scriptCode(txPreimage);

Then, the state of the previous counter (that is, the value of the counter) is extracted from the scriptCode :

int counter = unpack(scriptCode[scriptLen - 1 :]);

Next, add 1 to the counter value and place it in the new lock script. Note that the counter value is the only changed part of the lock script:

bytes scriptCode_ = scriptCode[: scriptLen - 1] + num2bin(counter + 1, 1);

Finally, make sure that the new lock script is included in the output of the current transaction:

bytes hashOutputs = Util.hashOutputs(txPreimage);
Sha256 hashOutputs_ = hash256(num2bin(amount, 8) + Util.writeVarint(scriptCode_));
require(hashOutputs == hashOutputs_);

Here is the code to deploy the contract. Calling the contract method repeatedly increment(), the contract instance with the counter increasing from 0 to 9 can be seen here: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 . Please note that the counter status data is at the end of the script of the first output of each transaction. As shown below:

counter

to sum up

The purpose of this article is to show what Bitcoin smart contracts can do and how to implement them. Many of the so-called script limitations are due to not realizing its potential. As we further explain and demonstrate what the script can achieve, people will find that it has extremely strong scalability, versatility and future-oriented features. We will show that the Bitcoin network without artificial restrictions can run any smart contract that can run on other blockchains, and has the ability to expand infinitely. Coupled with some economic incentives, many cross-industry applications can be made more efficient and safer.

Guess you like

Origin blog.csdn.net/freedomhero/article/details/107307306