defmodule BitcoinLib.Script.Runner do
@moduledoc """
Script execution outcomes are based on this
https://learnmeabitcoin.com/technical/script#what-makes-a-script-valid
"""
alias BitcoinLib.Script.{Opcode}
@doc """
Executes a script and returns the resulting stack upon successful execution, or an error message
## Examples
iex> opcodes = [%BitcoinLib.Script.Opcodes.Stack.Dup{},
...> %BitcoinLib.Script.Opcodes.Crypto.Hash160{},
...> %BitcoinLib.Script.Opcodes.Data{value: <<0x842c9a57d0580a45cb91912b63ced8866c8f7b28::160>>},
...> %BitcoinLib.Script.Opcodes.BitwiseLogic.EqualVerify{},
...> %BitcoinLib.Script.Opcodes.Crypto.CheckSig{script: <<0x76a914842c9a57d0580a45cb91912b63ced8866c8f7b2888ac::200>>}
...> ]
...> stack = [
...> <<0x0253df833b37c1690234118d29fcff7c9f2f7ecc044d5b0ca6f1de76952f3f6463::264>>,
...> <<0x3044022053646a4c2ea586024baa2c640246694e168da673ba96526aa82fc40e9508e993022056872566b2c39fd6a95613558278987acfb084fbdc8d3f2ef8d50ca58d570f05::560>>
...> ]
...> BitcoinLib.Script.Runner.execute(opcodes, stack)
{:ok, [1]}
"""
@spec execute(list(), list()) :: {:ok, list()} | {:error, binary()}
def execute(opcodes, stack) when is_list(opcodes) do
{stack, opcodes}
|> execute_next_opcode
end
@doc """
Executes a script and returns a boolean result upon successful execution, or an error message
## Examples
iex> opcodes = [%BitcoinLib.Script.Opcodes.Stack.Dup{},
...> %BitcoinLib.Script.Opcodes.Crypto.Hash160{},
...> %BitcoinLib.Script.Opcodes.Data{value: <<0x842c9a57d0580a45cb91912b63ced8866c8f7b28::160>>},
...> %BitcoinLib.Script.Opcodes.BitwiseLogic.EqualVerify{},
...> %BitcoinLib.Script.Opcodes.Crypto.CheckSig{script: <<0x76a914842c9a57d0580a45cb91912b63ced8866c8f7b2888ac::200>>}
...> ]
...> stack = [
...> <<0x0253df833b37c1690234118d29fcff7c9f2f7ecc044d5b0ca6f1de76952f3f6463::264>>,
...> <<0x3044022053646a4c2ea586024baa2c640246694e168da673ba96526aa82fc40e9508e993022056872566b2c39fd6a95613558278987acfb084fbdc8d3f2ef8d50ca58d570f05::560>>
...> ]
...> BitcoinLib.Script.Runner.validate(opcodes, stack)
{:ok, true}
"""
@spec validate(list(), list()) :: {:ok, boolean()} | {:error, binary()}
def validate(opcodes, stack) when is_list(opcodes) do
with {:ok, result} <- execute(opcodes, stack) do
validate_script_result(result)
else
{:error, message} -> {:error, message}
end
end
defp validate_script_result([0]), do: {:ok, false}
defp validate_script_result([]), do: {:ok, false}
defp validate_script_result([_, _]), do: {:ok, false}
defp validate_script_result([_]), do: {:ok, true}
defp execute_next_opcode({stack, []}), do: {:ok, stack}
defp execute_next_opcode({stack, opcodes}) do
[opcode | remaining_opcodes] = opcodes
case Opcode.execute(opcode, stack) do
{:ok, stack} -> execute_next_opcode({stack, remaining_opcodes})
end
end
end