cardano-transactions-1.0.0: Library utilities for constructing and signing Cardano transactions.

Safe HaskellSafe
LanguageHaskell2010

Data.UTxO.Transaction

Contents

Synopsis

Overview

This module offers an abstraction over the construction of simple UTxO transactions. Such a transaction is called a Payment. Payments are constructed by selecting inputs and outputs, and signing each input independently.

This can be represented as a state machine, such as:

                         empty
                           |
     *-----------------*   |   *----------------*
     |                 |   |   |                |
     |                 v   v   v                |
     *--- addOutput ---=========--- addInput ---*
                           |
                           |
                         lock  *----------------*
                           |   |                |
                           |   v                |
                       =========--- signWith ---*
                           |
                           |
                       serialize
                           |
                           |
                           v
                          ...

Functions from the MkPayment interfaces are designed to be easy to chain like pipes. To give a concrete example, let's see how to construct a transaction with one input and two outputs:

import Data.Function
    ( (&) )
import qualified Data.UTxO.Transaction as Tx

bytes = Tx.empty
      & Tx.addInput  input0
      & Tx.addOutput output0
      & Tx.addOutput output1
      & Tx.lock
      & Tx.signWith  key0
      & Tx.serialize

The reason why the lock step is required is because, in order to create witnesses, one must produce signatures of the whole transaction body (inputs + outputs). Hence, signatures can only be added once all inputs and all outputs have already been addded.

This is correct-by-construction thanks to the lock step and the distinction between CoinSel and Tx.

Abstraction

class MkPayment dom where Source #

Abstract interface for constructing a Payment.

Associated Types

type Init dom :: * Source #

Some initial extra data needed to construct and or serialize the transaction. This is a free-type which is implementation-specific.

For example, Byron transactions requires the underlying protocol magic in order to produce witnesses. This is an appropriate place for.

type Input dom = inp | inp -> dom Source #

Type representation of transaction Input

type Output dom = out | out -> dom Source #

Type representation of transaction Output

type SignKey dom = key | key -> dom Source #

Type representation of transaction SignKey

type CoinSel dom = sel | sel -> dom Source #

An intermediate type for representing a transaction under construction. Input and Output can still be added to a CoinSel and must be added before trying to sign any of the inputs.

type Tx dom = tx | tx -> dom Source #

A transaction is a locked CoinSel to which it is no longer possible to add new Input and or Output. Only Tx can be serialized.

Methods

empty :: Init dom -> CoinSel dom Source #

addInput :: Input dom -> CoinSel dom -> CoinSel dom Source #

addOutput :: Output dom -> CoinSel dom -> CoinSel dom Source #

lock :: CoinSel dom -> Tx dom Source #

signWith :: SignKey dom -> Tx dom -> Tx dom Source #

serialize :: Tx dom -> Either ErrMkPayment ByteString Source #

data ErrMkPayment Source #

Possible errors occuring when constructing a Payment. This can in practice be safely ignored if the transaction is known to be well-formed.

Constructors

MissingInput

Payments must have at least one input.

MissingOutput

Payments must have at least one output

MissingSignature

Payments must have a signature for each input.

Note about type-level constraints

It is relatively easy to tweak the interface below to get it fully type-safe by using an inductive type, such as:

data N = Zero | Some N

class MkPayment dom where
    type Init    dom :: *

    type Input   dom = inp | inp -> dom
    type Output  dom = out | out -> dom
    type SignKey dom = key | key -> dom

    type CoinSel dom = (sel :: N -> N -> *) | sel -> dom
    type Tx      dom = (tx  :: N -> *)      | tx  -> dom

    empty     :: Init dom -> CoinSel dom Zero Zero

    addInput  :: Input  dom -> CoinSel dom n m -> CoinSel dom (Some n) m
    addOutput :: Output dom -> CoinSel dom n m -> CoinSel dom n (Some m)

    lock      :: CoinSel dom (Some n) (Some m) -> Tx dom (Some n)

    signWith :: SignKey dom -> Tx dom (Some n) -> Tx dom n

    serialize :: Tx dom Zero -> ByteString

Such approach is great for ensuring that the transaction can't be ill-formed and works well when manually constructing the transaction as shown above.

It gets however quite complex when one wants to start folding or mapping on a list of inputs or outputs where their numbers isn't known at compile-time! Indeed, each call to addInput does actually return a new type!

Let's say we want to write the following function:

addMany :: [Input dom] -> CoinSel dom n m -> CoinSel dom ? m

What should be the type of ?. This can't possibly known at compile-time.