├── .circleci └── config.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── config └── config.exs ├── lib ├── aes │ └── aes.ex ├── cipher.ex ├── ecies │ ├── ecdh.ex │ ├── ecies.ex │ └── parameters.ex ├── exth_crypto.ex ├── hash │ ├── fake.ex │ ├── hash.ex │ ├── keccak.ex │ └── sha.ex ├── kdf │ └── nist_sp_800_56.ex ├── key │ └── key.ex ├── mac │ └── mac.ex ├── math │ └── math.ex ├── signature │ └── signature.ex └── test.ex ├── mix.exs ├── mix.lock └── test ├── aes └── aes_test.exs ├── cipher_test.exs ├── ecies ├── ecdh_test.exs ├── ecies_test.exs └── parameters_test.exs ├── exth_crypto_test.exs ├── hash ├── fake_test.exs ├── keccak_test.exs └── sha_test.exs ├── hash_test.exs ├── kdf └── nist_sp_800_65_test.exs ├── mac └── mac_test.exs ├── math └── math_test.exs ├── signature └── signature_test.exs └── test_helper.exs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | jobs: 2 | build: 3 | working_directory: ~/exth_crypto 4 | docker: 5 | - image: elixir:latest 6 | steps: 7 | - run: apt-get update; apt-get -y install libgmp3-dev 8 | - checkout 9 | - run: git submodule sync --recursive 10 | - run: git submodule update --recursive --init 11 | - restore_cache: 12 | key: _build 13 | - run: mix local.hex --force 14 | - run: mix local.rebar --force 15 | - run: mix deps.get 16 | - run: mix test 17 | - run: mix dialyzer 18 | - save_cache: 19 | key: _build 20 | paths: 21 | - _build 22 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | * 0.1.5 2 | * Upgraded libsecp256k1 dependency 3 | * 0.1.4 4 | * Add MAC stream. 5 | * Improve signature recovery algorithms 6 | * Add ECB mode for AES 7 | # 0.1.3 8 | * Adds additional test keys pairs and `key_pair` as a type. 9 | # 0.1.2 10 | * Refactor key functions to new module 11 | # 0.1.1 12 | * Minor updates 13 | # 0.1.0 14 | * First pass, includes ECIES implementation 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2017 Geoffrey Hayes, Mason Fischer 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ExthCrypto [![CircleCI](https://circleci.com/gh/exthereum/exth_crypto.svg?style=svg)](https://circleci.com/gh/exthereum/exth_crypto) 2 | 3 | ExthCrypto handles the majority of cryptographic operations for Exthereum. The goal of this project is to give each Exthereum project a common set of cryptographic functions where the backends can be swapped out as need be. Additionally, more complicated protocols (such as ECIES) can be implemented and tested in this project. 4 | 5 | Note: we opt, whenever possible, to use erlang core or open-source implementations for all functions. The goal of this project is to create a consistent API for cryptographic functions that can be referenced from Exthereum projects. The goal of this project is not to re-write such functions in native erlang or Elixir. 6 | 7 | We currently support: 8 | 9 | * AES symmetric encryption in block mode and (simplified) stream mode 10 | * Elliptic Curve Diffie Hellman (ECDH) key exchange 11 | * ECIES perfect-forward secret generation 12 | * SHA1, SHA2 and Keccak one-way cryptographic hash functions 13 | * NIST-SP-800-56 key derivation function 14 | * HMAC with SHA1, SHA2 or Keccak 15 | * Elliptic Curve Digital Signature Algorithm (ECDSA) with public key recovery 16 | 17 | ## Installation 18 | 19 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 20 | by adding `exth_crypto` to your list of dependencies in `mix.exs`: 21 | 22 | ```elixir 23 | def deps do 24 | [{:exth_crypto, "~> 0.1.6"}] 25 | end 26 | ``` 27 | 28 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 29 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 30 | be found at [https://hexdocs.pm/exth_crypto](https://hexdocs.pm/exth_crypto). 31 | 32 | -------------------------------------------------------------------------------- /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 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :exth_crypto, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:exth_crypto, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/aes/aes.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.AES do 2 | @moduledoc """ 3 | Defines standard functions for use with AES symmetric cryptography in block mode. 4 | """ 5 | 6 | @block_size 32 7 | 8 | @doc """ 9 | Returns the blocksize for AES encryption when used as block mode encryption. 10 | 11 | ## Examples 12 | 13 | iex> ExthCrypto.AES.block_size 14 | 32 15 | """ 16 | @spec block_size :: integer() 17 | def block_size, do: @block_size 18 | 19 | @doc """ 20 | Encrypts a given binary with the given public key. For block mode, this is the 21 | standard encrypt operation. For streaming mode, this will return the final block 22 | of the stream. 23 | 24 | Note: for streaming modes, `init_vector` is the same as ICB. 25 | 26 | ## Examples 27 | 28 | iex> ExthCrypto.AES.encrypt("obi wan", :cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 29 | <<86, 16, 7, 47, 97, 219, 8, 46, 16, 170, 70, 100, 131, 140, 241, 28>> 30 | 31 | iex> ExthCrypto.AES.encrypt("obi wan", :cbc, ExthCrypto.Test.symmetric_key(:key_b), ExthCrypto.Test.init_vector) 32 | <<219, 181, 173, 235, 88, 139, 229, 61, 172, 142, 36, 195, 83, 203, 237, 39>> 33 | 34 | iex> ExthCrypto.AES.encrypt("obi wan", :cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector(2)) 35 | <<134, 126, 59, 64, 83, 197, 85, 40, 155, 178, 52, 165, 27, 190, 60, 170>> 36 | 37 | iex> ExthCrypto.AES.encrypt("jedi knight", :cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 38 | <<54, 252, 188, 111, 221, 182, 65, 54, 77, 143, 127, 188, 176, 178, 50, 160>> 39 | 40 | iex> ExthCrypto.AES.encrypt("Did you ever hear the story of Darth Plagueis The Wise? I thought not.", :cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) |> ExthCrypto.Math.bin_to_hex 41 | "3ee326e03303a303df6eac828b0bdc8ed67254b44a6a79cd0082bc245977b0e7d4283d63a346744d2f1ecaafca8be906d9f3d27db914d80b601d7e0c598418380e5fe2b48c0e0b8454c6d251f577f28f" 42 | 43 | iex> ExthCrypto.AES.encrypt("obi wan", :ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 44 | <<32, 99, 57, 7, 64, 82, 28>> 45 | 46 | iex> ExthCrypto.AES.encrypt("obi wan", :ctr, ExthCrypto.Test.symmetric_key(:key_b), ExthCrypto.Test.init_vector) 47 | <<156, 176, 33, 64, 69, 16, 173>> 48 | 49 | iex> ExthCrypto.AES.encrypt("obi wan", :ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector(2)) 50 | <<214, 99, 7, 241, 219, 189, 178>> 51 | 52 | iex> ExthCrypto.AES.encrypt("jedi knight", :ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 53 | <<37, 100, 52, 78, 23, 88, 28, 22, 254, 47, 32>> 54 | 55 | iex> ExthCrypto.AES.encrypt("Did you ever hear the story of Darth Plagueis The Wise? I thought not.", :ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) |> ExthCrypto.Math.bin_to_hex 56 | "0b6834074e5c075ffc31318cc03cba1fe35648a6f149a74952661473b73570fb98332e31870c111d3ae5ccff2154bd4083a7ee4bfd19bc85eba77835aac4cea881ada2630cdd" 57 | 58 | iex> ExthCrypto.AES.encrypt("jedi knight", :ecb, ExthCrypto.Test.symmetric_key) 59 | <<98, 60, 215, 107, 189, 132, 176, 63, 62, 225, 92, 13, 70, 53, 187, 240>> 60 | """ 61 | @spec encrypt(ExthCrypto.Cipher.plaintext, ExthCrypto.Cipher.mode, ExthCrypto.symmetric_key, ExthCrypto.Cipher.init_vector) :: ExthCrypto.Cipher.ciphertext 62 | def encrypt(plaintext, :cbc, symmetric_key, init_vector) do 63 | padding_bits = ( 16 - rem(byte_size(plaintext), 16) ) * 8 64 | 65 | :crypto.block_encrypt(:aes_cbc, symmetric_key, init_vector, <<0::size(padding_bits)>> <> plaintext) 66 | end 67 | 68 | def encrypt(plaintext, :ctr, symmetric_key, init_vector) do 69 | {_state, ciphertext} = 70 | :crypto.stream_init(:aes_ctr, symmetric_key, init_vector) 71 | |> :crypto.stream_encrypt(plaintext) 72 | 73 | ciphertext 74 | end 75 | 76 | @spec encrypt(ExthCrypto.Cipher.plaintext, ExthCrypto.Cipher.mode, ExthCrypto.symmetric_key) :: ExthCrypto.Cipher.ciphertext 77 | def encrypt(plaintext, :ecb, symmetric_key) do 78 | padding_bits = ( 16 - rem(byte_size(plaintext), 16) ) * 8 79 | 80 | :crypto.block_encrypt(:aes_ecb, symmetric_key, <<0::size(padding_bits)>> <> plaintext) 81 | end 82 | 83 | @doc """ 84 | Decrypts the given binary with the given private key. 85 | 86 | ## Examples 87 | 88 | iex> <<86, 16, 7, 47, 97, 219, 8, 46, 16, 170, 70, 100, 131, 140, 241, 28>> 89 | ...> |> ExthCrypto.AES.decrypt(:cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 90 | <<0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "obi wan" 91 | 92 | iex> <<219, 181, 173, 235, 88, 139, 229, 61, 172, 142, 36, 195, 83, 203, 237, 39>> 93 | ...> |> ExthCrypto.AES.decrypt(:cbc, ExthCrypto.Test.symmetric_key(:key_b), ExthCrypto.Test.init_vector) 94 | <<0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "obi wan" 95 | 96 | iex> <<134, 126, 59, 64, 83, 197, 85, 40, 155, 178, 52, 165, 27, 190, 60, 170>> 97 | ...> |> ExthCrypto.AES.decrypt(:cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector(2)) 98 | <<0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "obi wan" 99 | 100 | iex> <<54, 252, 188, 111, 221, 182, 65, 54, 77, 143, 127, 188, 176, 178, 50, 160>> 101 | ...> |> ExthCrypto.AES.decrypt(:cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 102 | <<0, 0, 0, 0, 0>> <> "jedi knight" 103 | 104 | iex> "3ee326e03303a303df6eac828b0bdc8ed67254b44a6a79cd0082bc245977b0e7d4283d63a346744d2f1ecaafca8be906d9f3d27db914d80b601d7e0c598418380e5fe2b48c0e0b8454c6d251f577f28f" 105 | ...> |> ExthCrypto.Math.hex_to_bin 106 | ...> |> ExthCrypto.AES.decrypt(:cbc, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 107 | <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "Did you ever hear the story of Darth Plagueis The Wise? I thought not." 108 | 109 | iex> <<32, 99, 57, 7, 64, 82, 28>> 110 | ...> |> ExthCrypto.AES.decrypt(:ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 111 | "obi wan" 112 | 113 | iex> <<156, 176, 33, 64, 69, 16, 173>> 114 | ...> |> ExthCrypto.AES.decrypt(:ctr, ExthCrypto.Test.symmetric_key(:key_b), ExthCrypto.Test.init_vector) 115 | "obi wan" 116 | 117 | iex> <<214, 99, 7, 241, 219, 189, 178>> 118 | ...> |> ExthCrypto.AES.decrypt(:ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector(2)) 119 | "obi wan" 120 | 121 | iex> <<37, 100, 52, 78, 23, 88, 28, 22, 254, 47, 32>> 122 | ...> |> ExthCrypto.AES.decrypt(:ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 123 | "jedi knight" 124 | 125 | iex> "0b6834074e5c075ffc31318cc03cba1fe35648a6f149a74952661473b73570fb98332e31870c111d3ae5ccff2154bd4083a7ee4bfd19bc85eba77835aac4cea881ada2630cdd" 126 | ...> |> ExthCrypto.Math.hex_to_bin 127 | ...> |> ExthCrypto.AES.decrypt(:ctr, ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector) 128 | "Did you ever hear the story of Darth Plagueis The Wise? I thought not." 129 | 130 | iex> ExthCrypto.AES.decrypt(<<98, 60, 215, 107, 189, 132, 176, 63, 62, 225, 92, 13, 70, 53, 187, 240>>, :ecb, ExthCrypto.Test.symmetric_key) 131 | <<0, 0, 0, 0, 0>> <> "jedi knight" 132 | """ 133 | @spec decrypt(ExthCrypto.Cipher.ciphertext, ExthCrypto.Cipher.mode, ExthCrypto.symmetric_key, ExthCrypto.Cipher.init_vector) :: ExthCrypto.Cipher.plaintext 134 | def decrypt(ciphertext, :cbc, symmetric_key, init_vector) do 135 | :crypto.block_decrypt(:aes_cbc, symmetric_key, init_vector, ciphertext) 136 | end 137 | 138 | def decrypt(ciphertext, :ctr, symmetric_key, init_vector) do 139 | {_state, plaintext} = 140 | :crypto.stream_init(:aes_ctr, symmetric_key, init_vector) 141 | |> :crypto.stream_decrypt(ciphertext) 142 | 143 | plaintext 144 | end 145 | 146 | @spec decrypt(ExthCrypto.Cipher.ciphertext, ExthCrypto.Cipher.mode, ExthCrypto.symmetric_key) :: ExthCrypto.Cipher.plaintext 147 | def decrypt(ciphertext, :ecb, symmetric_key) do 148 | :crypto.block_decrypt(:aes_ecb, symmetric_key, ciphertext) 149 | end 150 | 151 | @doc """ 152 | Initializes an AES stream in the given mode with a given 153 | key and init vector. 154 | 155 | ## Examples 156 | 157 | iex> stream = ExthCrypto.AES.stream_init(:ctr, ExthCrypto.Test.symmetric_key(), ExthCrypto.Test.init_vector) 158 | iex> is_nil(stream) 159 | false 160 | """ 161 | @spec stream_init(ExthCrypto.Cipher.mode, ExthCrypto.Key.symmetric_key, ExthCrypto.Cipher.init_vector) :: ExthCrypto.Cipher.stream 162 | def stream_init(:ctr, symmetric_key, init_vector) do 163 | # IO.inspect(["Have symm key: ", symmetric_key]) 164 | :crypto.stream_init(:aes_ctr, symmetric_key, init_vector) 165 | end 166 | 167 | @doc """ 168 | Encrypts data with an already initialized AES stream, returning a 169 | stream with updated state, as well as the ciphertext. 170 | 171 | ## Examples 172 | 173 | iex> stream = ExthCrypto.AES.stream_init(:ctr, ExthCrypto.Test.symmetric_key(), ExthCrypto.Test.init_vector) 174 | iex> {_stream_2, ciphertext} = ExthCrypto.AES.stream_encrypt("hello", stream) 175 | iex> ciphertext 176 | "'d stream = ExthCrypto.AES.stream_init(:ctr, ExthCrypto.Test.symmetric_key(), ExthCrypto.Test.init_vector) 190 | iex> {_stream_2, ciphertext} = ExthCrypto.AES.stream_encrypt("hello", stream) 191 | iex> {_stream_3, plaintext} = ExthCrypto.AES.stream_decrypt(ciphertext, stream) 192 | iex> plaintext 193 | "hello" 194 | """ 195 | @spec stream_decrypt(ExthCrypto.Cipher.ciphertext, ExthCrypto.Cipher.stream) :: { ExthCrypto.Cipher.stream, ExthCrypto.Cipher.plaintrxt } 196 | def stream_decrypt(plaintext, stream) do 197 | :crypto.stream_decrypt(stream, plaintext) 198 | end 199 | 200 | end -------------------------------------------------------------------------------- /lib/cipher.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Cipher do 2 | @moduledoc """ 3 | Module for symmetric encryption. 4 | """ 5 | 6 | @type mode :: :cbc | :ctr | :ecb 7 | @type cipher :: {atom(), integer(), mode} 8 | @type plaintext :: iodata() 9 | @type ciphertext :: binary() 10 | @type init_vector :: binary() 11 | @opaque stream :: :crypto.ctr_state 12 | 13 | @doc """ 14 | Encrypts the given plaintext for the given block cipher. 15 | 16 | ## Examples 17 | 18 | iex> ExthCrypto.Cipher.encrypt("execute order 66", ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector, {ExthCrypto.AES, ExthCrypto.AES.block_size, :cbc}) |> ExthCrypto.Math.bin_to_hex 19 | "4f0150273733727f994754fee054df7e18ec169892db5ba973cf8580b898651b" 20 | 21 | iex> ExthCrypto.Cipher.encrypt("execute order 66", ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector, {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}) |> ExthCrypto.Math.bin_to_hex 22 | "2a7935444247175ff635309b9274e948" 23 | 24 | iex> ExthCrypto.Cipher.encrypt("execute order 66", ExthCrypto.Test.symmetric_key, {ExthCrypto.AES, ExthCrypto.AES.block_size, :ecb}) |> ExthCrypto.Math.bin_to_hex 25 | "a73c5576667b7b43a23a9fd930b5465d637a44d08bf702881a8d4e6a5d4944b5" 26 | """ 27 | @spec encrypt(plaintext, ExthCrypto.Key.symmetric_key, init_vector, cipher) :: ciphertext 28 | def encrypt(plaintext, symmetric_key, init_vector, {mod, _block_size, mode} = _cipher) do 29 | mod.encrypt(plaintext, mode, symmetric_key, init_vector) 30 | end 31 | 32 | @spec encrypt(plaintext, ExthCrypto.Key.symmetric_key, cipher) :: ciphertext 33 | def encrypt(plaintext, symmetric_key, {mod, _block_size, mode} = _cipher) do 34 | mod.encrypt(plaintext, mode, symmetric_key) 35 | end 36 | 37 | @doc """ 38 | Decrypts the given ciphertext from the given block cipher. 39 | 40 | ## Examples 41 | 42 | iex> "4f0150273733727f994754fee054df7e18ec169892db5ba973cf8580b898651b" 43 | ...> |> ExthCrypto.Math.hex_to_bin 44 | ...> |> ExthCrypto.Cipher.decrypt(ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector, {ExthCrypto.AES, ExthCrypto.AES.block_size, :cbc}) 45 | <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "execute order 66" 46 | 47 | iex> "2a7935444247175ff635309b9274e948" 48 | ...> |> ExthCrypto.Math.hex_to_bin 49 | ...> |> ExthCrypto.Cipher.decrypt(ExthCrypto.Test.symmetric_key, ExthCrypto.Test.init_vector, {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}) 50 | "execute order 66" 51 | 52 | iex> "a73c5576667b7b43a23a9fd930b5465d637a44d08bf702881a8d4e6a5d4944b5" 53 | ...> |> ExthCrypto.Math.hex_to_bin 54 | ...> |> ExthCrypto.Cipher.decrypt(ExthCrypto.Test.symmetric_key, {ExthCrypto.AES, ExthCrypto.AES.block_size, :ecb}) 55 | <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> <> "execute order 66" 56 | """ 57 | @spec decrypt(ciphertext, ExthCrypto.Key.symmetric_key, init_vector, cipher) :: plaintext 58 | def decrypt(ciphertext, symmetric_key, init_vector, {mod, _block_size, mode} = _cipher) do 59 | mod.decrypt(ciphertext, mode, symmetric_key, init_vector) 60 | end 61 | 62 | @spec decrypt(ciphertext, ExthCrypto.Key.symmetric_key, cipher) :: plaintext 63 | def decrypt(ciphertext, symmetric_key, {mod, _block_size, mode} = _cipher) do 64 | mod.decrypt(ciphertext, mode, symmetric_key) 65 | end 66 | 67 | @doc """ 68 | Generate a random initialization vector for the given type of cipher. 69 | 70 | ## Examples 71 | 72 | iex> ExthCrypto.Cipher.generate_init_vector(32) |> byte_size 73 | 32 74 | 75 | iex> ExthCrypto.Cipher.generate_init_vector(32) == ExthCrypto.Cipher.generate_init_vector(32) 76 | false 77 | """ 78 | @spec generate_init_vector(integer()) :: init_vector 79 | def generate_init_vector(block_size) do 80 | :crypto.strong_rand_bytes(block_size) 81 | end 82 | end -------------------------------------------------------------------------------- /lib/ecies/ecdh.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ECDH do 2 | @moduledoc """ 3 | Implements Elliptic Curve Diffie-Hellman, as it pertains to Exthereum. 4 | """ 5 | 6 | @default_curve :secp256k1 7 | 8 | @doc """ 9 | Generates a new keypair for elliptic curve diffie-hellman. 10 | 11 | These keys should be used as ephemeral keys in the key-exchange protocol. 12 | 13 | ## Examples 14 | 15 | iex> {public_key, private_key} = ExthCrypto.ECIES.ECDH.new_ecdh_keypair() 16 | iex> byte_size(public_key) 17 | 65 18 | iex> byte_size(private_key) 19 | 32 20 | iex> {public_key, private_key} == :crypto.generate_key(:ecdh, :secp256k1, private_key) 21 | true 22 | """ 23 | @spec new_ecdh_keypair(ExthCrypto.named_curve) :: ExthCrypto.Key.keypair 24 | def new_ecdh_keypair(curve \\ @default_curve) when is_atom(curve) do 25 | :crypto.generate_key(:ecdh, curve) 26 | end 27 | 28 | @doc """ 29 | Generates a static shared secret between two parties according to the 30 | protocol for elliptic curve diffie hellman. 31 | 32 | ## Examples 33 | 34 | iex> ExthCrypto.ECIES.ECDH.generate_shared_secret(ExthCrypto.Test.private_key(:key_b), ExthCrypto.Test.public_key(:key_a), :secp256k1) 35 | <<68, 139, 102, 172, 32, 159, 198, 236, 33, 216, 132, 22, 62, 46, 163, 215, 53, 40, 177, 14, 51, 94, 155, 151, 21, 226, 9, 254, 153, 48, 112, 226>> 36 | 37 | iex> ExthCrypto.ECIES.ECDH.generate_shared_secret(ExthCrypto.Test.private_key(:key_a), ExthCrypto.Test.public_key(:key_b), :secp256k1) 38 | <<68, 139, 102, 172, 32, 159, 198, 236, 33, 216, 132, 22, 62, 46, 163, 215, 53, 40, 177, 14, 51, 94, 155, 151, 21, 226, 9, 254, 153, 48, 112, 226>> 39 | """ 40 | @spec generate_shared_secret(ExthCrypto.Key.private_key, ExthCrypto.Key.public_key, ExthCrypto.named_curve) :: binary() 41 | def generate_shared_secret(local_private_key, remote_public_key, curve \\ @default_curve) when is_atom(curve) do 42 | :crypto.compute_key(:ecdh, remote_public_key, local_private_key, curve) 43 | end 44 | end -------------------------------------------------------------------------------- /lib/ecies/ecies.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES do 2 | @moduledoc """ 3 | Defines ECIES, as it pertains to Ethereum. 4 | 5 | This is derived primarily from [SEC 1: Elliptic Curve Cryptography](http://www.secg.org/sec1-v1.99.dif.pdf) 6 | """ 7 | 8 | alias ExthCrypto.ECIES.Parameters 9 | alias ExthCrypto.ECIES.ECDH 10 | alias ExthCrypto.MAC 11 | alias ExthCrypto.Hash 12 | alias ExthCrypto.Cipher 13 | 14 | @curve_name :secp256k1 15 | 16 | @doc """ 17 | Encrypts a message according to ECIES specification. 18 | 19 | ECIES Encrypt, where P = recipient public key is: 20 | ``` 21 | 1) generate r = random value 22 | 2) generate shared-secret = kdf( ecdhAgree(r, P) ) 23 | 3) generate R = rG [same op as generating a public key] 24 | 4) send 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag 25 | ``` 26 | 27 | ## Examples 28 | 29 | iex> {:ok, enc} = ExthCrypto.ECIES.encrypt(ExthCrypto.Test.public_key(:key_a), "hello", "shared_info_1", "shared_info_2", ExthCrypto.Test.key_pair(:key_b), ExthCrypto.Test.init_vector) 30 | iex> enc |> ExthCrypto.Math.bin_to_hex 31 | "049871eb081567823267592abac8ec9e9fddfdece7901a15f233b53f304d7860686c21601ba1a7f56680e22d0ac03eccd08e496469514c25ae1d5e55f391c1956f0102030405060708090a0b0c0d0e0f10a6c88ba08a258e9e5b5124997ee1b502570f933d4fc0b48cef5a504749e4eac1a56f3211de" 32 | 33 | # Test overhead is exactly 113 bytes 34 | iex> msg = "The quick brown fox jumped over the lazy dog." 35 | iex> {:ok, enc} = ExthCrypto.ECIES.encrypt(ExthCrypto.Test.public_key(:key_a), msg, "shared_info_1", "shared_info_2", ExthCrypto.Test.key_pair(:key_b), ExthCrypto.Test.init_vector) 36 | iex> byte_size(enc) - byte_size(msg) 37 | 113 38 | 39 | # TODO: More tests 40 | """ 41 | @spec encrypt(ExthCrypto.Key.public_key, Cipher.plaintext, binary(), binary(), {ExthCrypto.Key.public_key, ExthCrypto.Key.private_key} | nil, Cipher.init_vector | nil) :: {:ok, binary()} | {:error, String.t} 42 | def encrypt(her_static_public_key, message, shared_info_1 \\ <<>>, shared_info_2 \\ <<>>, my_ephemeral_key_pair \\ nil, init_vector \\ nil) do 43 | params = Parameters.ecies_aes128_sha256() # Question, is this always the parameters? If not, how do we choose? 44 | key_len = params.key_len 45 | 46 | # First, create a new ephemeral key pair (SEC1 - §5.1.3 - Step 1) 47 | {my_ephemeral_public_key, my_ephemeral_private_key} = case my_ephemeral_key_pair do 48 | {my_ephemeral_public_key, my_ephemeral_private_key} -> {my_ephemeral_public_key, my_ephemeral_private_key} 49 | nil -> ECDH.new_ecdh_keypair(@curve_name) 50 | end 51 | 52 | init_vector = if init_vector, do: init_vector, else: Cipher.generate_init_vector(key_len) 53 | 54 | # SEC1 - §5.1.3 - Step 2 55 | # No point compression. 56 | 57 | # SEC1 - §5.1.3 - Steps 3, 4 58 | # Next, generate our ECDH shared secret (no co-factor) 59 | shared_secret = ECDH.generate_shared_secret(my_ephemeral_private_key, her_static_public_key, @curve_name) 60 | 61 | # Next, derive a KDF twice the length as needed, with shared_info_1 as the extra_data 62 | # SEC1 - §5.1.3 - Step 5 63 | kdf = ExthCrypto.KDF.NistSp80056.single_step_kdf(shared_secret, 2 * params.key_len, params.hasher, shared_info_1) 64 | 65 | # The first half becomes the encoded key, the second half becomes a mac 66 | with {:ok, derived_keys} <- kdf do 67 | # SEC1 - §5.1.3 - Step 6 68 | <> = derived_keys 69 | 70 | # Now, encrypt the message with our encoded key 71 | # SEC1 - §5.1.3 - Step 7 72 | encoded_message = Cipher.encrypt(message, key_enc, init_vector, params.cipher) 73 | 74 | # Hash the key mac 75 | key_mac_hashed = Hash.hash(key_mac, params.hasher) 76 | 77 | # Tag the messsage and shared_info_2 data 78 | message_tag = MAC.mac(init_vector <> encoded_message <> shared_info_2, key_mac_hashed, params.mac) 79 | 80 | # Remove DER encoding byte 81 | my_ephemeral_public_key_raw = ExthCrypto.Key.der_to_raw(my_ephemeral_public_key) 82 | 83 | # return 0x04 || R || AsymmetricEncrypt(shared-secret, plaintext) || tag 84 | {:ok, <<0x04>> <> my_ephemeral_public_key_raw <> init_vector <> encoded_message <> message_tag} 85 | end 86 | end 87 | 88 | @doc """ 89 | Decrypts a message according to ECIES specification. 90 | 91 | ECIES Decrypt (performed by recipient): 92 | ``` 93 | 1) generate shared-secret = kdf( ecdhAgree(myPrivKey, msg[1:65]) ) 94 | 2) verify tag 95 | 3) decrypt 96 | 97 | ecdhAgree(r, recipientPublic) == ecdhAgree(recipientPrivate, R) 98 | [where R = r*G, and recipientPublic = recipientPrivate*G] 99 | ``` 100 | 101 | ## Examples 102 | 103 | iex> ecies_encoded_msg = "049871eb081567823267592abac8ec9e9fddfdece7901a15f233b53f304d7860686c21601ba1a7f56680e22d0ac03eccd08e496469514c25ae1d5e55f391c1956f0102030405060708090a0b0c0d0e0f10a6c88ba08a258e9e5b5124997ee1b502570f933d4fc0b48cef5a504749e4eac1a56f3211de" |> ExthCrypto.Math.hex_to_bin 104 | iex> ExthCrypto.ECIES.decrypt(ExthCrypto.Test.private_key(:key_a), ecies_encoded_msg, "shared_info_1", "shared_info_2") 105 | {:ok, "hello"} 106 | """ 107 | @spec decrypt(ExthCrypto.Key.private_key, binary(), binary(), binary()) :: {:ok, Cipher.plaintext} | {:error, String.t} 108 | def decrypt(my_static_private_key, ecies_encoded_msg, shared_info_1 \\ <<>>, shared_info_2 \\ <<>>) do 109 | params = Parameters.ecies_aes128_sha256() # Question, is this always the parameters? If not, how do we choose? 110 | 111 | # Get size of key len, block size and hash len, all in bits 112 | header_size = 1 113 | header_size_bits = header_size * 8 114 | key_len = params.key_len 115 | private_key_len = byte_size(my_static_private_key) 116 | public_key_len = private_key_len * 2 117 | hash_len = Parameters.hash_len(params) 118 | encoded_message_len = byte_size(ecies_encoded_msg) - header_size - public_key_len - key_len - hash_len 119 | 120 | # Decode the ECIES encoded message 121 | case ecies_encoded_msg do 122 | # SEC1 - §5.1.4 - Step 1 123 | # Note, we only allow 0x04 as the header byte 124 | << 125 | 0x04::size(header_size_bits), # header 126 | her_ephemeral_public_key_raw::binary-size(public_key_len), # public key 127 | cipher_iv::binary-size(key_len), # cipher iv 128 | encoded_message::binary-size(encoded_message_len), # encoded_message 129 | message_tag::binary-size(hash_len)>> -> # message tag 130 | 131 | # TODO: SEC1 - §5.1.4 - Steps 2, 3 - Verify curve 132 | 133 | # SEC1 - §5.1.4 - Steps 4, 5 134 | # Generate a shared secret based on our ephemeral private key and the ephemeral public key from the message 135 | her_ephemeral_public_key = ExthCrypto.Key.raw_to_der(her_ephemeral_public_key_raw) 136 | 137 | shared_secret = ECDH.generate_shared_secret(my_static_private_key, her_ephemeral_public_key, @curve_name) 138 | 139 | # SEC1 - §5.1.4 - Step 6 140 | # Geneate our KDF as before 141 | kdf = ExthCrypto.KDF.NistSp80056.single_step_kdf(shared_secret, 2 * params.key_len, params.hasher, shared_info_1) 142 | 143 | # The first half becomes the encoded key, the second half becomes a mac 144 | with {:ok, derived_keys} <- kdf do 145 | 146 | # SEC1 - §5.1.4 - Step 7 147 | <> = derived_keys 148 | 149 | # Hash the key mac 150 | key_mac_hashed = Hash.hash(key_mac, params.hasher) 151 | 152 | # SEC1 - §5.1.4 - Step 8 153 | # Tag the messsage and shared_info_2 data 154 | generated_message_tag = MAC.mac(cipher_iv <> encoded_message <> shared_info_2, key_mac_hashed, params.mac) 155 | 156 | unless message_tag == generated_message_tag do 157 | {:error, "Invalid message tag"} 158 | else 159 | # SEC1 - §5.1.4 - Step 9 160 | message = Cipher.decrypt(encoded_message, key_enc, cipher_iv, params.cipher) 161 | 162 | # SEC1 - §5.1.4 - Step 10 163 | {:ok, message} 164 | end 165 | end 166 | _els -> {:error, "Invalid ECIES encoded message"} 167 | end 168 | end 169 | 170 | end -------------------------------------------------------------------------------- /lib/ecies/parameters.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.Parameters do 2 | @moduledoc """ 3 | Returns one set of the Standard ECIES parameters: 4 | 5 | * ECIES using AES128 and HMAC-SHA-256-16 6 | * ECIES using AES256 and HMAC-SHA-256-32 7 | * ECIES using AES256 and HMAC-SHA-384-48 8 | * ECIES using AES256 and HMAC-SHA-512-64 9 | """ 10 | 11 | defstruct [ 12 | mac: nil, 13 | hasher: nil, 14 | cipher: nil, 15 | key_len: nil 16 | ] 17 | 18 | @type t :: %__MODULE__{ 19 | mac: :crypto.hash_algorithms, 20 | hasher: ExthCrypto.hash_type, 21 | cipher: ExthCrypto.cipher, 22 | key_len: integer() 23 | } 24 | 25 | @doc """ 26 | Returns curve parameters for ECIES with AES-256 symmetric 27 | encryption and SHA-256 hash. 28 | """ 29 | @spec ecies_aes128_sha256() :: t 30 | def ecies_aes128_sha256 do 31 | %__MODULE__{ 32 | mac: :sha256, 33 | hasher: {&ExthCrypto.Hash.SHA.sha256/1, nil, 32}, 34 | cipher: {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}, 35 | key_len: 16, 36 | } 37 | end 38 | 39 | @doc """ 40 | Returns curve parameters for ECIES with AES-256 symmetric 41 | encryption and SHA-256 hash. 42 | """ 43 | @spec ecies_aes256_sha256() :: t 44 | def ecies_aes256_sha256 do 45 | %__MODULE__{ 46 | mac: :sha256, 47 | hasher: {&ExthCrypto.Hash.SHA.sha256/1, nil, 32}, 48 | cipher: {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}, 49 | key_len: 32, 50 | } 51 | end 52 | 53 | @doc """ 54 | Returns curve parameters for ECIES with AES-256 symmetric 55 | encryption and SHA-384 hash. 56 | """ 57 | @spec ecies_aes256_sha384() :: t 58 | def ecies_aes256_sha384 do 59 | %__MODULE__{ 60 | mac: :sha256, 61 | hasher: {&ExthCrypto.Hash.SHA.sha384/1, nil, 48}, 62 | cipher: {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}, 63 | key_len: 32, 64 | } 65 | end 66 | 67 | @doc """ 68 | Returns curve parameters for ECIES with AES-256 symmetric 69 | encryption and SHA-512 hash. 70 | """ 71 | @spec ecies_aes256_sha512() :: t 72 | def ecies_aes256_sha512 do 73 | %__MODULE__{ 74 | mac: :sha256, 75 | hasher: {&ExthCrypto.Hash.SHA.sha512/1, nil, 64}, 76 | cipher: {ExthCrypto.AES, ExthCrypto.AES.block_size, :ctr}, 77 | key_len: 32, 78 | } 79 | end 80 | 81 | @doc """ 82 | Returns the block size of a given set of ECIES params. 83 | 84 | ## Examples 85 | 86 | iex> ExthCrypto.ECIES.Parameters.block_size(ExthCrypto.ECIES.Parameters.ecies_aes256_sha512) 87 | 32 88 | """ 89 | @spec block_size(t) :: integer() 90 | def block_size(params) do 91 | {_, block_size, _args} = params.cipher 92 | 93 | block_size 94 | end 95 | 96 | @doc """ 97 | Returns the hash len of a given set of ECIES params. 98 | 99 | ## Examples 100 | 101 | iex> ExthCrypto.ECIES.Parameters.hash_len(ExthCrypto.ECIES.Parameters.ecies_aes256_sha256) 102 | 32 103 | 104 | iex> ExthCrypto.ECIES.Parameters.hash_len(ExthCrypto.ECIES.Parameters.ecies_aes256_sha512) 105 | 64 106 | """ 107 | @spec hash_len(t) :: integer() 108 | def hash_len(params) do 109 | # Get size of hash cipher 110 | {_, _, hash_len} = params.hasher 111 | 112 | hash_len 113 | end 114 | end -------------------------------------------------------------------------------- /lib/exth_crypto.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto do 2 | @moduledoc """ 3 | Handles the general crypto stuff. 4 | """ 5 | 6 | @type curve :: nil 7 | @type curve_params :: nil 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/hash/fake.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.Fake do 2 | @moduledoc """ 3 | Simple fake hash that basically just returns its own input. 4 | 5 | Gasp, that's reversable! 6 | """ 7 | 8 | @type fake_mac :: {:fake_mac, binary()} 9 | 10 | @doc """ 11 | Initializes a new Fake mac stream. 12 | 13 | ## Examples 14 | 15 | iex> fake_mac = ExthCrypto.Hash.Fake.init_mac("abc") 16 | iex> is_nil(fake_mac) 17 | false 18 | """ 19 | @spec init_mac(binary()) :: fake_mac 20 | def init_mac(initial) do 21 | {:fake_mac, initial} 22 | end 23 | 24 | @doc """ 25 | Updates a given Fake mac stream, which is, do nothing. 26 | 27 | ## Examples 28 | 29 | iex> fake_mac = ExthCrypto.Hash.Fake.init_mac("init") 30 | ...> |> ExthCrypto.Hash.Fake.update_mac("data") 31 | iex> is_nil(fake_mac) 32 | false 33 | """ 34 | @spec update_mac(fake_mac, binary()) :: fake_mac 35 | def update_mac({:fake_mac, mac}, _data) do 36 | {:fake_mac, mac} 37 | end 38 | 39 | @doc """ 40 | Finalizes a given Fake mac stream to produce the current 41 | hash. 42 | 43 | ## Examples 44 | 45 | iex> ExthCrypto.Hash.Fake.init_mac("abc") 46 | ...> |> ExthCrypto.Hash.Fake.update_mac("def") 47 | ...> |> ExthCrypto.Hash.Fake.final_mac() 48 | "abc" 49 | """ 50 | @spec final_mac(fake_mac) :: binary() 51 | def final_mac({:fake_mac, mac}) do 52 | mac 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /lib/hash/hash.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash do 2 | @moduledoc """ 3 | A variety of functions to handle one-way hashing functions as 4 | defined by Ethereum. 5 | """ 6 | 7 | @type hash_algorithm :: atom() 8 | @type hash :: binary() 9 | @type hasher :: (binary() -> binary()) 10 | @type hash_type :: {hasher, integer() | nil, integer()} 11 | 12 | @doc """ 13 | Returns a list of supported hash algorithms. 14 | """ 15 | @hash_algorithms [ :md5, :ripemd160, :sha, :sha224, :sha256, :sha384, :sha512 ] 16 | @spec hash_algorithms() :: [hash_algorithm] 17 | def hash_algorithms, do: @hash_algorithms 18 | 19 | @doc """ 20 | The SHA1 hasher. 21 | """ 22 | @spec sha1() :: ExthCrypto.hash_type 23 | def sha1, do: {&ExthCrypto.Hash.SHA.sha1/1, nil, 20} 24 | 25 | @doc """ 26 | The KECCAK hasher, as defined by Ethereum. 27 | """ 28 | @spec kec() :: ExCrpyto.hash_type 29 | def kec, do: {&ExthCrypto.Hash.Keccak.kec/1, nil, 256} 30 | 31 | @doc """ 32 | Runs the specified hash type on the given data. 33 | 34 | ## Examples 35 | 36 | iex> ExthCrypto.Hash.hash("hello world", ExthCrypto.Hash.kec) |> ExthCrypto.Math.bin_to_hex 37 | "47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" 38 | 39 | iex> ExthCrypto.Hash.hash("hello world", ExthCrypto.Hash.sha1) |> ExthCrypto.Math.bin_to_hex 40 | "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" 41 | """ 42 | @spec hash(iodata(), hash_type) :: hash 43 | def hash(data, {hash_fun, _, _}=_hasher) do 44 | hash_fun.(data) 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /lib/hash/keccak.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.Keccak do 2 | @moduledoc """ 3 | Simple wrapper for Keccak function for Ethereum. 4 | 5 | Note: This module defines KECCAK as defined by Ethereum, which differs slightly 6 | than that assigned as the new SHA-3 variant. For SHA-3, a few constants have 7 | been changed prior to adoption by NIST, but after adoption by Ethereum. 8 | """ 9 | 10 | @type keccak_hash :: ExthCrypto.hash 11 | @type keccak_mac :: {atom(), binary()} 12 | 13 | @doc """ 14 | Returns the keccak sha256 of a given input. 15 | 16 | ## Examples 17 | 18 | iex> ExthCrypto.Hash.Keccak.kec("hello world") 19 | <<71, 23, 50, 133, 168, 215, 52, 30, 94, 151, 47, 198, 119, 40, 99, 20 | 132, 248, 2, 248, 239, 66, 165, 236, 95, 3, 187, 250, 37, 76, 176, 21 | 31, 173>> 22 | 23 | iex> ExthCrypto.Hash.Keccak.kec(<<0x01, 0x02, 0x03>>) 24 | <<241, 136, 94, 218, 84, 183, 160, 83, 49, 140, 212, 30, 32, 147, 34, 25 | 13, 171, 21, 214, 83, 129, 177, 21, 122, 54, 51, 168, 59, 253, 92, 26 | 146, 57>> 27 | """ 28 | @spec kec(binary()) :: keccak_hash 29 | def kec(data) do 30 | :keccakf1600.sha3_256(data) 31 | end 32 | 33 | @doc """ 34 | Initializes a new Keccak mac stream. 35 | 36 | ## Examples 37 | 38 | iex> keccak_mac = ExthCrypto.Hash.Keccak.init_mac() 39 | iex> is_nil(keccak_mac) 40 | false 41 | """ 42 | @spec init_mac() :: keccak_mac 43 | def init_mac() do 44 | :keccakf1600.init(:sha3_256) 45 | end 46 | 47 | @doc """ 48 | Updates a given Keccak mac stream with the given 49 | secret and data, returning a new mac stream. 50 | 51 | ## Examples 52 | 53 | iex> keccak_mac = ExthCrypto.Hash.Keccak.init_mac() 54 | ...> |> ExthCrypto.Hash.Keccak.update_mac("data") 55 | iex> is_nil(keccak_mac) 56 | false 57 | """ 58 | @spec update_mac(keccak_mac, binary()) :: keccak_mac 59 | def update_mac(mac, data) do 60 | :keccakf1600.update(mac, data) 61 | end 62 | 63 | @doc """ 64 | Finalizes a given Keccak mac stream to produce the current 65 | hash. 66 | 67 | ## Examples 68 | 69 | iex> ExthCrypto.Hash.Keccak.init_mac() 70 | ...> |> ExthCrypto.Hash.Keccak.update_mac("data") 71 | ...> |> ExthCrypto.Hash.Keccak.final_mac() 72 | ...> |> ExthCrypto.Math.bin_to_hex 73 | "8f54f1c2d0eb5771cd5bf67a6689fcd6eed9444d91a39e5ef32a9b4ae5ca14ff" 74 | """ 75 | @spec final_mac(keccak_mac) :: keccak_hash 76 | def final_mac(mac) do 77 | :keccakf1600.final(mac) 78 | end 79 | 80 | end -------------------------------------------------------------------------------- /lib/hash/sha.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.SHA do 2 | @moduledoc """ 3 | Helper functions for running Secure Hash Algorithm (SHA). 4 | """ 5 | 6 | @doc """ 7 | Computes the SHA-1 of a given input. 8 | 9 | ## Examples 10 | 11 | iex> ExthCrypto.Hash.SHA.sha1("The quick brown fox jumps over the lazy dog") |> ExthCrypto.Math.bin_to_hex 12 | "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" 13 | 14 | iex> ExthCrypto.Hash.SHA.sha1("") |> ExthCrypto.Math.bin_to_hex 15 | "da39a3ee5e6b4b0d3255bfef95601890afd80709" 16 | """ 17 | @spec sha1(binary()) :: <<_::160>> 18 | def sha1(data) do 19 | :crypto.hash(:sha, data) 20 | end 21 | 22 | @doc """ 23 | Computes the SHA-2 of a given input outputting 256 bits. 24 | 25 | ## Examples 26 | 27 | iex> ExthCrypto.Hash.SHA.sha256("") |> ExthCrypto.Math.bin_to_hex 28 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 29 | """ 30 | @spec sha256(binary()) :: <<_::256>> 31 | def sha256(data) do 32 | :crypto.hash(:sha256, data) 33 | end 34 | 35 | @doc """ 36 | Computes the SHA-2 of a given input outputting 384 bits. 37 | 38 | ## Examples 39 | 40 | iex> ExthCrypto.Hash.SHA.sha384("") |> ExthCrypto.Math.bin_to_hex 41 | "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" 42 | """ 43 | @spec sha384(binary()) :: <<_::384>> 44 | def sha384(data) do 45 | :crypto.hash(:sha384, data) 46 | end 47 | 48 | @doc """ 49 | Computes the SHA-2 of a given input outputting 512 bits. 50 | 51 | ## Examples 52 | 53 | iex> ExthCrypto.Hash.SHA.sha512("") |> ExthCrypto.Math.bin_to_hex 54 | "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" 55 | """ 56 | @spec sha512(binary()) :: <<_::512>> 57 | def sha512(data) do 58 | :crypto.hash(:sha512, data) 59 | end 60 | end -------------------------------------------------------------------------------- /lib/kdf/nist_sp_800_56.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.KDF.NistSp80056 do 2 | @moduledoc """ 3 | Implements NIST SP 800-56 Key Deriviation Function, 4 | as defined in http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf. 5 | 6 | See Section 5.8.1 "The Single-step Key-Derivation Function" 7 | """ 8 | 9 | import ExthCrypto.Math 10 | 11 | @two_power_32 round(:math.pow(2, 32)) 12 | @max_32_int @two_power_32 - 1 13 | 14 | @doc """ 15 | Simples the The Single-step Key-Derivation Function as defined in NISP-SP-800-56. 16 | 17 | Note: we do not currently support HMAC. 18 | 19 | # TODO: Test canonical tests 20 | 21 | ## Examples 22 | 23 | iex> ExthCrypto.KDF.NistSp80056.single_step_kdf("secret", 20, ExthCrypto.Hash.sha1(), "extra") 24 | {:ok, ExthCrypto.Hash.SHA.sha1(<<0, 0, 0, 1>> <> "secret" <> "extra")} 25 | 26 | iex> <> = ExthCrypto.Hash.SHA.sha1(<<0, 0, 0, 1>> <> "secret" <> "extra") <> ExthCrypto.Hash.SHA.sha1(<<0, 0, 0, 2>> <> "secret" <> "extra") 27 | iex> {:ok, bytes} == ExthCrypto.KDF.NistSp80056.single_step_kdf("secret", 32, ExthCrypto.Hash.sha1(), "extra") 28 | true 29 | 30 | iex> ExthCrypto.KDF.NistSp80056.single_step_kdf("secret", 32, ExthCrypto.Hash.sha1(), "extra") 31 | {:ok, <<35, 233, 145, 168, 1, 248, 54, 133, 234, 105, 153, 226, 62, 181, 32 | 40, 209, 153, 239, 241, 41, 16, 157, 216, 219, 15, 132, 68, 116, 33 | 206, 152, 20, 98>>} 34 | 35 | iex> {:ok, key} = ExthCrypto.KDF.NistSp80056.single_step_kdf("secret", 200, ExthCrypto.Hash.sha1(), "extra") 36 | iex> ExthCrypto.Math.bin_to_hex(key) 37 | "23e991a801f83685ea6999e23eb528d199eff129109dd8db0f844474ce981462fca2dc108bde378d83d9e714a9964d9cd9b1364a167d98fbfe1f94bc6f606879f9150be2979fe27812b7de86546e1994672038b9493abfb4b959676b5927c75dd9f33489f865a71905100633412d9ae93677ca6b4b3646310550252cf1c30da0f9014c72728a66ce20489ec89c718231f163a359a282ff8f73b65e129a1980e130d58cb88d3b041eb29ea69561f0b24b80cb7f421042edf07374bfa553be44ee6b5bf4459de8ef2a" 38 | """ 39 | @spec single_step_kdf(binary(), integer(), ExthCrypto.Hash.hash_type, binary()) :: {:ok, binary()} | {:error, String.t} 40 | def single_step_kdf(shared_secret, key_data_len, {hasher, hash_in_max_len, hash_out_len}, extra_data \\ <<>>) do 41 | # ((key_len + 7) * 8) / (hash_blocksize * 8) 42 | reps = round(:math.ceil(key_data_len / hash_out_len)) 43 | key_data_len_bits = key_data_len * 8 44 | 45 | cond do 46 | key_data_len_bits > hash_out_len * @max_32_int -> {:error, "Key data is too large"} 47 | reps > @max_32_int -> {:error, "Too many reps required"} 48 | not is_nil(hash_in_max_len) and byte_size(shared_secret <> extra_data) + 4 > hash_in_max_len -> {:error, "Concatenation of counter, shared_secret and extra_data too large"} 49 | true -> 50 | derived_keying_material_padded = Enum.reduce(1..reps, <<>>, fn counter, results -> 51 | counter_enc = :binary.encode_unsigned(counter |> mod(@two_power_32), :big) |> ExthCrypto.Math.pad(4) 52 | 53 | result = hasher.(counter_enc <> shared_secret <> extra_data) 54 | 55 | results <> result 56 | end) 57 | 58 | <> = derived_keying_material_padded 59 | 60 | {:ok, derived_keying_material} 61 | end 62 | end 63 | end -------------------------------------------------------------------------------- /lib/key/key.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Key do 2 | @moduledoc """ 3 | Simple functions to interact with keys. 4 | """ 5 | 6 | @type symmetric_key :: binary() 7 | @type public_key_der :: binary() 8 | @type public_key :: binary() 9 | @type private_key_der :: binary() 10 | @type private_key :: binary() 11 | @type key_pair :: {public_key, private_key} 12 | 13 | @doc """ 14 | Converts a key from der to raw format. 15 | 16 | ## Examples 17 | 18 | iex> ExthCrypto.Key.der_to_raw(<<0x04, 0x01>>) 19 | <<0x01>> 20 | """ 21 | @spec der_to_raw(public_key_der) :: public_key 22 | def der_to_raw(public_key_der) do 23 | <<0x04, public_key::binary()>> = public_key_der 24 | 25 | public_key 26 | end 27 | 28 | @doc """ 29 | Converts a key from raw to der format. 30 | 31 | ## Examples 32 | 33 | iex> ExthCrypto.Key.der_to_raw(<<0x04, 0x01>>) 34 | <<0x01>> 35 | """ 36 | @spec raw_to_der(public_key) :: public_key_der 37 | def raw_to_der(public_key) do 38 | <<0x04>> <> public_key 39 | end 40 | 41 | end -------------------------------------------------------------------------------- /lib/mac/mac.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MAC do 2 | @moduledoc """ 3 | Wrapper for erlang's built-in HMAC (Hash-based Message Authentication Code) 4 | and CMAC (Cipher-based Message Authentication Code) routines, to be used for Exthereum. 5 | """ 6 | 7 | alias ExthCrypto.Hash 8 | 9 | @type mac :: binary() 10 | @type mac_type :: :kec | :fake 11 | @type mac_inst :: {mac_type, any()} 12 | 13 | defp mac_module(:kec), do: ExthCrypto.Hash.Keccak 14 | defp mac_module(:fake), do: ExthCrypto.Hash.Fake 15 | 16 | @doc """ 17 | Calcluates the MAC of a given set of input. 18 | 19 | ## Examples 20 | 21 | iex> ExthCrypto.MAC.mac("The quick brown fox jumps over the lazy dog", "key", :sha256) |> ExthCrypto.Math.bin_to_hex 22 | "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8" 23 | 24 | iex> ExthCrypto.MAC.mac("The quick brown fox jumps over the lazy dog", "key", :sha256, 8) 25 | <<247, 188, 131, 244, 48, 83, 132, 36>> 26 | """ 27 | @spec mac(iodata(), iodata(), Hash.hash_algorithm, integer()) :: mac 28 | def mac(data, key, hash_algorithm, length \\ nil) when is_atom(hash_algorithm) do 29 | cond do 30 | Enum.member?(Hash.hash_algorithms, hash_algorithm) -> 31 | case length do 32 | nil -> :crypto.hmac(hash_algorithm, key, data) 33 | _ -> :crypto.hmac(hash_algorithm, key, data, length) 34 | end 35 | # TODO: Implement CMAC 36 | end 37 | end 38 | 39 | @doc """ 40 | Initializes a new mac of given type with given args. 41 | """ 42 | @spec init(mac_type) :: mac_inst 43 | def init(mac_type, args \\ []) do 44 | {mac_type, apply(mac_module(mac_type), :init_mac, args)} 45 | end 46 | 47 | @doc """ 48 | Updates a given mac stream with the given secret and data, returning a new mac stream. 49 | 50 | ## Examples 51 | 52 | iex> mac = ExthCrypto.MAC.init(:kec) 53 | ...> |> ExthCrypto.MAC.update("data") 54 | iex> is_nil(mac) 55 | false 56 | """ 57 | @spec update(mac_inst, binary()) :: mac_inst 58 | def update({mac_type, mac}, data) do 59 | {mac_type, mac_module(mac_type).update_mac(mac, data)} 60 | end 61 | 62 | @doc """ 63 | Finalizes a given mac stream to produce the current hash. 64 | 65 | ## Examples 66 | 67 | iex> ExthCrypto.MAC.init(:kec) 68 | ...> |> ExthCrypto.MAC.update("data") 69 | ...> |> ExthCrypto.MAC.final() 70 | ...> |> ExthCrypto.Math.bin_to_hex 71 | "8f54f1c2d0eb5771cd5bf67a6689fcd6eed9444d91a39e5ef32a9b4ae5ca14ff" 72 | 73 | iex> ExthCrypto.MAC.init(:fake, ["jedi"]) 74 | ...> |> ExthCrypto.MAC.update(" ") 75 | ...> |> ExthCrypto.MAC.update("knight") 76 | ...> |> ExthCrypto.MAC.final() 77 | "jedi" 78 | """ 79 | @spec final(mac_inst) :: binary() 80 | def final({mac_type, mac}) do 81 | mac_module(mac_type).final_mac(mac) 82 | end 83 | end -------------------------------------------------------------------------------- /lib/math/math.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Math do 2 | @moduledoc """ 3 | Helpers for basic math functions. 4 | """ 5 | 6 | @doc """ 7 | Simple function to compute modulo function to work on integers of any sign. 8 | 9 | ## Examples 10 | 11 | iex> ExthCrypto.Math.mod(5, 2) 12 | 1 13 | 14 | iex> ExthCrypto.Math.mod(-5, 1337) 15 | 1332 16 | 17 | iex> ExthCrypto.Math.mod(1337 + 5, 1337) 18 | 5 19 | 20 | iex> ExthCrypto.Math.mod(0, 1337) 21 | 0 22 | """ 23 | def mod(x, n) when x > 0, do: rem x, n 24 | def mod(x, n) when x < 0, do: rem n + x, n 25 | def mod(0, _n), do: 0 26 | 27 | @doc """ 28 | Simple wrapper function to convert a hex string to a binary. 29 | 30 | ## Examples 31 | 32 | iex> ExthCrypto.Math.hex_to_bin("01020a0d") 33 | <<0x01, 0x02, 0x0a, 0x0d>> 34 | """ 35 | @spec hex_to_bin(String.t) :: binary() 36 | def hex_to_bin(hex) do 37 | {:ok, bin} = Base.decode16(hex, case: :lower) 38 | 39 | bin 40 | end 41 | 42 | @doc """ 43 | Left pads a given binary to specified length in bytes. 44 | 45 | This function raises if binary longer than given length already. 46 | 47 | ## Examples 48 | 49 | iex> ExthCrypto.Math.pad(<<1, 2, 3>>, 6) 50 | <<0x00, 0x00, 0x00, 0x01, 0x02, 0x03>> 51 | 52 | iex> ExthCrypto.Math.pad(<<1, 2, 3>>, 4) 53 | <<0x00, 0x01, 0x02, 0x03>> 54 | 55 | iex> ExthCrypto.Math.pad(<<1, 2, 3>>, 3) 56 | <<0x01, 0x02, 0x03>> 57 | 58 | iex> ExthCrypto.Math.pad(<<1, 2, 3>>, 0) 59 | ** (ArgumentError) argument error 60 | 61 | iex> ExthCrypto.Math.pad(<<>>, 0) 62 | <<>> 63 | """ 64 | @spec pad(binary(), integer()) :: binary() 65 | def pad(bin, length) do 66 | padding_bits = ( length - byte_size(bin) ) * 8 67 | 68 | <<0x00::size(padding_bits)>> <> bin 69 | end 70 | 71 | @doc """ 72 | Simple wrapper function to convert a binary to a hex string. 73 | 74 | ## Examples 75 | 76 | iex> ExthCrypto.Math.bin_to_hex(<<0x01, 0x02, 0x0a, 0x0d>>) 77 | "01020a0d" 78 | """ 79 | @spec bin_to_hex(binary()) :: String.t 80 | def bin_to_hex(bin), do: Base.encode16(bin, case: :lower) 81 | 82 | @doc """ 83 | Generate a random nonce value of specified length. 84 | 85 | ## Examples 86 | 87 | iex> ExthCrypto.Math.nonce(32) |> byte_size 88 | 32 89 | 90 | iex> ExthCrypto.Math.nonce(32) == ExthCrypto.Math.nonce(32) 91 | false 92 | """ 93 | @spec nonce(integer()) :: binary() 94 | def nonce(nonce_size) do 95 | :crypto.strong_rand_bytes(nonce_size) 96 | end 97 | 98 | @doc """ 99 | Computes the xor between two equal length binaries. 100 | 101 | ## Examples 102 | 103 | iex> ExthCrypto.Math.xor(<<0b10101010>>, <<0b11110000>>) 104 | <<0b01011010>> 105 | """ 106 | @spec xor(binary(), binary()) :: binary() 107 | def xor(a, b) when byte_size(a) == byte_size(b) do 108 | :crypto.exor(a, b) 109 | end 110 | 111 | end -------------------------------------------------------------------------------- /lib/signature/signature.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Signature do 2 | @moduledoc """ 3 | A variety of functions for calculating cryptographic signatures. 4 | 5 | Right now these all rely on the `libsecp256k1` library, and thus only 6 | performs signatures on this Elliptic Curve, but I don't see any reason 7 | why we couldn't switch to standard libraries if they provide similar 8 | functionality. 9 | """ 10 | 11 | @type signature :: binary() 12 | @type r :: integer() 13 | @type s :: integer() 14 | @type recovery_id :: integer() 15 | 16 | @doc """ 17 | Given a private key, returns a public key. 18 | 19 | ## Examples 20 | 21 | iex> ExthCrypto.Signature.get_public_key(ExthCrypto.Test.private_key) 22 | {:ok, <<4, 54, 241, 224, 126, 85, 135, 69, 213, 129, 115, 3, 41, 161, 217, 87, 215, 23 | 159, 64, 17, 167, 128, 113, 172, 232, 46, 34, 145, 136, 72, 160, 207, 161, 24 | 171, 255, 26, 163, 160, 158, 227, 196, 92, 62, 119, 84, 156, 99, 224, 155, 25 | 120, 250, 153, 134, 180, 218, 177, 186, 200, 199, 106, 97, 103, 50, 215, 114>>} 26 | 27 | iex> ExthCrypto.Signature.get_public_key(<<1>>) 28 | {:error, "Private key size not 32 bytes"} 29 | """ 30 | @spec get_public_key(ExthCrypto.Key.private_key) :: {:ok, ExthCrypto.Key.public_key} | {:error, String.t} 31 | def get_public_key(private_key) do 32 | case :libsecp256k1.ec_pubkey_create(private_key, :uncompressed) do 33 | {:ok, public_key} -> {:ok, public_key} 34 | {:error, reason} -> {:error, to_string(reason)} 35 | end 36 | end 37 | 38 | @doc """ 39 | Computes an ECDSA signature of the given (already digested) data. 40 | 41 | This returns both the `r` and `s` results, the recovery id, and the 42 | concatenated signature `sig`. 43 | 44 | ## Examples 45 | 46 | iex> {signature, _r, _s, _recovery_id} = ExthCrypto.Signature.sign_digest("12345", ExthCrypto.Test.private_key) 47 | iex> ExthCrypto.Signature.verify("12345", signature, ExthCrypto.Test.public_key) 48 | true 49 | """ 50 | @spec sign_digest(binary(), ExthCrypto.Key.private_key) :: {signature, r, s, recovery_id} 51 | def sign_digest(digest, private_key) do 52 | {:ok, <>=signature, recovery_id} = :libsecp256k1.ecdsa_sign_compact(digest, private_key, :default, <<>>) 53 | 54 | {signature, r, s, recovery_id} 55 | end 56 | 57 | @doc """ 58 | Verifies a that a given signature for a given digest is valid against a public key. 59 | 60 | ## Examples 61 | 62 | iex> msg = ExthCrypto.Math.nonce(32) 63 | iex> {signature, _r, _s, _recovery_id} = ExthCrypto.Signature.sign_digest(msg, ExthCrypto.Test.private_key(:key_a)) 64 | iex> ExthCrypto.Signature.verify(msg, signature, ExthCrypto.Test.public_key(:key_a)) 65 | true 66 | 67 | iex> msg = ExthCrypto.Math.nonce(32) 68 | iex> {signature, _r, _s, _recovery_id} = ExthCrypto.Signature.sign_digest(msg, ExthCrypto.Test.private_key(:key_a)) 69 | iex> ExthCrypto.Signature.verify(msg, signature, ExthCrypto.Test.public_key(:key_b)) 70 | false 71 | 72 | iex> msg = ExthCrypto.Math.nonce(32) 73 | iex> {signature, _r, _s, _recovery_id} = ExthCrypto.Signature.sign_digest(msg, ExthCrypto.Test.private_key(:key_a)) 74 | iex> ExthCrypto.Signature.verify(msg |> Binary.drop(1), signature, ExthCrypto.Test.public_key(:key_a)) 75 | false 76 | """ 77 | @spec verify(binary(), signature, ExthCrypto.Key.public_key) :: boolean() 78 | def verify(digest, signature, public_key) do 79 | case :libsecp256k1.ecdsa_verify_compact(digest, signature, public_key) do 80 | :ok -> true 81 | :error -> false 82 | end 83 | end 84 | 85 | @doc """ 86 | Recovers a public key from a given signature for a given digest. 87 | 88 | Note, the key is returned in DER format (with a leading 0x04 to indicate 89 | that it's an octet-string). 90 | 91 | ## Examples 92 | 93 | iex> msg = ExthCrypto.Math.nonce(32) 94 | iex> {signature, _r, _s, recovery_id} = ExthCrypto.Signature.sign_digest(msg, ExthCrypto.Test.private_key(:key_a)) 95 | iex> ExthCrypto.Signature.recover(msg, signature, recovery_id) 96 | {:ok, <<4, 54, 241, 224, 126, 85, 135, 69, 213, 129, 115, 3, 41, 161, 217, 87, 215, 97 | 159, 64, 17, 167, 128, 113, 172, 232, 46, 34, 145, 136, 72, 160, 207, 161, 98 | 171, 255, 26, 163, 160, 158, 227, 196, 92, 62, 119, 84, 156, 99, 224, 155, 99 | 120, 250, 153, 134, 180, 218, 177, 186, 200, 199, 106, 97, 103, 50, 215, 114>>} 100 | 101 | iex> {signature, _r, _s, recovery_id} = ExthCrypto.Signature.sign_digest(msg = ExthCrypto.Math.nonce(32), ExthCrypto.Test.private_key(:key_a)) 102 | iex> ExthCrypto.Signature.recover(msg, signature, recovery_id) 103 | {:ok, <<4, 54, 241, 224, 126, 85, 135, 69, 213, 129, 115, 3, 41, 161, 217, 87, 215, 104 | 159, 64, 17, 167, 128, 113, 172, 232, 46, 34, 145, 136, 72, 160, 207, 161, 105 | 171, 255, 26, 163, 160, 158, 227, 196, 92, 62, 119, 84, 156, 99, 224, 155, 106 | 120, 250, 153, 134, 180, 218, 177, 186, 200, 199, 106, 97, 103, 50, 215, 114>>} 107 | """ 108 | @spec recover(binary(), signature, recovery_id) :: {:ok, ExthCrypto.Key.public_key} | {:error, String.t} 109 | def recover(digest, signature, recovery_id) do 110 | case :libsecp256k1.ecdsa_recover_compact(digest, signature, :uncompressed, recovery_id) do 111 | {:ok, public_key} -> {:ok, public_key} 112 | {:error, reason} -> {:error, to_string(reason)} 113 | end 114 | end 115 | 116 | end -------------------------------------------------------------------------------- /lib/test.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Test do 2 | @moduledoc """ 3 | A variety of helper functions to make the tests consistent 4 | in their usage of keys, etc. 5 | """ 6 | 7 | @public_keys %{ 8 | key_a: <<4, 54, 241, 224, 126, 85, 135, 69, 213, 129, 115, 3, 41, 161, 217, 87, 215, 9 | 159, 64, 17, 167, 128, 113, 172, 232, 46, 34, 145, 136, 72, 160, 207, 161, 10 | 171, 255, 26, 163, 160, 158, 227, 196, 92, 62, 119, 84, 156, 99, 224, 155, 11 | 120, 250, 153, 134, 180, 218, 177, 186, 200, 199, 106, 97, 103, 50, 215, 114>>, 12 | key_b: <<4, 152, 113, 235, 8, 21, 103, 130, 50, 103, 89, 42, 186, 200, 236, 158, 159, 13 | 221, 253, 236, 231, 144, 26, 21, 242, 51, 181, 63, 48, 77, 120, 96, 104, 108, 14 | 33, 96, 27, 161, 167, 245, 102, 128, 226, 45, 10, 192, 62, 204, 208, 142, 73, 15 | 100, 105, 81, 76, 37, 174, 29, 94, 85, 243, 145, 193, 149, 111>>, 16 | key_c: <<4, 146, 201, 161, 205, 19, 177, 147, 33, 107, 190, 144, 81, 145, 173, 83, 20, 17 | 105, 150, 114, 196, 249, 143, 167, 152, 63, 225, 96, 184, 86, 203, 38, 134, 18 | 241, 40, 152, 74, 34, 68, 233, 204, 91, 240, 208, 254, 62, 169, 53, 201, 248, 19 | 156, 236, 34, 203, 156, 75, 18, 121, 162, 104, 3, 164, 156, 46, 186>>, 20 | key_d: <<4, 205, 208, 163, 120, 32, 181, 60, 4, 225, 235, 149, 145, 233, 130, 114, 21 | 254, 250, 181, 166, 76, 124, 6, 241, 169, 49, 36, 251, 61, 166, 127, 63, 10, 22 | 171, 21, 243, 26, 250, 157, 62, 224, 19, 31, 127, 12, 29, 32, 146, 191, 9, 23 | 168, 124, 96, 120, 236, 233, 81, 8, 23, 61, 48, 4, 9, 213, 31>> 24 | } 25 | 26 | @private_keys %{ 27 | key_a: <<94, 217, 126, 139, 193, 247, 132, 35, 174, 8, 191, 12, 133, 229, 115, 237, 28 | 78, 81, 160, 114, 73, 118, 207, 206, 98, 114, 27, 62, 25, 29, 219, 206>>, 29 | key_b: <<226, 137, 30, 163, 26, 230, 61, 203, 158, 81, 58, 4, 197, 149, 169, 80, 34, 30 | 11, 157, 221, 132, 75, 78, 202, 254, 8, 94, 254, 229, 98, 104, 5>>, 31 | key_c: <<178, 68, 134, 194, 0, 187, 118, 35, 33, 220, 4, 3, 50, 96, 97, 91, 96, 14, 32 | 71, 239, 7, 102, 33, 187, 194, 221, 152, 36, 95, 22, 121, 48>>, 33 | key_d: <<157, 39, 212, 230, 12, 119, 100, 92, 134, 157, 151, 31, 151, 234, 63, 46, 34 | 213, 201, 34, 95, 208, 141, 55, 74, 2, 59, 121, 174, 7, 243, 230, 46>> 35 | } 36 | 37 | @symmetric_keys %{ 38 | key_a: <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 39 | 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32>>, 40 | key_b: <<11, 22, 33, 44, 55, 66, 77, 88, 99, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 41 | 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32>>, 42 | } 43 | 44 | @doc """ 45 | Returns a generic elliptic curve public key based on a given curve. 46 | """ 47 | def public_key(key \\ :key_a) do 48 | @public_keys[key] 49 | end 50 | 51 | @doc """ 52 | Returns a generic elliptic curve public key based on a given curve, 53 | which is paired with the corresponding public key. 54 | """ 55 | def private_key(key \\ :key_a) do 56 | @private_keys[key] 57 | end 58 | 59 | @doc """ 60 | Returns a generic elliptic curve key pair. 61 | """ 62 | def key_pair(key \\ :key_a) do 63 | {@public_keys[key], @private_keys[key]} 64 | end 65 | 66 | def symmetric_key(key \\ :key_a) do 67 | @symmetric_keys[key] 68 | end 69 | 70 | @doc """ 71 | Returns an initialization vector of the given size that is specifically 72 | not random at all (just for testing). 73 | """ 74 | def init_vector(base \\ 1, block_size \\ 16) do 75 | for i <- base..(base + block_size - 1), do: <>, into: <<>> 76 | end 77 | end -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :exth_crypto, 6 | version: "0.1.6", 7 | elixir: "~> 1.4", 8 | description: "Exthereum's Crypto Suite.", 9 | package: [ 10 | maintainers: ["Geoffrey Hayes", "Mason Fischer"], 11 | licenses: ["MIT"], 12 | links: %{"GitHub" => "https://github.com/exthereum/exth_crypto"} 13 | ], 14 | build_embedded: Mix.env == :prod, 15 | start_permanent: Mix.env == :prod, 16 | deps: deps()] 17 | end 18 | 19 | # Configuration for the OTP application 20 | # 21 | # Type "mix help compile.app" for more information 22 | def application do 23 | # Specify extra applications you'll use from Erlang/Elixir 24 | [extra_applications: [:logger]] 25 | end 26 | 27 | # Dependencies can be Hex packages: 28 | # 29 | # {:my_dep, "~> 0.3.0"} 30 | # 31 | # Or git/path repositories: 32 | # 33 | # {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 34 | # 35 | # Type "mix help deps" for more examples and options 36 | defp deps do 37 | [ 38 | {:libsecp256k1, "~> 0.1.9"}, 39 | {:keccakf1600, "~> 2.0.0", hex: :keccakf1600_orig}, 40 | {:credo, "~> 0.8", only: [:dev, :test], runtime: false}, 41 | {:ex_doc, "~> 0.17", only: :dev, runtime: false}, 42 | {:dialyxir, "~> 0.5", only: [:dev], runtime: false}, 43 | {:binary, "~> 0.0.4"}, 44 | ] 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "binary": {:hex, :binary, "0.0.4", "dd077db70c0ded3e85c132b802338e14b80694684a7e2277666bfa4004b7fa66", [:mix], [], "hexpm"}, 3 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, 4 | "credo": {:hex, :credo, "0.8.6", "335f723772d35da499b5ebfdaf6b426bfb73590b6fcbc8908d476b75f8cbca3f", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, 6 | "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, 7 | "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "keccakf1600": {:hex, :keccakf1600_orig, "2.0.0", "0a7217ddb3ee8220d449bbf7575ec39d4e967099f220a91e3dfca4dbaef91963", [:rebar3], [], "hexpm"}, 9 | "libsecp256k1": {:hex, :libsecp256k1, "0.1.9", "e725f31364cda7b554d56ce2bb976241303dde5ffd1ad59598513297bf1f2af6", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm"}, 10 | "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 12 | "mix_erlang_tasks": {:hex, :mix_erlang_tasks, "0.1.0", "36819fec60b80689eb1380938675af215565a89320a9e29c72c70d97512e4649", [:mix], [], "hexpm"}, 13 | "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, 14 | } 15 | -------------------------------------------------------------------------------- /test/aes/aes_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.AESTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.AES 4 | 5 | end -------------------------------------------------------------------------------- /test/cipher_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.CipherTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Cipher 4 | 5 | end -------------------------------------------------------------------------------- /test/ecies/ecdh_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ECDHTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES.ECDH 4 | 5 | end -------------------------------------------------------------------------------- /test/ecies/ecies_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIESTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES 4 | 5 | end -------------------------------------------------------------------------------- /test/ecies/parameters_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ParametersTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES.Parameters 4 | 5 | end -------------------------------------------------------------------------------- /test/exth_crypto_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCryptoTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/hash/fake_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.FakeTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.Fake 4 | 5 | end -------------------------------------------------------------------------------- /test/hash/keccak_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.KeccakTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.Keccak 4 | 5 | end -------------------------------------------------------------------------------- /test/hash/sha_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.SHATest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.SHA 4 | 5 | end -------------------------------------------------------------------------------- /test/hash_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.HashTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash 4 | 5 | end -------------------------------------------------------------------------------- /test/kdf/nist_sp_800_65_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.KDF.NistSp80056Test do 2 | use ExUnit.Case 3 | doctest ExthCrypto.KDF.NistSp80056 4 | 5 | end -------------------------------------------------------------------------------- /test/mac/mac_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MACTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.MAC 4 | 5 | end -------------------------------------------------------------------------------- /test/math/math_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MathTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Math 4 | 5 | end -------------------------------------------------------------------------------- /test/signature/signature_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.SignatureTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Signature 4 | 5 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------