Current section
Files
Jump to
Current section
Files
lib/bitcoin/script.ex
defmodule Bitcoin.Script do
@moduledoc """
Bitcoin Script.
Bitcoin uses a scripting system for transactions. Forth-like, Script is simple, stack-based
and processed from left to right. It is purposefully not Turing-complete, with no loops.
This module contains functions to oparet on scripts. The actual implementation of the Script
engine can be found in `Bitcoin.Script.Interpreter`
Common pattern of `opts` is passed to verification and run functions. They set up the context
for script verification. Here are the opts used:
* `tx` - `Bitcoin.Protocol.Messages.Tx` in which the script is present
* `input_number` - input number of that transaction (needed for sighash)
* `sub_script` - also used for sighash, (often equal to pk_script)
* `flags` - script validation flags, see below
Flags are in the format of hash e.g. %{p2sh: true, dersig: true}. The reason for using that
instead of a simple list is ability to do function matching.
"""
# Script.Serialization handles parsing the script from a binary into a list with opcodes as symbols.
# Script.Control implements parsing OP_IF
# Script.Number handles parsing and serializing script integers (CStriptNum)
use Bitcoin.Script.Opcodes
# TODO block sigop limit (MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50), so we need to be abel to export that count
# TODO verify signature encoding https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
use Bitcoin.Script.P2SH
alias Bitcoin.Script.Serialization
alias Bitcoin.Script.Interpreter
@doc """
Parse binary script into a form consumable by the interpreter (ops list). Parsed script looks like this:
[:OP_10, :OP_10, :OP_ADD, <<20>>, :OP_EQUAL]
"""
defdelegate parse(binary), to: Serialization
@doc """
Represent parsed script (list of :OP_CODES and binary data), in it's original binary form.
"""
defdelegate to_binary(script), to: Serialization
@doc """
Returns string representation of the provided parsed script in the same form as bitcoind decodescript command
"""
defdelegate to_string(script), to: Serialization
@doc """
Parse script from the string in a format that is outputed by bitcoid.
E.g. "2 OP_IF 0 OP_ELSE 1 OP_ENDIF"
"""
defdelegate parse_string(string), to: Serialization
@doc """
Parse script from a strig form familiar from test cases.
E.g. "128 SIZE 2 EQUAL"
Binaries appear in the 0x form or literaly in single quotes.
"""
defdelegate parse_string2(string), to: Serialization
@doc """
Execute the provided script. Returns the resulting stack or {:error, reason} tuple.
"""
@spec exec(list | binary, map) :: list | {:error, term}
def exec(script, opts \\ %{})
def exec(binary, opts) when is_binary(binary), do: binary |> parse |> exec(opts)
defdelegate exec(script, opts), to: Interpreter
defdelegate exec(stack, script, opts), to: Interpreter
# The reason for this function is that we need to parse sig script and pk separately.
# Otherwise sig script could do some nasty stuff with malformed PUSHDATA
# Then we have to run it separately
# default opts are done this weird way because additional function cases come from uses above (to avoid warnings)
def verify_sig_pk(sig_bin, pk_bin), do: verify_sig_pk(sig_bin, pk_bin, %{})
def verify_sig_pk(sig_bin, pk_bin, opts) when is_binary(sig_bin) and is_binary(pk_bin), do: verify_sig_pk(sig_bin |> parse, pk_bin |> parse, opts)
def verify_sig_pk(sig_script, pk_script, opts) do
try do
sig_script
|> exec(opts)
|> exec(pk_script, opts)
|> cast_to_bool
catch _,_ ->
false
end
end
# Returns true if top item of the stack is non-zero
def verify(script, opts \\ %{}) do
# TODO we should get rid of exceptions, make parser return {:error and non matched script cases should just be :invalid
try do
script |> exec(opts) |> cast_to_bool
catch _, _ ->
false
end
end
# Cast stack to boolean
def cast_to_bool({:error, _}), do: false
def cast_to_bool([]), do: false
def cast_to_bool([x | _]), do: Interpreter.bool(x)
end