Transaction Validation

Last updated 4 months ago

A description of how transactions are validated.

Basic validation (pre-validation)

Each transaction has a non-empty list of inputs and a list of outputs. Each input is either

  1. The unspent output of some other transaction

  2. A mint, that is, a source of newly created tokens.

A transaction output becomes spent when used as the input to another transaction.

Each transaction input has a spend. A spend comprises an asset and an amount. The asset may refer either to a contract, or to the native Zen token. If it refers to a contract, it further identifies which of the contract’s assets is present. (A contract is free to create any of 2^256 assets, identified by a 256-bit value – the subtype.)

Rule: Inputs equal outputs

For each type of asset, the sum of the amounts of that asset in the inputs of a transaction must equal the sum of the asset’s amounts in the transaction outputs.

For instance, suppose a transaction has three inputs: the first contains a spend of 10 Zen, the second a mint of 500 tokens from contract A, of subtype X, and the third a spend of 100 tokens from contract B, of subtype Y. Then the transaction’s outputs with the Zen type must have amounts summing to 10, those with the A:X type must sum to 500, and those with the B:Y type must sum to 100. No other assets can be present in the outputs of the transaction.

Rule: No spendable zero-value outputs

Output locks are regarded as spendable if their outputs enter the UTXO pool. All other outputs, including Destroy, Fee and Sacrifice outputs, are regarded as unspendable. Spendable outputs must have an amount strictly greater than zero.

Spendable and Unspendable output locks

An output lock is spendable if its identifier is even. Odd identifiers denote unspendable output locks.

Coinbase validation

A coinbase transaction is the first transaction in a block. It has special validation rules:

  1. All inputs must be mints

  2. All outputs must have Coinbase locks, with the height parameter set to the current block height

  3. The usual rules about validating mint inputs are not applied. In particular, contracts do not run, and native Zen tokens may be minted.

The amount of tokens of any given type that may be minted in a coinbase transaction are bounded above by the sum of the block reward, the claimable sacrifice, and the total fees paid.

Normal validation

Each transaction is validated per-input. For each input, zero or more witnesses provide information sufficient to prove that the input can be spent. The rules for validating this can refer to the whole transaction, or even to other parts of the blockchain (e.g. to UTXOs not spent in the transaction being verified, to the last block’s timestamp, etc.).

Transaction validation also keeps some state between validating each input. This is defined specifically for inputs with version-0 contract locks: each contract version will track intra-transaction state particular to that version. It is possible that a general state-passing mechanism for different lock-types to communicate will be available (via soft-fork) at some point.

In general, any input with an unknown type of lock can be spent with any number of witnesses, including zero, and any unknown type of witness can apply to any number of inputs (whether known or unknown), including zero.

A mint input implicitly has a contract lock, where the contract is given by the first part of its spend’s asset identifier. Mint inputs also have a version, defining the contract lock’s version. This is also given by the first part of the spend’s asset identifier.

Note: The ContractLock defined below applies to version-0 contracts. Although high version mint inputs implicitly have high version contract locks, the release version does not define anything about a high version contract lock – neither its serialization nor its semantics.

Three types of spendable input lock are defined, along with a catch-all case for undefined spendable input locks:

Unidentified lock

Inputs with these locks are validated. Advance the input index by one, without advancing the witness index.

High version contract locks are validated like any other unidentified lock type. The only difference of any significance is that they are the kind of lock which mint inputs implicitly possess.

PKLock

Read the next recognized witness. It must be of type PK. Further, the public key it contains must hash to the value in the PKLock. Then the signature must sign the transaction, as masked by the sighash. Advance the witness index by one.

CoinbaseLock

If maturity is 100 or more, validated according to its nested lock. Otherwise fails. Advance the witness index by one.

ContractLock (Version 0)

This is the tricky one. ContractLock’s require a version 0 contract witness to unlock. The same witness can unlock several successive inputs. Contract lock validation is stateful: a small amount of state can be written to and read from while validating a contract lock.

Contract locks validate roughly as follows:

  • If the transaction validation state indicates that the input is already unlocked, skip further validation for this input.

  • Read the contract witness.

  • Get a truncated version of the transaction, by applying the lower limits of a “transaction mask” contained in the witness.

  • Pass this truncated transaction to the contract function, a black-box that generates a new transaction.

  • Check that this return transaction is also a prefix of the transaction being validated.

  • Check that the return transaction matches the upper limits of the transaction mask.

  • Update the state of transaction validation to record the bounds of this return transaction.

  • Optionally, leave a message. This message can and must be consumed by anothercontract running to validate the next locked input of the tranasaction.

This mechanism allows the contract programmer to chain together multiple contracts, passing messages between them in sequence.

In detail, version 0 contract locks are validated as follows:

  1. If the input’s index is less than the “inputBounds” parameter of the txvalidation state, the input is inside the validated result of running a contract. No more validation is done on the input.

  2. Otherwise, check the message parameter of the txvalidation state. If the message is not None, then require that the lock’s contract hash is the same as the “recipient” parameter of the message. Then read the current witness and require that:

    • it be a contract witness, version 0, with contract hash matching the lock.

    • the transaction mask’s lower bounds (both for inputs and for outputs) match the bounds in the txvalidation state,

    • that the message matches the message in the txvalidation state.

  3. If the message is None, then check only for a contract witness, version 0, with contract hash matching the lock.

  4. Obtain an input transaction skeleton, by applying the lower transaction mask bounds to the transaction.

  5. Run the contract function on this input transaction, the message, and other parameters, obtaining a transaction skeleton as a return, along with an optional message.

  6. Check the return transaction skeleton matches the upper limits of the mask. Check that it matches the current transaction.

  7. Update the state to set “sender” to the current transaction, “inputBounds” to the length of the return transaction’s input list, “outputBounds” to the length of the return transaction’s output list, and “message” to the message returned by the contract function.

  8. Advance the witness index by one.

End-of-transaction validation (post-validation)

After each input has been validated, check the transaction’s state. The message it contains must be “None”.

Note: if a contract returns a message, and the next input does not have a contract lock, then at the latest, validation will fail here. This prevents a chain of contracts from being broken.

Rule: No all-mint input lists

Non-coinbase transactions, all of whose inputs are mints, are invalid. This restriction ensures that all transactions have unique identifiers. (Coinbase transactions, which do have all-mint input lists, are identified by their blocks, so are uniquely identified in any case).