siwe 0.1.5 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6672768ccc989b67fd335a571585a7bbae2f88ccd8c095f7332a308f39037dab
4
- data.tar.gz: 3612d9b498a36fa37e0cb70e81e2ae64450baf34d7ef9d75cc666957957c15de
3
+ metadata.gz: 1b93dcc0ece62bd6bdd3316e51661198c5b4f4b5a8433c355bcae8a51997b183
4
+ data.tar.gz: f5b5720ea3a8de09d52f1896b6371159c98bad1385e8ab8dae2420e037e7e6bc
5
5
  SHA512:
6
- metadata.gz: 6f42e272519ac16310a8ee9bf75139e121acaf52b446b0ed35e8868bb17bfcb816caced089dc7e342e5a686a1ab532cab6a9d385a3b80cbc7b6a15c668e019aa
7
- data.tar.gz: 93c9e1a87a4dae94562f89b44de590f03a23c673dfb30962f2cd47c52aa4547f3b8c0a0b0a3263773b4113783dcfcd97154de7d9401b689935dfa3da671b001a
6
+ metadata.gz: 02626d246f7d5cfb189057feb2619cdcaaf0271d52c70be86f77e9c04ef214974ca2ce9506565a2f9c927bcceb2dede81e9c2e7c57b629312c0e966bf110d5e4
7
+ data.tar.gz: 3bd251a0027c2cb831d90e6fad7b920731c5f19553472ce1219216daac97727e7bf939fc859a994fc9eeed672789ed67af0d0c23f737b95334d8446d3141fc13
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- siwe (0.1.4)
5
- eth (~> 0.5.0)
4
+ siwe (1.1.0)
5
+ eth (~> 0.5.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
@@ -12,17 +12,17 @@ GEM
12
12
  benchmark (0.2.0)
13
13
  diff-lcs (1.5.0)
14
14
  e2mmap (0.1.0)
15
- eth (0.5.0)
15
+ eth (0.5.1)
16
16
  keccak (~> 1.3)
17
17
  konstructor (~> 1.0)
18
- openssl (~> 3.0)
18
+ openssl (~> 2.2)
19
19
  rbsecp256k1 (~> 5.1)
20
- rlp (~> 0.7)
21
20
  scrypt (~> 3.0)
22
21
  ffi (1.15.5)
23
22
  ffi-compiler (1.0.1)
24
23
  ffi (>= 1.0.0)
25
24
  rake
25
+ ipaddr (1.2.4)
26
26
  jaro_winkler (1.5.4)
27
27
  keccak (1.3.0)
28
28
  konstructor (1.0.2)
@@ -33,7 +33,8 @@ GEM
33
33
  mini_portile2 (2.7.1)
34
34
  nokogiri (1.13.1-x86_64-linux)
35
35
  racc (~> 1.4)
36
- openssl (3.0.0)
36
+ openssl (2.2.1)
37
+ ipaddr
37
38
  parallel (1.21.0)
38
39
  parser (3.1.0.0)
39
40
  ast (~> 2.4.1)
@@ -49,7 +50,6 @@ GEM
49
50
  reverse_markdown (2.1.1)
50
51
  nokogiri
51
52
  rexml (3.2.5)
52
- rlp (0.7.3)
53
53
  rspec (3.10.0)
54
54
  rspec-core (~> 3.10.0)
55
55
  rspec-expectations (~> 3.10.0)
data/README.md CHANGED
@@ -1,2 +1,107 @@
1
1
  # siwe-ruby
2
- A ruby implementation of Sign-In with Ethereum
2
+ A Ruby implementation of EIP-4361: Sign In With Ethereum.
3
+
4
+ ## Getting started
5
+ ### Dependencies
6
+ Additional packages may be required to install the gem:
7
+
8
+ ### macOS
9
+ ```bash
10
+ brew install automake openssl libtool pkg-config gmp libffi
11
+ ```
12
+
13
+ ### Linux
14
+ ```bash
15
+ sudo apt-get install build-essential automake pkg-config libtool \
16
+ libffi-dev libssl-dev libgmp-dev python-dev
17
+ ```
18
+
19
+ After installing any required dependencies SIWE can be easily installed with:
20
+ ```bash
21
+ gem install siwe
22
+ ```
23
+
24
+ ## Usage
25
+ SIWE provides a Message class which implements EIP-4361.
26
+ ### Creating a SIWE Message
27
+
28
+ ```ruby
29
+ require 'siwe'
30
+ require 'time'
31
+
32
+ # Only the mandatory arguments
33
+ Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1")
34
+
35
+ # Complete SIWE message with default values
36
+ Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1", {
37
+ issued_at: Time.now.utc.iso8601,
38
+ statement: "Example statement for SIWE",
39
+ nonce: Siwe::Util.generate_nonce,
40
+ chain_id: "1",
41
+ expiration_time: "",
42
+ not_before: "",
43
+ request_id: "",
44
+ resources: []
45
+ })
46
+ ```
47
+
48
+ ### Parsing a SIWE Message
49
+ To parse from EIP-4361 use `Siwe::Message.from_message`
50
+
51
+ ```ruby
52
+ require 'siwe'
53
+
54
+ Siwe::Message.from_message "domain.example wants you to sign in with your Ethereum account:\n0x9D85ca56217D2bb651b00f15e694EB7E713637D4\n\nExample statement for SIWE\n\nURI: some.uri\nVersion: 1\nChain ID: 1\nNonce: k1Ne4KWzBHYEFQo8\nIssued At: 2022-02-03T20:06:19Z"
55
+ ```
56
+
57
+ Messages can be parsed to and from JSON strings, using Siwe::Message.from_json_string and Siwe::Message.to_json_string respectively:
58
+
59
+ ```ruby
60
+ require 'siwe'
61
+
62
+ Siwe::Message.from_json_string "{\"domain\":\"domain.example\",\"address\":\"0x9D85ca56217D2bb651b00f15e694EB7E713637D4\",\"uri\":\"some.uri\",\"version\":\"1\",\"chain_id\":\"1\",\"nonce\":\"k1Ne4KWzBHYEFQo8\",\"issued_at\":\"2022-02-03T20:06:19Z\",\"statement\":\"Example statement for SIWE\",\"expiration_time\":\"\",\"not_before\":\"\",\"request_id\":\"\",\"resources\":[]}"
63
+
64
+ Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1").to_json_string
65
+ ```
66
+
67
+ ## Verifying and Authenticating a SIWE Message
68
+ Verification and authentication is performed via EIP-191, using the address field of the SiweMessage as the expected signer. The validate method checks message structural integrity, signature address validity, and time-based validity attributes.
69
+
70
+ ```ruby
71
+ begin
72
+ message.validate(signature) # returns true if valid throws otherwise
73
+ rescue Siwe::ExpiredMessage
74
+ # Used when the message is already expired. (Expires At < Time.now)
75
+ rescue Siwe::NotValidMessage
76
+ # Used when the message is not yet valid. (Not Before > Time.now)
77
+ rescue Siwe::InvalidSignature
78
+ # Used when the signature doesn't correspond to the address of the message.
79
+ end
80
+ ```
81
+
82
+ ## Serialization of a SIWE Message
83
+ `Siwe::Message` instances can also be serialized as their EIP-4361 string representations via the `Siwe::Message.prepare_message` method:
84
+
85
+ ```ruby
86
+ require 'siwe'
87
+
88
+ Siwe::Message.new("domain.example", "0x9D85ca56217D2bb651b00f15e694EB7E713637D4", "some.uri", "1").prepare_message
89
+ ```
90
+
91
+ ## Example
92
+ Parsing and verifying a `Siwe::Message`:
93
+ ```ruby
94
+ require 'siwe'
95
+
96
+ begin
97
+ message = Siwe::Message.from_message "https://example.com wants you to sign in with your Ethereum account:\n0xA712a0AFBFA8656581BfA96352c9EdFc519e9cad\n\n\nURI: https://example.com\nVersion: 1\nChain ID: 1\nNonce: 9WrH24z8zpiYOoBQ\nIssued At: 2022-02-04T15:52:03Z"
98
+ message.validate "aca5e5649a357cee608ecbd1a8455b4143311381636b88a66ec7bcaf64b3a4743ff2c7cc18501a3401e182f79233dc73fc56d01506a6098d5e7e4d881bbb02921c"
99
+ puts "Congrats, your message is valid"
100
+ rescue Siwe::ExpiredMessage
101
+ # Used when the message is already expired. (Expires At < Time.now)
102
+ rescue Siwe::NotValidMessage
103
+ # Used when the message is not yet valid. (Not Before > Time.now)
104
+ rescue Siwe::InvalidSignature
105
+ # Used when the signature doesn't correspond to the address of the message.
106
+ end
107
+ ```
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Siwe
4
+ # Used when the message is already expired. (Expires At < Time.now)
5
+ class ExpiredMessage < StandardError
6
+ def initialize(msg = "Message expired.")
7
+ super
8
+ end
9
+ end
10
+
11
+ # Used when the address does not conform to EIP-55 or is invalid.
12
+ class InvalidAddress < StandardError
13
+ def initialize(msg = "Address does not conform to EIP-55 or is invalid.")
14
+ super
15
+ end
16
+ end
17
+
18
+ # Used when the message is not yet valid. (Not Before > Time.now)
19
+ class NotValidMessage < StandardError
20
+ def initialize(msg = "Message not yet valid.")
21
+ super
22
+ end
23
+ end
24
+
25
+ # Used when the signature doesn't correspond to the address of the message.
26
+ class InvalidSignature < StandardError
27
+ def initialize(msg = "Signature doesn't match message.")
28
+ super
29
+ end
30
+ end
31
+ end
data/lib/siwe/message.rb CHANGED
@@ -74,15 +74,17 @@ module Siwe
74
74
  # expressed as RFC 3986 URIs separated by `\n- `.
75
75
  attr_accessor :resources
76
76
 
77
- # Signature of the message signed by the wallet.
78
- attr_accessor :signature
79
-
80
77
  def initialize(domain, address, uri, version, options = {})
81
78
  @domain = domain
82
- @address = address
79
+ begin
80
+ @address = Eth::Address.new(address).to_s
81
+ rescue StandardError
82
+ raise Siwe::InvalidAddress
83
+ end
84
+ raise Siwe::InvalidAddress unless @address.eql? address
85
+
83
86
  @uri = uri
84
87
  @version = version
85
-
86
88
  @statement = options.fetch :statement, ""
87
89
  @issued_at = options.fetch :issued_at, Time.now.utc.iso8601
88
90
  @nonce = options.fetch :nonce, Siwe::Util.generate_nonce
@@ -91,15 +93,13 @@ module Siwe
91
93
  @not_before = options.fetch :not_before, ""
92
94
  @request_id = options.fetch :request_id, ""
93
95
  @resources = options.fetch :resources, []
94
- @signature = options.fetch :signature, ""
95
- validate(true)
96
96
  end
97
97
 
98
98
  def self.from_message(msg)
99
99
  if (message = msg.match SIWE_MESSAGE)
100
- msg = new(
100
+ new(
101
101
  message[:domain],
102
- message[:address],
102
+ Eth::Address.new(message[:address]).to_s,
103
103
  message[:uri],
104
104
  message[:version],
105
105
  {
@@ -113,8 +113,7 @@ module Siwe
113
113
  resources: message[:resources]&.split("\n- ")&.drop(1) || []
114
114
  }
115
115
  )
116
- msg.validate(true)
117
- msg
116
+
118
117
  else
119
118
  throw "Invalid message input."
120
119
  end
@@ -123,7 +122,7 @@ module Siwe
123
122
  def to_json_string
124
123
  obj = {
125
124
  domain: @domain,
126
- address: @address,
125
+ address: Eth::Address.new(@address).to_s,
127
126
  uri: @uri,
128
127
  version: @version,
129
128
  chain_id: @chain_id,
@@ -133,15 +132,14 @@ module Siwe
133
132
  expiration_time: @expiration_time,
134
133
  not_before: @not_before,
135
134
  request_id: @request_id,
136
- resources: @resources,
137
- signature: @signature
135
+ resources: @resources
138
136
  }
139
137
  obj.to_json
140
138
  end
141
139
 
142
140
  def self.from_json_string(str)
143
141
  obj = JSON.parse str, { symbolize_names: true }
144
- msg = Siwe::Message.new(
142
+ Siwe::Message.new(
145
143
  obj[:domain],
146
144
  obj[:address],
147
145
  obj[:uri],
@@ -153,30 +151,33 @@ module Siwe
153
151
  expiration_time: obj[:expiration_time],
154
152
  not_before: obj[:not_before],
155
153
  request_id: obj[:request_id],
156
- resources: obj[:resources],
157
- signature: obj[:signature]
154
+ resources: obj[:resources]
158
155
  }
159
156
  )
160
- msg.validate(true)
161
- msg
162
157
  end
163
158
 
164
- def validate(skip_signature = false)
165
- raise "Message expired." if !@expiration_time.empty? && Time.now.utc > Time.parse(@expiration_time)
166
- raise "Message not yet valid." if !@not_before.empty? && Time.now.utc < Time.parse(@not_before)
159
+ def validate(signature)
160
+ raise Siwe::ExpiredMessage if !@expiration_time.empty? && Time.now.utc > Time.parse(@expiration_time)
161
+ raise Siwe::NotValidMessage if !@not_before.empty? && Time.now.utc < Time.parse(@not_before)
162
+
163
+ raise Siwe::InvalidSignature if signature.empty?
167
164
 
168
- unless skip_signature
169
- raise "Missing signature field." if @signature.empty?
165
+ raise Siwe::InvalidAddress unless @address.eql?(Eth::Address.new(@address).to_s)
170
166
 
171
- pub_key = Eth::Signature.personal_recover personal_sign, @signature
167
+ puts "whatever"
168
+ begin
169
+ pub_key = Eth::Signature.personal_recover prepare_message, signature
172
170
  signature_address = Eth::Util.public_key_to_address pub_key
173
- raise "Signature doesn't match message." unless signature_address.to_s.downcase.eql? @address.to_s.downcase
171
+ rescue StandardError
172
+ raise Siwe::InvalidSignature
174
173
  end
175
174
 
175
+ raise Siwe::InvalidSignature unless signature_address.to_s.downcase.eql? @address.to_s.downcase
176
+
176
177
  true
177
178
  end
178
179
 
179
- def personal_sign
180
+ def prepare_message
180
181
  greeting = "#{@domain} wants you to sign in with your Ethereum account:"
181
182
  address = @address
182
183
  statement = "\n#{@statement}\n"
data/lib/siwe/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Siwe
4
- VERSION = "0.1.5"
4
+ VERSION = "1.1.1"
5
5
  end
data/lib/siwe.rb CHANGED
@@ -6,6 +6,10 @@ require_relative "siwe/version"
6
6
  module Siwe
7
7
  autoload :Message, "siwe/message"
8
8
  autoload :Util, "siwe/util"
9
+ autoload :ExpiredMessage, "siwe/exceptions"
10
+ autoload :NotValidMessage, "siwe/exceptions"
11
+ autoload :InvalidSignature, "siwe/exceptions"
12
+ autoload :InvalidAddress, "siwe/exceptions"
9
13
 
10
14
  class Error < StandardError; end
11
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: siwe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Spruce Systems Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-24 00:00:00.000000000 Z
11
+ date: 2022-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: eth
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.5.0
19
+ version: 0.5.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.5.0
26
+ version: 0.5.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -101,6 +101,7 @@ files:
101
101
  - bin/console
102
102
  - bin/setup
103
103
  - lib/siwe.rb
104
+ - lib/siwe/exceptions.rb
104
105
  - lib/siwe/message.rb
105
106
  - lib/siwe/util.rb
106
107
  - lib/siwe/version.rb