BSV.Contract behaviour (BSV v2.1.0) View Source

A behaviour module for implementing Bitcoin transaction contracts.

A Bitcoin transaction contains two sides: inputs and outputs.

Transaction outputs are script puzzles, called "locking scripts" (sometimes also known as a "ScriptPubKey") which lock a number of satoshis. Transaction inputs are contain an "unlocking script" (or the "ScriptSig") and unlock the satoshis contained in the previous transaction's outputs.

Therefore, each locking script is unlocked by a corresponding unlocking script.

The BSV.Contract module provides a way to define a locking script and unlocking script in a plain Elixir function. Because it is just Elixir, it is trivial to add helper functions and macros to reduce boilerplate and create more complex contract types and scripts.

Defining a contract

The following module implements a Pay to Public Key Hash contract. Implementing a contract is just a case of defining locking_script/2 and unlocking_script/2.

defmodule P2PKH do
  @moduledoc "Pay to Public Key Hash contract."
  use BSV.Contract

  @impl true
  def locking_script(ctx, %{address: address}) do
    ctx
    |> op_dup
    |> op_hash160
    |> push(address.pubkey_hash)
    |> op_equalverify
    |> op_checksig
  end

  @impl true
  def unlocking_script(ctx, %{keypair: keypair}) do
    ctx
    |> signature(keypair.privkey)
    |> push(BSV.PubKey.to_binary(keypair.pubkey))
  end
end

Locking a contract

A contract locking script is initiated by calling lock/2 on the contract module, passing the number of satoshis and a map of parameters expected by locking_script/2 defined in the contract.

# Initiate the contract locking script
contract = P2PKH.lock(10_000, %{address: Address.from_pubkey(bob_pubkey)})

script = Contract.to_script(contract) # returns the locking script
txout = Contract.to_txout(contract)   # returns the full txout

Unlocking a contract

To unlock and spend the contract, a BSV.UTXO.t/0 is passed to unlock/2 with the parameters expected by unlocking_script/2 defined in the contract.

# Initiate the contract unlocking script
contract = P2PKH.unlock(utxo, %{keypair: keypair})

Optionally the current transaction context can be given to the contract. This allows the correct sighash to be calculated for any signatures.

# Pass the current transaction ctx
contract = Contract.put_ctx(contract, {tx, vin})

# returns the signed txin
txin = Contract.to_txin(contract)

Building transactions

The BSV.Contract behaviour is taken advantage of in the BSV.TxBuilder module, resulting in transaction building semantics that are easy to grasp and pleasing to work with.

builder = %TxBuilder{
  inputs: [
    P2PKH.unlock(utxo, %{keypair: keypair})
  ],
  outputs: [
    P2PKH.lock(10_000, %{address: address})
  ]
}

# Returns a fully signed transaction
TxBuilder.to_tx(builder)

For more information, refer to BSV.TxBuilder.

Link to this section Summary

Types

Transaction context.

t()

BSV Contract struct

Callbacks

Callback executed to generate the contract locking script.

Callback executed to generate the contract unlocking script.

Functions

Puts the given transaction context (tx and vin) onto the contract.

Appends the given value onto the end of the contract script.

Returns the size (in bytes) of the contract script.

Simulates the contract with the given locking and unlocking parameters.

Compiles the contract and returns the script.

Compiles the unlocking contract and returns the BSV.TxIn.t/0.

Compiles the locking contract and returns the BSV.TxIn.t/0.

Link to this section Types

Specs

ctx() :: {BSV.Tx.t(), non_neg_integer()}

Transaction context.

A tuple containing a BSV.Tx.t/0 and vin. When attached to a contract, the he correct sighash to be calculated for any signatures.

Specs

t() :: %BSV.Contract{
  ctx: ctx() | nil,
  mfa: {module(), atom(), list()},
  opts: keyword(),
  script: BSV.Script.t(),
  subject: non_neg_integer() | BSV.UTXO.t()
}

BSV Contract struct

Link to this section Callbacks

Specs

locking_script(t(), map()) :: t()

Callback executed to generate the contract locking script.

Is passed the contract and a map of parameters. It must return the updated contract.

Link to this callback

unlocking_script(t, map)

View Source (optional)

Specs

unlocking_script(t(), map()) :: t()

Callback executed to generate the contract unlocking script.

Is passed the contract and a map of parameters. It must return the updated contract.

Link to this section Functions

Specs

put_ctx(t(), ctx()) :: t()

Puts the given transaction context (tx and vin) onto the contract.

When the transaction context is attached, the contract can generate valid signatures. If it is not attached, all signatures will be 71 bytes of zeros.

Link to this function

script_push(contract, val)

View Source

Specs

script_push(t(), atom() | integer() | binary()) :: t()

Appends the given value onto the end of the contract script.

Specs

script_size(t()) :: non_neg_integer()

Returns the size (in bytes) of the contract script.

Link to this function

simulate(mod, lock_params, unlock_params)

View Source

Specs

simulate(module(), map(), map()) :: {:ok, BSV.VM.t()} | {:error, BSV.VM.t()}

Simulates the contract with the given locking and unlocking parameters.

Internally this works by creating a fake transaction containing the locking script, and then attempts to spend that UTXO in a second fake transaction. The entire script is concatenated and passed to VM.eval/2.

Example

iex> alias BSV.Contract.P2PKH
iex> keypair = BSV.KeyPair.new()
iex> lock_params = %{address: BSV.Address.from_pubkey(keypair.pubkey)}
iex> unlock_params = %{keypair: keypair}
iex>
iex> {:ok, vm} = Contract.simulate(P2PKH, lock_params, unlock_params)
iex> BSV.VM.valid?(vm)
true

Specs

to_script(t()) :: BSV.Script.t()

Compiles the contract and returns the script.

Specs

to_txin(t()) :: BSV.TxIn.t()

Compiles the unlocking contract and returns the BSV.TxIn.t/0.

Specs

to_txout(t()) :: BSV.TxOut.t()

Compiles the locking contract and returns the BSV.TxIn.t/0.