├── .formatter.exs ├── .gitignore ├── README.md ├── config └── config.exs ├── lib ├── base58.ex ├── base58check.ex ├── hello_bitcoin.ex ├── private_key.ex └── vanity_address.ex ├── mix.exs ├── mix.lock └── test ├── base58check_test.exs └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 3 | ] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HelloBitcoin 2 | 3 | **TODO: Add description** 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `hello_bitcoin` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [ 13 | {:hello_bitcoin, "~> 0.1.0"} 14 | ] 15 | end 16 | ``` 17 | 18 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 19 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 20 | be found at [https://hexdocs.pm/hello_bitcoin](https://hexdocs.pm/hello_bitcoin). 21 | 22 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | config :hello_bitcoin, bitcoin_url: "http://:@localhost:8332" 6 | 7 | # This configuration is loaded before any dependency and is restricted 8 | # to this project. If another project depends on this project, this 9 | # file won't be loaded nor affect the parent project. For this reason, 10 | # if you want to provide default values for your application for 11 | # 3rd-party users, it should be done in your "mix.exs" file. 12 | 13 | # You can configure your application as: 14 | # 15 | # config :hello_bitcoin, key: :value 16 | # 17 | # and access this configuration in your application as: 18 | # 19 | # Application.get_env(:hello_bitcoin, :key) 20 | # 21 | # You can also configure a 3rd-party app: 22 | # 23 | # config :logger, level: :info 24 | # 25 | 26 | # It is also possible to import configuration files, relative to this 27 | # directory. For example, you can emulate configuration per environment 28 | # by uncommenting the line below and defining dev.exs, test.exs and such. 29 | # Configuration from the imported file will override the ones defined 30 | # here (which is why it is important to import them last). 31 | # 32 | # import_config "#{Mix.env}.exs" 33 | -------------------------------------------------------------------------------- /lib/base58.ex: -------------------------------------------------------------------------------- 1 | defmodule Base58 do 2 | @alphabet '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' 3 | 4 | def encode(data, hash \\ "") 5 | 6 | def encode(data, hash) when is_binary(data) do 7 | encode_zeros(data) <> encode(:binary.decode_unsigned(data), hash) 8 | end 9 | 10 | def encode(0, hash), do: hash 11 | 12 | def encode(data, hash) do 13 | character = <> 14 | encode(div(data, 58), character <> hash) 15 | end 16 | 17 | defp encode_zeros(data) do 18 | <> 19 | |> String.duplicate(leading_zeros(data)) 20 | end 21 | 22 | defp leading_zeros(data) do 23 | :binary.bin_to_list(data) 24 | |> Enum.find_index(&(&1 != 0)) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/base58check.ex: -------------------------------------------------------------------------------- 1 | defmodule Base58Check do 2 | def encode(data, version) do 3 | (version <> data <> checksum(data, version)) 4 | |> Base58.encode() 5 | end 6 | 7 | defp checksum(data, version) do 8 | (version <> data) 9 | |> sha256 10 | |> sha256 11 | |> split 12 | end 13 | 14 | defp split(<>), do: hash 15 | 16 | defp sha256(data), do: :crypto.hash(:sha256, data) 17 | end 18 | -------------------------------------------------------------------------------- /lib/hello_bitcoin.ex: -------------------------------------------------------------------------------- 1 | defmodule HelloBitcoin do 2 | def bitcoin_rpc(method, params \\ []) do 3 | with url <- Application.get_env(:hello_bitcoin, :bitcoin_url), 4 | command <- %{jsonrpc: "1.0", method: method, params: params}, 5 | {:ok, body} <- Poison.encode(command), 6 | {:ok, response} <- HTTPoison.post(url, body), 7 | {:ok, metadata} <- Poison.decode(response.body), 8 | %{"error" => nil, "result" => result} <- metadata do 9 | {:ok, result} 10 | else 11 | %{"error" => reason} -> {:error, reason} 12 | error -> error 13 | end 14 | end 15 | 16 | def getinfo, do: bitcoin_rpc("getinfo") 17 | 18 | def getblockhash(index), do: bitcoin_rpc("getblockhash", [index]) 19 | end 20 | -------------------------------------------------------------------------------- /lib/private_key.ex: -------------------------------------------------------------------------------- 1 | defmodule PrivateKey do 2 | @n :binary.decode_unsigned(<< 3 | 0xFF, 4 | 0xFF, 5 | 0xFF, 6 | 0xFF, 7 | 0xFF, 8 | 0xFF, 9 | 0xFF, 10 | 0xFF, 11 | 0xFF, 12 | 0xFF, 13 | 0xFF, 14 | 0xFF, 15 | 0xFF, 16 | 0xFF, 17 | 0xFF, 18 | 0xFE, 19 | 0xBA, 20 | 0xAE, 21 | 0xDC, 22 | 0xE6, 23 | 0xAF, 24 | 0x48, 25 | 0xA0, 26 | 0x3B, 27 | 0xBF, 28 | 0xD2, 29 | 0x5E, 30 | 0x8C, 31 | 0xD0, 32 | 0x36, 33 | 0x41, 34 | 0x41 35 | >>) 36 | 37 | def generate do 38 | private_key = :crypto.strong_rand_bytes(32) 39 | 40 | case valid?(private_key) do 41 | true -> private_key 42 | false -> generate() 43 | end 44 | end 45 | 46 | def to_public_key(private_key) do 47 | :crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key) 48 | |> elem(0) 49 | end 50 | 51 | def to_compressed_public_key(private_key) do 52 | {<<0x04, x::binary-size(32), y::binary-size(32)>>, _} = 53 | :crypto.generate_key(:ecdh, :crypto.ec_curve(:secp256k1), private_key) 54 | 55 | if rem(:binary.decode_unsigned(y), 2) == 0 do 56 | <<0x02>> <> x 57 | else 58 | <<0x03>> <> x 59 | end 60 | end 61 | 62 | def to_public_hash(private_key) do 63 | private_key 64 | |> to_public_key 65 | |> hash(:sha256) 66 | |> hash(:ripemd160) 67 | end 68 | 69 | def to_public_address(private_key, version \\ <<0x00>>) do 70 | private_key 71 | |> to_public_hash 72 | |> Base58Check.encode(version) 73 | end 74 | 75 | def valid?(key) when is_binary(key) do 76 | key 77 | |> :binary.decode_unsigned() 78 | |> valid? 79 | end 80 | 81 | def valid?(key) when key > 1 and key < @n, do: true 82 | def valid?(_), do: false 83 | 84 | defp hash(data, algorithm), do: :crypto.hash(algorithm, data) 85 | end 86 | -------------------------------------------------------------------------------- /lib/vanity_address.ex: -------------------------------------------------------------------------------- 1 | defmodule VanityAddress do 2 | def stream_private_keys(regex, version \\ <<0x00>>) do 3 | [fn -> generate_private_key(regex, version) end] 4 | |> Stream.cycle() 5 | |> Task.async_stream(fn f -> f.() end, timeout: :infinity) 6 | |> Stream.map(fn 7 | {:ok, thing} -> thing 8 | _ -> nil 9 | end) 10 | |> Stream.reject(&(&1 == nil)) 11 | end 12 | 13 | def generate_private_key(regex, version \\ <<0x00>>) do 14 | private_key = PrivateKey.generate() 15 | public_address = PrivateKey.to_public_address(private_key, version) 16 | 17 | case public_address =~ regex do 18 | true -> private_key 19 | false -> generate_private_key(regex, version) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HelloBitcoin.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :hello_bitcoin, 7 | version: "0.1.0", 8 | elixir: "~> 1.5", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | {:httpoison, "~> 0.13"}, 25 | {:poison, "~> 3.1"}, 26 | {:stream_data, "~> 0.1", only: :test} 27 | ] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [], [], "hexpm"}, 2 | "hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"}, 6 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"}, 7 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}, 8 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [], [], "hexpm"}, 9 | "stream_data": {:hex, :stream_data, "0.4.0", "128c01bfd0fae0108d169eee1772aeed6958604f8782abc2d6e11da4e52468b0", [], [], "hexpm"}, 10 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"}} 11 | -------------------------------------------------------------------------------- /test/base58check_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Base58CheckTest do 2 | use ExUnit.Case 3 | use ExUnitProperties 4 | 5 | test "correctly encodes" do 6 | # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses 7 | assert Base58Check.encode( 8 | << 9 | 0x01, 10 | 0x09, 11 | 0x66, 12 | 0x77, 13 | 0x60, 14 | 0x06, 15 | 0x95, 16 | 0x3D, 17 | 0x55, 18 | 0x67, 19 | 0x43, 20 | 0x9E, 21 | 0x5E, 22 | 0x39, 23 | 0xF8, 24 | 0x6A, 25 | 0x0D, 26 | 0x27, 27 | 0x3B, 28 | 0xEE 29 | >>, 30 | <<0x00>> 31 | ) == "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM" 32 | end 33 | 34 | property "gives the same results as `bx base58check-encode`" do 35 | check all key <- binary(min_length: 1), 36 | version <- byte() do 37 | result = Base58Check.encode(key, <>) 38 | 39 | oracle = 40 | System.cmd("bx", [ 41 | "base58check-encode", 42 | Base.encode16(key), 43 | "--version", 44 | "#{version}" 45 | ]) 46 | |> elem(0) 47 | |> String.trim() 48 | 49 | assert result == oracle 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------