Skip to content

Transaction Input Payload Contract

Status: DRAFT (Version 1)
Authors: dagurval@pvv.ntnu.no
Date: April 2021

Overview

We describe a P2SH Script contract for publishing arbitrary data payload on the Bitcoin Cash network using transaction input (scriptSig).

The payload contract is a designed to allow maximum arbitrary data payload in a single input spending from the contract.

This method of data delivery on the network has the following properties:

  • Simplicity, all payload data is in a single transaction input.

  • The contract can be funded by using the hash of the payload data. The data itself need not be known*.

  • Using transaction input allows for larger data payload within a single transaction than the use of OP_RETURN output**.

*This allows a third party to fund the contract without knowing the payload. See anonymous contract funding for a use case.

**Based on current policy limitation of 223 bytes for OP_RETURN outputs in Bitcoin Cash.

Payload limitations

The size and shape of allowed payload is limited by protocol policy and consensus limits. The main two limits to consider are:

  • Max script element size of 520 bytes (consensus limit)
  • Max scriptSig size of 1650 bytes per input (standardness limit)

These limits allow for payload consisting of 3 stack elements in the unlocking script that shall be concatenated to make a single payload.

The contract

Redeem script

To create the reedeem script, the following two inputs are needed:

  • Hash of payload
  • Public key held by the owner of the payload

Where hash of payload is HASH160(HASH160(pubkey) || HASH160(push3) || HASH160(push2) || HASH160(push1))

// hash push3
OP_HASH160

// hash push2
OP_SWAP
OP_HASH160

// concatenate hashes (push3 + push2)
OP_CAT

// hash push1
OP_SWAP
OP_HASH160

// concatenate hashes (push3 + push2 + push1)
OP_CAT

// introduce the pubkey that will be required to spend the payload (33 bytes).
<pubkey>
// take extra copy of the pubkey for use with checksig later
OP_DUP

// hash pubkey and concatinate hashes (pkh + push3 + push2 + push1)
OP_HASH160
// after ROT, the stack is (signature pubkey pushhashes pubkeyhash)
OP_ROT
OP_CAT

// finally, the hash of hashes
OP_HASH160

// compare it to he expected hash
<hash of payload>
OP_EQUALVERIFY

// check signature
OP_CHECKSIGVERIFY

// verify that there are no additional inputs
OP_DEPTH
OP_NOT)

Unlocking script

<signature> <push1> <push2> <push3> <redeem script>

The maximum unlocking script size is 1650. This allows for a payload of 1503 bytes.

<PUSH> + <signature> <PUSHDATA2> <push1> <PUSHDATA2> <push2> <PUSHDATA2> <push3> <PUSH> <redeem script>
  1    +     65     +     3     +  520        3     +  520        3     +  463  +   1  +       71

Design decisions

Signature and public key take up 33 + 65 + 2 bytes on the input, if we don't restrict how the input is spent, this data could be used for payload instead. This would allow anyone observing the network to double spend the coins in the contract, but the double spender would still need to "deliver the payload"

This would save 65 (signature) + 33 (public key) + 2 (push opcodes) bytes in the input, as well as one byte for the OP_CHECKSIGVERIFY.

Malleability

The contract enfroces a check of the hash of the payload and the public key. This protects it from first-party malleability.

The contract uses OP_CHECKSIGVERIFY to protect from third-party malleability.

Using multiple contracts to deliver larger payloads

A single input is able to deliver 1503 bytes. If a larger payload is required, then it is possible to split it up into multiple Input Payload Contracts. All the contracts can be used as inputs in a single transaction, then concatenated the chunks of payloads from each input together for the larger payload.

Recommended method for doing this is to:

  1. The order of inputs in the transaction is the order they shall be concatenated together as.

  2. The first input to the transaction must be a Input Payload Contract.

  3. When iterating over inputs, stop at the first input that is not a Input Payload Contract.

payload = [];
for (i = 0; i < tx.inputs.size(); ++i) {
    if (!is_input_payload_contract(tx.inputs[i])) {
        break;
    }
    payload += read_payload(tx.inputs[i]);
}

Limitations with multi-input payloads

When combining multiple contracts for a larger payload, we'll eventually run into the standardness limit MAX_STANDARD_TX_SIZE that limits the transaction size to 100000 bytes.

Assuming a transaction with one P2PKH output, we are able to create a transaction that is 99994 bytes with a payload of 88677 bytes:

Transaction fields Size in bytes
Version 4
Input count 2
59 contract inputs 99946
Output count 2
1 P2PKH output 36
Locktime 4

Prior work

This is a similar to the P2SH payload script for bitcoin files.

Notable differences

  • In our contract, the whole payload is in the unlocking script. There is no data is in the redeem script.

  • In our data, the public key is part of the redeemscript, saving space otherwise used for PKH.

Implementations

Implemented in libbitcoincashkotlin (Kotlin)