siwe 0.1.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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