SigHash Lotus
The Lotus blockchain includes a new Signature Hash format (in addition to supporting BIP143 and Legacy signature hashes). It is loosely based on BIP341 signature hashes from Bitcoin Core, but includes several modifications to make it more useful in smart contracts on the lotus blockchain. This format solves several security issues which were discovered with BIP143, and additionally is targetted at TapRoot-style contracts.
Serialization
- sigHashType (4 byte)
- input_hash (32 bytes): SHA256d of:
- spend type (1 byte)
- prevout (36 bytes)
- nSequence (4 bytes)
- spent_output (8 bytes + N) serialized as CTxOut
- If script spend path:
- codesep_pos (4 bytes): the opcode position of the last executed OP_CODESEPARATOR before the currently executed signature opcode, with the value in little endian (or 0xffffffff if none executed). The first opcode in a script has a position of 0. A multi-byte push opcode is counted as one opcode, regardless of the size of data being pushed. Opcodes in parsed but unexecuted branches count towards this value as well.
- exec_script_hash (32 bytes): tapleaf_hash for Taproot, otherwise SHA256d of full scriptCode
- If not ANYONECANPAY:
- input_index (4 bytes): index of this input in the transaction input vector. Index of the first input is 0.
- spent_outputs_merkle_root (32 bytes): Merkle root using SHA256d of the spent outputs in serialized CTxOut format (amount + scriptPubKey)
- amount_inputs_sum (8 bytes): The total of all input amounts added up
- If SIGHASH_ALL:
- amount_outputs_sum (8 bytes): The total of all output amounts added up
- nVersion (4 bytes)
- If not ANYONECANPAY:
- inputs_merkle_root (32 bytes): Merkle root using SHA256d of (prevout || nSequence) for all inputs
- inputs_tree_height (1 byte): Tree height of the merkle root of the inputs
- If SIGHASH_SINGLE:
- SHA256d of the corresponding output in CTxOut format (32 bytes)
- If SIGHASH_ALL:
- outputs_merkle_root (32 byets): Merkle root using SHA256d of all outputs in serialized CTxOut format
- outputs_tree_height (1 bytes): Tree height of merkle roof of the inputs (1 bytes)
- nLockTime (4 bytes)
Advantages
- For a given sighash type, the preimage is always fixed length. This makes slicing using OP_SPLIT very simple, no OP_SIZE needed
- If a part of the preimage is not needed, it is simply cut out, reducing the preimage size, making contracts more efficient.
- Outputs are SHA256'd first and then concatenated, then SHA256'd again. This means we can inspect outputs by index: We take the concatenated hashes, and then split at i*32, then
32 OP_SPLIT
again. This gives us the hash at the output i. - Same for inputs.
- We get all individual amounts for each input, addressable by index. This allows to assert another input has a specific amount.
- We get the scriptPubKeys for each input, addressable by index. This allows to assert another input is a specific smart contract, which would allow communication between smart contracts:
- For instance, a looping contract can be refilled by sending to a special P2SH, which states that it can only be spent if an input is present with the looping contract's scriptPubKey.
- It also allows something like OP_EVAL; a contract can assert a given scriptPubKey is being spent, this way it knows the constraints set in that scriptPubKey must hold for the entire transaction. These inputs could have dust amount and just exist for the sake of verifying constraints. This effectively makes the opcode limit unbounded; as long as the transaction ends up being below 100kB in size.
- It contains the input_index, so contracts can enforce conditions relative to each other, e.g. "The input before me has to have an amount of 10'000 sats"
- It contains amount_inputs_sum, which is handy for contracts with a lot of inputs. E.g., a Flipstarter can be done quite easily this way where people simply send to a P2SH address (without having to have an EC plugin) and the contract verifies amount_inputs_sum >= target amount.
- It contains amount_outputs_sum, which, coupled with amount_inputs_sum allows e.g. calculating the tx fee.
- codesep_pos is now simply an integer, which avoids quadratic hashing, see https://gist.github.com/markblundeberg/c2c88d25d5f34213830e48d459cbfb44