lib/block/header.ex

defmodule BitcoinLib.Block.Header do
  @moduledoc """
  Represents a Bitcoin block header

  Based on https://en.bitcoin.it/wiki/Block_hashing_algorithm
  """
  defstruct [:version, :previous_block_hash, :merkle_root_hash, :time, :bits, :nonce]

  alias BitcoinLib.Block.Header
  alias BitcoinLib.Crypto.Bitstring

  @doc """
  Converts a hex binary into a %Header{}

  ## Examples
      Parse the genesis block's header

      iex> "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
      ...> |> Binary.from_hex()
      ...> |> BitcoinLib.Block.Header.decode()
      {
        :ok,
        %BitcoinLib.Block.Header{
          version: 1,
          previous_block_hash: <<0::256>>,
          merkle_root_hash: <<0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b::256>>,
          time: ~U[2009-01-03 18:15:05Z],
          bits: 486604799,
          nonce: 2083236893
        }
      }
  """
  @spec decode(<<_::640>>) :: {:ok, %Header{}} | {:error, binary()}
  def decode(<<
        version::little-32,
        previous_block_hash::bitstring-256,
        merkle_root_hash::bitstring-256,
        time::little-32,
        bits::little-32,
        nonce::little-32
      >>) do
    {
      :ok,
      %Header{
        version: version,
        previous_block_hash: previous_block_hash |> Bitstring.reverse(),
        merkle_root_hash: merkle_root_hash |> Bitstring.reverse(),
        time: DateTime.from_unix!(time),
        bits: bits,
        nonce: nonce
      }
    }
  end

  def decode(_), do: {:error, "invalid block"}
end