Elixir library to enable Sign-In with Ethereum message validation.
Full documentation found at https://hexdocs.pm/siwe.
This library provides functions for parsing and validating SIWE message strings and their corresponding signatures.
Elixir at version 1.10
or higher using OTP 23 or greater. Rustler officially supports the last 3 minor versions of Elixir, which we also suggest for best experience.
A Rust compiler at version 1.56
or higher so that 2021 edition libraries can be compiled.
The package can be installed by adding siwe
to your list of dependencies in mix.exs
:
def deps do
[
{:siwe, "~> 0.3"}
]
end
To see how this works in iex
, clone this repository and from the root run:
$ mix deps.get
Then create two files
message.txt
:
login.xyz wants you to sign in with your Ethereum account:
0xfA151B5453CE69ABf60f0dbdE71F6C9C5868800E
Sign-In With Ethereum Example Statement
URI: https://login.xyz
Version: 1
Chain ID: 1
Nonce: ToTaLLyRanDOM
Issued At: 2021-12-17T00:38:39.834Z
signature.txt
:
0x8d1327a1abbdf172875e5be41706c50fc3bede8af363b67aefbb543d6d082fb76a22057d7cb6d668ceba883f7d70ab7f1dc015b76b51d226af9d610fa20360ad1c
then run
$ iex -S mix
Once in iex, you can then run the following to see the result:
iex> {:ok, msg} = File.read("./message.txt")
...
iex> {:ok, sig} = File.read("./signature.txt")
...
iex> Siwe.parse_if_valid(String.trim(msg), String.trim(sig))
{:ok, %{
__struct__: Siwe,
address: "0xfA151B5453CE69ABf60f0dbdE71F6C9C5868800E",
chain_id: "1",
domain: "login.xyz",
expiration_time: nil,
issued_at: "2021-12-17T00:38:39.834Z",
nonce: "ToTaLLyRanDOM",
not_before: nil,
request_id: nil,
resources: [],
statement: "Sign-In With Ethereum Example Statement",
uri: "https://login.xyz",
version: "1"
}}
Any valid SIWE message and signature pair can be substituted.The functions described below can also be tested with msg
, sig
, or a value set to the result Siwe.parse_if_valid
.
This library deals with three different types of input:
- SIWE message strings.
- Signatures of SIWE message strings.
- A parsed SIWE message which is defined as:
defmodule Message do
defstruct domain: "",
address: "",
statement: "",
uri: "",
version: "",
chain_id: "",
nonce: "",
issued_at: "",
expiration_time: nil, # or a string datetime
not_before: nil, # or a string datetime
request_id: nil, # or string
resources: []
end
The most basic functions are parse
and to_str
which translate a SIWE message string to a parsed SIWE message and back (respectively). To simplify using the variables from the above example:
iex> {:ok, parsed} = Siwe.parse(String.trim(msg))
...
iex> {:ok, str2} = Siwe.to_str(parsed)
iex> str2 == String.trim(msg)
:true
Once parsed, the Message
can be verified.
-
verify_sig
takes theMessage
and a correspondingsignature
and returns true if theMessage
'saddress
field would produce thesignature
if it had signed theMessage
's string form. -
verify
returns true ifverify_sig
would and current time is after theMessage
'snot_before
field (if it exists) and before theMessage
'sexpiration_time
field (if it exists). Three optional string parameters can be passed toverify
:domain_binding
, which Message.domain must match to pass verificationmatch_nonce
, which Message.nonce must match to pass verificationtimestamp
, which will instead verify the message at that point in time
iex> Siwe.verify_sig(parsed, String.trim(sig))
:true
iex> Siwe.verify(parsed, String.trim(sig), "login.xyz", nil, nil)
:true
iex> Siwe.verify(parsed, String.trim(sig), nil, "12341234", nil)
:true
iex> Siwe.verify(parsed, String.trim(sig), nil, nil, "2021-04-04T00:38:39.834Z")
:true
iex> Siwe.verify(parsed, String.trim(sig), nil, nil, nil)
:true
parse_if_valid
is an optimized helper function which takes a SIWE message string and a signature
then returns a parsed Message
only if the signature
matches and the current time is after the Message
's not_before
field (if it exists) and before the Message
's expiration_time
field (if it exists).
iex> Siwe.generate_nonce()
"EaLc76FkngQ"
Another helper, generate_nonce
function is provided to create alphanumeric [a-z, A-Z, 1-9] nonces compliant with SIWE's spec, used like so:
This is useful for servers using SIWE for their own authentication systems.
Our Elixir library for Sign-In with Ethereum has not yet undergone a formal security audit. We welcome continued feedback on the usability, architecture, and security of this implementation.