Coder Social home page Coder Social logo

siwe-ex's Introduction

Sign-In with Ethereum

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.

Requirements:

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.

Installation

The package can be installed by adding siwe to your list of dependencies in mix.exs:

def deps do
  [
    {:siwe, "~> 0.3"}
  ]
end

Example

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.

API Overview

This library deals with three different types of input:

  1. SIWE message strings.
  2. Signatures of SIWE message strings.
  3. 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 the Message and a corresponding signature and returns true if the Message's address field would produce the signature if it had signed the Message's string form.

  • verify returns true if verify_sig would and current time is after the Message's not_before field (if it exists) and before the Message's expiration_time field (if it exists). Three optional string parameters can be passed to verify:

    • domain_binding, which Message.domain must match to pass verification
    • match_nonce, which Message.nonce must match to pass verification
    • timestamp, 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.

Disclaimer

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.

See Also

siwe-ex's People

Contributors

krhoda avatar obstropolos avatar theosirian avatar

Stargazers

Marko Vukovic avatar Xi avatar ERKANSKG avatar Theesit Konkaew avatar Jelle van der Ploeg avatar Karlo Smid avatar Conrad Steenberg avatar Francesco Ceccon avatar Alejandro Baez avatar Martin Rosenberg avatar Lucas Alves avatar  avatar

Watchers

Camilo Hollanda avatar wyc avatar  avatar  avatar  avatar

siwe-ex's Issues

Struct Siwe.Message does not match NifStruct Parsed module name

The Siwe.Message struct does not match the internal NifStruct module name (Siwe instead of Siwe.Message):

#[derive(NifStruct)]
#[module = "Siwe"]
pub struct Parsed {
pub domain: String,
pub address: String,
pub statement: Option<String>,
pub uri: String,
pub version: String,
pub chain_id: u64,
pub nonce: String,
pub issued_at: String,
pub expiration_time: Option<String>,
pub not_before: Option<String>,
pub request_id: Option<String>,
pub resources: Vec<String>,
}

This leads to an :invalid_struct error when passing a Siwe.Message to NIF functions (to_str/1, verify/5, verify_sig/2):

iex(2)> %Siwe.Message{} |> Siwe.to_str()
:invalid_struct

This could happen when generating a Siwe.Message manually instead of parsing a string. Here's a basic example that returns a Siwe.Message in a Phoenix environment:

  def generate_sign_in_message(address) do
    %Siwe.Message{
      address: address,
      chain_id: 1,
      domain: MyAppWeb.Endpoint.host(),
      issued_at: DateTime.utc_now() |> DateTime.to_iso8601(),
      nonce: Siwe.generate_nonce(),
      statement: "Sign-In With Ethereum",
      uri: MyAppWeb.Endpoint.url(),
      version: "1"
    }
  end

Currently, in order to use generate_sign_in_message, one must change the internal :__struct__ field as follow:

message = generate_sign_in_message(address) |> Map.put(:__struct__, Siwe)

Rust version

Can you clarify the rust version needed? Tried to compile it worked with 1.56.0 but not 1.55.0.
Is that correct?

Remove Rust dependency by providing native Elixir implementation

Hi, I'm interested in using this library in a self-hosted open source project.

The extra dependency on Rust makes this a problem since it would break existing installations and make it harder for new users to install.

I'm wondering if it would be feasible to implement this natively in Elixir.

Parsing/Validation and Verification

We should make sure the terms validation and verification are used consistently. We should validate a SIWE message when it is parsed or created. Validation includes schema validation and making sure the message complies with EIP-4361 spec. Verification means the EIP-191 signature is correct and is verified against a given optional domain, timestamp, nonce etc.

For this I propose, we do the following for validation:

  • SIWE message complies with the SIWE ABNF in general
  • SIWE message contains valid types such as for address (EIP-55), authority (see RFC/EIP), Resources (should be URIs) etc.
  • SIWE has all mandatory parameters (e.g., domain, address, issued at)
  • SIWE only uses accepted white spaces (LF)
  • Parameters in SIWE message are correctly ordered (this is required by the ABNF)
    As a result of the validation above, it should not be possible to get a SIWE message that is invalid.

Then verification includes the following:

  • verifying the EIP-191 signature
  • verifying that expiration time is < date.now() + some minimal clock skew
  • verifying that expected nonce and domain are in the message (for this, a server would need to have a configuration for domain and has to remember issued nonces which should also expire individually -> note, if a server issues a nonce it is still up to the frontend WHEN to create the SIWE message which can use a different issued at and a longer expiration time).
  • verifying not before date < date.now()

Hex package does not contain native directory

It looks like the native directory is not being uploaded to hex.pm.

The only way I can get it is if I specify the github repo ({:siwe, "~> 0.3.2", git: "https://github.com/spruceid/siwe-ex", branch: "main"}, and then mix will download the dep directly from github.

As per documentation https://hex.pm/docs/publish it looks like it is not possible to use git submodules.

But some other libs are doing it.

It may be needed to specify the files, like here:

https://github.com/omgnetwork/ex_secp256k1/blob/master/mix.exs

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.