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 +4 -4
- data/Gemfile.lock +7 -7
- data/README.md +106 -1
- data/lib/siwe/exceptions.rb +31 -0
- data/lib/siwe/message.rb +28 -27
- data/lib/siwe/version.rb +1 -1
- data/lib/siwe.rb +4 -0
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b93dcc0ece62bd6bdd3316e51661198c5b4f4b5a8433c355bcae8a51997b183
|
|
4
|
+
data.tar.gz: f5b5720ea3a8de09d52f1896b6371159c98bad1385e8ab8dae2420e037e7e6bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 (
|
|
5
|
-
eth (~> 0.5.
|
|
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.
|
|
15
|
+
eth (0.5.1)
|
|
16
16
|
keccak (~> 1.3)
|
|
17
17
|
konstructor (~> 1.0)
|
|
18
|
-
openssl (~>
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
165
|
-
raise
|
|
166
|
-
raise
|
|
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
|
|
169
|
-
raise "Missing signature field." if @signature.empty?
|
|
165
|
+
raise Siwe::InvalidAddress unless @address.eql?(Eth::Address.new(@address).to_s)
|
|
170
166
|
|
|
171
|
-
|
|
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
|
-
|
|
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
|
|
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
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:
|
|
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-
|
|
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.
|
|
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.
|
|
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
|