In-depth study of OP_PUSH_TX of Bitcoin script (2)

In the previous article , we introduced a powerful OP_PUSH_TX . It allows access to the current transaction in the Bitcoin smart contract, so that many complex functions can be implemented, such as stateful contracts . This article will further introduce an interesting extended contract on this basis.

Tx.checkPreimageAdvanced() 方法

sCrypt in the standard library contract Tx provided in a Tx.checkPreimage(txPreimage)way to achieve OP_PUSH_TX basic functions, it can meet the needs of many scenarios. However, as contracts become more complex, more customization may be required. For example, the temporary key k needs to be specified in the contract using R-Puzzle .

checkPreimageAdvanced is another method of sCrypt standard contract Tx , which provides more parameters that users can adjust. In addition to the sighashPreimage parameter, checkPreimageAdvanced()more parameters are added to the method to control the ECDSA signature of OP_PUSH_TX .

Tx.checkPreimageAdvanced(txPreimage, privKey, pubKey, invK, r, rBigEndian, sigHashType)

  • privKey and pubKey : ECDSA key pair
  • invK :modulus reciprocal of k , temporary key
  • r :the x coordinate of R ,calculatedby kG
  • rBigEndian : Signed r in big endian mode. Strictly speaking, this parameter can becalculatedby the parameter r , but this requires a lot of calculations in the script, and the cost is high. So we provide this parameter so that it can be calculated off-chain.
  • sigHashType : The SIGHASH identifier is used to specify which part of the transaction needs to be signed by ECDSA.

ANYONECANPAY counter contract

In another article , we implemented a counter contract that can record how many times its function is called. But it has a disadvantage. The contract needs to pay the transfer fee by itself. Once the funds in the contract are exhausted, it can no longer be called.

We can use checkPreimageAdvanced()the sighashType to improve counter contract parameters. By setting sighashType to SIGHASH_ANYONECANPAY , we allow additional input after the first input of the unlocked counter contract, so that the caller can pay the transfer fee by adding input. We also allow the caller to add another output, and transfer the remaining funds as change. The complete code is as follows:

import "util.scrypt";

/**
 * Demonstrates TxAdvanced, with external funding (additional input) and a change output
 */
contract AdvancedCounter {
    
    
	public function increment(bytes txPreimage, int amount, Ripemd160 changePKH, int changeSats) {
    
    
        // The following arguments can be generated using sample code at
        // https://gist.github.com/scrypt-sv/f6882be580780a88984cee75dd1564c4.js
		PrivKey privKey = PrivKey(0x621de38d9af72be8585d19584e3954d3fd0dc9752bb9f9fb28c4f9ed7c1e40ea);
		PubKey pubKey = PubKey(b'02773aca113a3217b67a95d5b78b69bb6386ed443ea5decf0ba92c00d179291921');
        // invK is the modular inverse of k, the ephemeral key
		int invK = 0xa2103f96554aba49bbf581738d3b5a38c5a44b6238ffb54cfcca65b8c87ddc08;
        // r is x coordinate of R, which is kG
		int r = 0x00f0fc43da25095812fcddde7d7cd353990c62b078e1493dc603961af25dfc6b60;
        // rBigEndian is the signed magnitude representation of r, in big endian
		bytes rBigEndian = b'00f0fc43da25095812fcddde7d7cd353990c62b078e1493dc603961af25dfc6b60';
        
        SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID;

		// this ensures the preimage is for the current tx
		require(Tx.checkPreimageAdvanced(txPreimage, privKey, pubKey, invK, r, rBigEndian, sigHashType));

		bytes scriptCode = Util.scriptCode(txPreimage);
		int scriptLen = length(scriptCode);

		// the last OP_RETURN byte contains the application state, i.e., counter
		int counter = unpack(scriptCode[scriptLen - 1 :]);

		// Expect the counter to be incremented in the new transaction state
		bytes scriptCode_ = scriptCode[: scriptLen - 1] + num2bin(counter + 1, 1);

        bytes counterOutput = num2bin(amount, 8) + Util.writeVarint(scriptCode_);

		// Expect the additional CHANGE output
		bytes changeScript = Util.buildPublicKeyHashScript(changePKH);
		bytes changeOutput = num2bin(changeSats, 8) + Util.writeVarint(changeScript);

		bytes hashOutputs = Util.hashOutputs(txPreimage);
		// output: amount + scriptlen + script
		Sha256 hashOutputs_ = hash256(counterOutput + changeOutput);

		// ensure output matches what we expect:
		//     - amount is same as specified
		//     - output script is the same as scriptCode except the counter was incremented
		//     - expected CHANGE output script is there
		require(hashOutputs == hashOutputs_);
    }
}

Set the sigHashType parameter to SIGHASH_ANYONECANPAY :

SigHashType sigHashType = SigHash.ANYONECANPAY | SigHash.ALL | SigHash.FORKID;

Check whether sighashPreimage is consistent with the current transaction:

require(Tx.checkPreimageAdvanced(txPreimage, privKey, pubKey, invK, r, rBigEndian, sigHashType));

Set the new count value of the contract:

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

Increase the change output:

bytes changeScript = Util.buildPublicKeyHashScript(changePKH);
bytes changeOutput = num2bin(changeSats, 8) + Util.writeVarint(changeScript);

Here is the deployment of contract and repeat calling increment()function's code. Here is an example of a contract where the number of calls increased from 0 to 4: 0 -> 1 -> 2 -> 3 -> 4 . Note that each transaction has two inputs and two outputs, unlike the old version of the counter contract, which has only one input and one output. The first input and the first output have the same number of coins, which means that the balance of the contract has not changed (marked in the figure below). The difference between the second input and the second output provides the transfer fee.

advancedCounter
Thanks to Bill of BitShizzle for implementing this advanced counter contract.

discuss

When deploying a smart contract, we make the new balance of the contract equal to the old balance of the contract. As long as someone is willing to pay a transfer fee to trigger it, the contract can continue to run.

An interesting alternative is to make the balance of the new contract greater than the balance of the old contract, which is equivalent to charging the caller's service fee to complete certain calculations. You can also think about what kind of applications can be made with this method.

Guess you like

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