preimage

The preimage under the script opcode OP_CODESEPARATOR

The signature checked by OP_CHECKSIG is the signature of the hash of the preimage. The preimage contains outputs of the entire transaction, input and the locking script. Usually the locking script is complete. But if OP_CHECKSIG has executed OP_CODESEPARATOR before, then the preimage only contains the locking script from the position of the most recently executed OP_CODESEPARATOR until the end of the script, that is, only part of the locking script is included. Using this feature can be used to reduce the size of the preimage, thereby reducing the size of the entire transaction.

The sCrypt standard library Tx provides the following functions to check this preimage that only contains partial locking scripts:

Function
Description

Tx.checkPreimageOCS

The preimage to be checked only contains part of the locking script. The signature type checked by default is SigHash.ALL | SigHash.FORKID, and does not support custom signature type.

Tx.checkPreimageSigHashTypeOCS

Support custom signature type

Tx.checkPreimageAdvancedOCS

Support custom signature type and custom temporary key

Tx.checkPreimageOptOCS

Optimized version of Tx.checkPreimageOCS

Tx.checkPreimageOptOCS_

Optimized version of Tx.checkPreimageOCS, support custom signature type

The following is an example of using Tx.checkPreimageOCS() to check the preimage:

contract CheckLockTimeVerifyOCS {
    int time;

    public function unlock(SigHashPreimage preimage) {
        require(Tx.checkPreimageOCS(preimage));
        require(SigHash.nLocktime(preimage) > this.time);
    }
}

The transaction size for executing this contract is 288 bytes, which contains a preimage with 201 size, and the transaction size for executing the CheckLockTimeVerify contract using the Tx.checkPreimage() version is 1005 bytes, which contains a preimage with 915 size. The functions of these two contracts are exactly the same, but the transaction size is optimized by 80%.

The following is an example of how to deploy and call the CheckLockTimeVerifyOCS contract:

const amount = 2000

// get locking script
const CLTVOCS = buildContractClass(loadDesc('cltvOCS_debug_desc.json'));
cltv = new CLTVOCS(1422674);

// lock fund to the script
const lockingTx = await deployContract(cltv, amount)
console.log('funding txid:      ', lockingTx.id);

// unlock
const unlockingTx = new bsv.Transaction();

unlockingTx.addInput(createInputFromPrevTx(lockingTx))
    .setLockTime(1422674 + 1)
    .change(privateKey.toAddress())
    .setInputScript(0, (tx, output) => {
        const preimage = getPreimage(tx, output.script.subScript(0), output.satoshis)
        return cltv.spend(new SigHashPreimage(toHex(preimage))).toScript()
    })
    .seal()


const unlockingTxid = await sendTx(unlockingTx)
console.log('unlocking txid:   ', unlockingTxid)

console.log('Succeeded on testnet')

When using getPreimage to calculate the preimage, the entire lock script cannot be passed. Need to use subScript(opsIndex: number) to tailor the locking script. The parameter index is the index of OP_CODESEPARATOR in this script, which is the number of OP_CODESEPARATOR. It should be noted that this function does not consider dynamic execution.

Last updated