siwe 0.1.4 → 1.1.0

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: 3e8191a3015440c3051896d22edba61a3b9f79d87930ec852e294e020afd29d2
4
- data.tar.gz: 5a015d2ed004f3e60b84218167bc9759228de7e0cae49c28eac076c6e2e397bd
3
+ metadata.gz: 9ce4aaa56993884786c2eb990729406a1ae4e0cb99d43f5dbcc6b3e0c8e0af67
4
+ data.tar.gz: 4dbcb76592a3c0bd4eed2e53e3a739fe9461f185772fdb70fa06dfcd43f7adec
5
5
  SHA512:
6
- metadata.gz: 228890a1cd243da5dec618b5a933b911058775fec4a9a0e90266c8a39ec7f80904a11487a810d6aae54badffcde6a09567ec5487f131fb0b3480348d427ece48
7
- data.tar.gz: d880c083a7c09042c5b8d1b1a9b9faec683475666f4fe749c78bd25b7d3c2891c0c577eb815b166178461cd6aad0f32f4ad8cc60e2a048649ac3125e8fa5f6cc
6
+ metadata.gz: fb5e213eb8d8f50f6cf9a4c00be528bd0a5a9afdc806f95d2714534f629f3ea538bc79655330fc313b01ff85f7ac93550f2447c84baa1f5996eaf6617543630c
7
+ data.tar.gz: ddf5168c793f6342f9f2e49ac8112dca937832fd7e6a42fb8362250cb6c8798ddb26bf444514064f25fa29ea09ad06d1827cdfc5a1ba696667448d04eb05eee6
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
2
+ TargetRubyVersion: 2.5
3
3
  NewCops: enable
4
4
  SuggestExtensions: false
5
5
 
@@ -33,4 +33,4 @@ Style/OptionalBooleanParameter:
33
33
  Enabled: false
34
34
 
35
35
  Layout/LineLength:
36
- Max: 120
36
+ Max: 120
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- siwe (0.1.3)
5
- eth (~> 0.4.17)
4
+ siwe (1.0.0)
5
+ eth (~> 0.5.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
@@ -10,67 +10,75 @@ GEM
10
10
  ast (2.4.2)
11
11
  backport (1.2.0)
12
12
  benchmark (0.2.0)
13
- diff-lcs (1.4.4)
13
+ diff-lcs (1.5.0)
14
14
  e2mmap (0.1.0)
15
- eth (0.4.17)
16
- ffi (~> 1.15)
15
+ eth (0.5.1)
17
16
  keccak (~> 1.3)
18
- money-tree (~> 0.10)
19
- rlp (~> 0.7)
17
+ konstructor (~> 1.0)
18
+ openssl (~> 2.2)
19
+ rbsecp256k1 (~> 5.1)
20
20
  scrypt (~> 3.0)
21
- ffi (1.15.4)
21
+ ffi (1.15.5)
22
22
  ffi-compiler (1.0.1)
23
23
  ffi (>= 1.0.0)
24
24
  rake
25
+ ipaddr (1.2.3)
25
26
  jaro_winkler (1.5.4)
26
27
  keccak (1.3.0)
28
+ konstructor (1.0.2)
27
29
  kramdown (2.3.1)
28
30
  rexml
29
31
  kramdown-parser-gfm (1.1.0)
30
32
  kramdown (~> 2.0)
31
- money-tree (0.10.0)
32
- ffi
33
- nokogiri (1.12.5-x86_64-linux)
33
+ mini_portile2 (2.7.1)
34
+ nokogiri (1.13.1-x86_64-linux)
34
35
  racc (~> 1.4)
36
+ openssl (2.2.1)
37
+ ipaddr
35
38
  parallel (1.21.0)
36
- parser (3.0.3.1)
39
+ parser (3.1.0.0)
37
40
  ast (~> 2.4.1)
41
+ pkg-config (1.4.7)
38
42
  racc (1.6.0)
39
- rainbow (3.0.0)
43
+ rainbow (3.1.1)
40
44
  rake (13.0.6)
41
- regexp_parser (2.1.1)
45
+ rbsecp256k1 (5.1.0)
46
+ mini_portile2 (~> 2.7)
47
+ pkg-config (~> 1.4)
48
+ rubyzip (~> 2.3)
49
+ regexp_parser (2.2.0)
42
50
  reverse_markdown (2.1.1)
43
51
  nokogiri
44
52
  rexml (3.2.5)
45
- rlp (0.7.3)
46
53
  rspec (3.10.0)
47
54
  rspec-core (~> 3.10.0)
48
55
  rspec-expectations (~> 3.10.0)
49
56
  rspec-mocks (~> 3.10.0)
50
57
  rspec-core (3.10.1)
51
58
  rspec-support (~> 3.10.0)
52
- rspec-expectations (3.10.1)
59
+ rspec-expectations (3.10.2)
53
60
  diff-lcs (>= 1.2.0, < 2.0)
54
61
  rspec-support (~> 3.10.0)
55
62
  rspec-mocks (3.10.2)
56
63
  diff-lcs (>= 1.2.0, < 2.0)
57
64
  rspec-support (~> 3.10.0)
58
65
  rspec-support (3.10.3)
59
- rubocop (1.23.0)
66
+ rubocop (1.25.0)
60
67
  parallel (~> 1.10)
61
- parser (>= 3.0.0.0)
68
+ parser (>= 3.1.0.0)
62
69
  rainbow (>= 2.2.2, < 4.0)
63
70
  regexp_parser (>= 1.8, < 3.0)
64
71
  rexml
65
- rubocop-ast (>= 1.12.0, < 2.0)
72
+ rubocop-ast (>= 1.15.1, < 2.0)
66
73
  ruby-progressbar (~> 1.7)
67
74
  unicode-display_width (>= 1.4.0, < 3.0)
68
- rubocop-ast (1.13.0)
75
+ rubocop-ast (1.15.1)
69
76
  parser (>= 3.0.1.1)
70
77
  ruby-progressbar (1.11.0)
78
+ rubyzip (2.3.2)
71
79
  scrypt (3.0.7)
72
80
  ffi-compiler (>= 1.0, < 2.0)
73
- solargraph (0.44.2)
81
+ solargraph (0.44.3)
74
82
  backport (~> 1.2)
75
83
  benchmark
76
84
  bundler (>= 1.17.2)
@@ -85,7 +93,7 @@ GEM
85
93
  thor (~> 1.0)
86
94
  tilt (~> 2.0)
87
95
  yard (~> 0.9, >= 0.9.24)
88
- thor (1.1.0)
96
+ thor (1.2.1)
89
97
  tilt (2.0.10)
90
98
  unicode-display_width (2.1.0)
91
99
  webrick (1.7.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 = "Adress 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,30 @@ 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)
167
162
 
168
- unless skip_signature
169
- raise "Missing signature field." if @signature.empty?
163
+ raise Siwe::InvalidSignature if signature.empty?
170
164
 
171
- pub_key = Eth::Key.personal_recover personal_sign, @signature
172
- signature_address = Eth::Utils.public_key_to_address pub_key
173
- raise "Signature doesn't match message." unless signature_address.downcase.eql? @address.downcase
165
+ begin
166
+ pub_key = Eth::Signature.personal_recover prepare_message, signature
167
+ signature_address = Eth::Util.public_key_to_address pub_key
168
+ rescue StandardError
169
+ raise Siwe::InvalidSignature
174
170
  end
175
171
 
172
+ raise Siwe::InvalidSignature unless signature_address.to_s.downcase.eql? @address.to_s.downcase
173
+
176
174
  true
177
175
  end
178
176
 
179
- def personal_sign
177
+ def prepare_message
180
178
  greeting = "#{@domain} wants you to sign in with your Ethereum account:"
181
179
  address = @address
182
180
  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.4"
4
+ VERSION = "1.1.0"
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.4
4
+ version: 1.1.0
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: 2021-12-09 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.4.17
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.4.17
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
@@ -117,7 +118,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
118
  requirements:
118
119
  - - ">="
119
120
  - !ruby/object:Gem::Version
120
- version: 2.6.0
121
+ version: '2.5'
121
122
  required_rubygems_version: !ruby/object:Gem::Requirement
122
123
  requirements:
123
124
  - - ">="