siwe-rb 0.1.0
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 +7 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +211 -0
- data/LICENSE-MIT +21 -0
- data/README.md +142 -0
- data/Rakefile +12 -0
- data/lib/siwe/adapter.rb +33 -0
- data/lib/siwe/config.rb +51 -0
- data/lib/siwe/eip6492.rb +38 -0
- data/lib/siwe/error.rb +22 -0
- data/lib/siwe/error_type.rb +65 -0
- data/lib/siwe/message.rb +276 -0
- data/lib/siwe/parser.rb +220 -0
- data/lib/siwe/response.rb +13 -0
- data/lib/siwe/rpc.rb +92 -0
- data/lib/siwe/smart_wallet.rb +63 -0
- data/lib/siwe/util.rb +71 -0
- data/lib/siwe/version.rb +5 -0
- data/lib/siwe.rb +37 -0
- data/siwe-rb.gemspec +33 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1344345a5daec54f2ab4b8e0da7dd6476561afd6669f23f4a85a8bdcdb2c751c
|
|
4
|
+
data.tar.gz: ba59b351b355dc16dffdd08ca99214f895acdaa933a492f746d4916634887b51
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0a3d065c0830b39ceb0d4b36650671cb7e92ad48907caa305219956d394c155f53fc322c91052b1609667ec6fefcadab23203449f311631d924977fbe525aab2
|
|
7
|
+
data.tar.gz: 47e4a640616be6de51ca67b78cf90650bbbc043ff9b3468021ae749fd0c5b403c7bc7d94b406dfc8b30735f869c7455898aee01fb2d251c5dfabed0e0849b7dc
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] — 2026-05-04
|
|
4
|
+
|
|
5
|
+
Initial release of `siwe-rb`. Hard fork of the abandoned `siwe` gem (last published 0.1.0, 2021).
|
|
6
|
+
|
|
7
|
+
- Modern Ruby (≥ 3.2), no Ruby 2.x cruft.
|
|
8
|
+
- ABNF-aligned EIP-4361 parser; passes the shared `@signinwithethereum/test-vectors` suite (parsing, grammar, objects, verification).
|
|
9
|
+
- 17-character alphanumeric nonce, matching the TypeScript reference (`Siwe.generate_nonce`).
|
|
10
|
+
- Optional `scheme` field for `https://`-prefixed messages (ERC-4361 erratum).
|
|
11
|
+
- Single structured `Siwe::Error` with 27 typed error codes (`Siwe::ErrorType`) and `expected:` / `received:` context, replacing the legacy one-class-per-error layout.
|
|
12
|
+
- `Siwe::Response` value object returned by `Message#verify`; `Message#verify!` raises on failure for the common Rails-controller path.
|
|
13
|
+
- ERC-1271 + EIP-6492 smart-wallet support via the off-chain universal validator (single `eth_call` covers deployed wallets like Safe and counterfactual wallets like Coinbase Smart Wallet).
|
|
14
|
+
- Built-in `Siwe::Rpc::HttpClient` (Net::HTTP-based JSON-RPC) plus a duck-typed plug-in surface — drop in `web3.rb`, `eth-rpc`, or any object responding to `eth_call(to:, data:, block:)`.
|
|
15
|
+
- Pluggable `Siwe::Adapter` (defaults to the `eth` gem) for crypto / signature recovery.
|
|
16
|
+
- Modern toolchain: RuboCop, RSpec, WebMock, GitHub Actions matrix on Ruby 3.2 / 3.3 / 3.4, optional gated live-RPC integration job.
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
siwe-rb (0.1.0)
|
|
5
|
+
eth (>= 0.5.11, < 1.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
addressable (2.9.0)
|
|
11
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
12
|
+
ast (2.4.3)
|
|
13
|
+
base64 (0.3.0)
|
|
14
|
+
bigdecimal (3.3.1)
|
|
15
|
+
bls12-381 (0.3.1)
|
|
16
|
+
bigdecimal
|
|
17
|
+
h2c (>= 0.2.1, < 0.3)
|
|
18
|
+
crack (1.0.1)
|
|
19
|
+
bigdecimal
|
|
20
|
+
rexml
|
|
21
|
+
diff-lcs (1.6.2)
|
|
22
|
+
docile (1.4.1)
|
|
23
|
+
ecdsa (1.2.0)
|
|
24
|
+
eth (0.5.17)
|
|
25
|
+
base64 (~> 0.1)
|
|
26
|
+
bigdecimal (~> 3.1)
|
|
27
|
+
bls12-381 (~> 0.3)
|
|
28
|
+
forwardable (~> 1.3)
|
|
29
|
+
httpx (~> 1.6)
|
|
30
|
+
keccak (~> 1.3)
|
|
31
|
+
konstructor (~> 1.0)
|
|
32
|
+
openssl (~> 3.3)
|
|
33
|
+
rbsecp256k1 (~> 6.0)
|
|
34
|
+
scrypt (~> 3.0)
|
|
35
|
+
ffi (1.17.4)
|
|
36
|
+
ffi (1.17.4-aarch64-linux-gnu)
|
|
37
|
+
ffi (1.17.4-aarch64-linux-musl)
|
|
38
|
+
ffi (1.17.4-arm-linux-gnu)
|
|
39
|
+
ffi (1.17.4-arm-linux-musl)
|
|
40
|
+
ffi (1.17.4-arm64-darwin)
|
|
41
|
+
ffi (1.17.4-x86-linux-gnu)
|
|
42
|
+
ffi (1.17.4-x86-linux-musl)
|
|
43
|
+
ffi (1.17.4-x86_64-darwin)
|
|
44
|
+
ffi (1.17.4-x86_64-linux-gnu)
|
|
45
|
+
ffi (1.17.4-x86_64-linux-musl)
|
|
46
|
+
ffi-compiler (1.4.2)
|
|
47
|
+
ffi (>= 1.15.5)
|
|
48
|
+
rake
|
|
49
|
+
forwardable (1.4.0)
|
|
50
|
+
h2c (0.2.1)
|
|
51
|
+
ecdsa (~> 1.2.0)
|
|
52
|
+
hashdiff (1.2.1)
|
|
53
|
+
http-2 (1.1.3)
|
|
54
|
+
httpx (1.7.6)
|
|
55
|
+
http-2 (>= 1.1.3)
|
|
56
|
+
json (2.19.5)
|
|
57
|
+
keccak (1.3.3)
|
|
58
|
+
konstructor (1.0.2)
|
|
59
|
+
language_server-protocol (3.17.0.5)
|
|
60
|
+
lint_roller (1.1.0)
|
|
61
|
+
mini_portile2 (2.8.9)
|
|
62
|
+
openssl (3.3.2)
|
|
63
|
+
parallel (2.1.0)
|
|
64
|
+
parser (3.3.11.1)
|
|
65
|
+
ast (~> 2.4.1)
|
|
66
|
+
racc
|
|
67
|
+
pkg-config (1.6.5)
|
|
68
|
+
prism (1.9.0)
|
|
69
|
+
public_suffix (7.0.5)
|
|
70
|
+
racc (1.8.1)
|
|
71
|
+
rainbow (3.1.1)
|
|
72
|
+
rake (13.4.2)
|
|
73
|
+
rbsecp256k1 (6.0.0)
|
|
74
|
+
mini_portile2 (~> 2.8)
|
|
75
|
+
pkg-config (~> 1.5)
|
|
76
|
+
rubyzip (~> 2.3)
|
|
77
|
+
regexp_parser (2.12.0)
|
|
78
|
+
rexml (3.4.4)
|
|
79
|
+
rspec (3.13.2)
|
|
80
|
+
rspec-core (~> 3.13.0)
|
|
81
|
+
rspec-expectations (~> 3.13.0)
|
|
82
|
+
rspec-mocks (~> 3.13.0)
|
|
83
|
+
rspec-core (3.13.6)
|
|
84
|
+
rspec-support (~> 3.13.0)
|
|
85
|
+
rspec-expectations (3.13.5)
|
|
86
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
87
|
+
rspec-support (~> 3.13.0)
|
|
88
|
+
rspec-mocks (3.13.8)
|
|
89
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
90
|
+
rspec-support (~> 3.13.0)
|
|
91
|
+
rspec-support (3.13.7)
|
|
92
|
+
rubocop (1.86.1)
|
|
93
|
+
json (~> 2.3)
|
|
94
|
+
language_server-protocol (~> 3.17.0.2)
|
|
95
|
+
lint_roller (~> 1.1.0)
|
|
96
|
+
parallel (>= 1.10)
|
|
97
|
+
parser (>= 3.3.0.2)
|
|
98
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
99
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
100
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
101
|
+
ruby-progressbar (~> 1.7)
|
|
102
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
103
|
+
rubocop-ast (1.49.1)
|
|
104
|
+
parser (>= 3.3.7.2)
|
|
105
|
+
prism (~> 1.7)
|
|
106
|
+
ruby-progressbar (1.13.0)
|
|
107
|
+
rubyzip (2.4.1)
|
|
108
|
+
scrypt (3.1.0)
|
|
109
|
+
ffi-compiler (>= 1.0, < 2.0)
|
|
110
|
+
rake (~> 13)
|
|
111
|
+
simplecov (0.22.0)
|
|
112
|
+
docile (~> 1.1)
|
|
113
|
+
simplecov-html (~> 0.11)
|
|
114
|
+
simplecov_json_formatter (~> 0.1)
|
|
115
|
+
simplecov-html (0.13.2)
|
|
116
|
+
simplecov_json_formatter (0.1.4)
|
|
117
|
+
unicode-display_width (3.2.0)
|
|
118
|
+
unicode-emoji (~> 4.1)
|
|
119
|
+
unicode-emoji (4.2.0)
|
|
120
|
+
webmock (3.26.2)
|
|
121
|
+
addressable (>= 2.8.0)
|
|
122
|
+
crack (>= 0.3.2)
|
|
123
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
124
|
+
|
|
125
|
+
PLATFORMS
|
|
126
|
+
aarch64-linux-gnu
|
|
127
|
+
aarch64-linux-musl
|
|
128
|
+
arm-linux-gnu
|
|
129
|
+
arm-linux-musl
|
|
130
|
+
arm64-darwin
|
|
131
|
+
ruby
|
|
132
|
+
x86-linux-gnu
|
|
133
|
+
x86-linux-musl
|
|
134
|
+
x86_64-darwin
|
|
135
|
+
x86_64-linux-gnu
|
|
136
|
+
x86_64-linux-musl
|
|
137
|
+
|
|
138
|
+
DEPENDENCIES
|
|
139
|
+
rake (~> 13.0)
|
|
140
|
+
rspec (~> 3.13)
|
|
141
|
+
rubocop (~> 1.65)
|
|
142
|
+
simplecov (~> 0.22)
|
|
143
|
+
siwe-rb!
|
|
144
|
+
webmock (~> 3.25)
|
|
145
|
+
|
|
146
|
+
CHECKSUMS
|
|
147
|
+
addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
|
|
148
|
+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
149
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
150
|
+
bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
|
|
151
|
+
bls12-381 (0.3.1) sha256=d1525cd319c53f14178d54f355dfe1b4c572fbf5625965c1afccdc425b9896dc
|
|
152
|
+
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
|
|
153
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
154
|
+
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
|
155
|
+
ecdsa (1.2.0) sha256=b8f7c9541b6c587cbe37e705a1b76be6dc1e85336aa23ac3cf2f07b038bcec55
|
|
156
|
+
eth (0.5.17) sha256=5be9caa85b0643613010ae33c92e746a02ce376a35245e6eb5f491e80531f137
|
|
157
|
+
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
|
|
158
|
+
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
|
159
|
+
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
|
160
|
+
ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564
|
|
161
|
+
ffi (1.17.4-arm-linux-musl) sha256=9d4838ded0465bef6e2426935f6bcc93134b6616785a84ffd2a3d82bc3cf6f95
|
|
162
|
+
ffi (1.17.4-arm64-darwin) sha256=19071aaf1419251b0a46852abf960e77330a3b334d13a4ab51d58b31a937001b
|
|
163
|
+
ffi (1.17.4-x86-linux-gnu) sha256=38e150df5f4ca555e25beca4090823ae09657bceded154e3c52f8631c1ed72cf
|
|
164
|
+
ffi (1.17.4-x86-linux-musl) sha256=fbeec0fc7c795bcf86f623bb18d31ea1820f7bd580e1703a3d3740d527437809
|
|
165
|
+
ffi (1.17.4-x86_64-darwin) sha256=aa70390523cf3235096cf64962b709b4cfbd5c082a2cb2ae714eb0fe2ccda496
|
|
166
|
+
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
|
167
|
+
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
|
168
|
+
ffi-compiler (1.4.2) sha256=a9d69d5d9ced6f64776ddafa535867ad90e5740ad37bed7afb4de6e450da126e
|
|
169
|
+
forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838
|
|
170
|
+
h2c (0.2.1) sha256=a53b7dfeb95660c97b615235ea1b29ec8e1856edc571845685682d94b6e8e227
|
|
171
|
+
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
172
|
+
http-2 (1.1.3) sha256=1b2f379d35a11dbae94f8a1a52c053d8c161eb4a0c98b5d1605ff1b2bf171c9c
|
|
173
|
+
httpx (1.7.6) sha256=82d825abc9876a132adc3492c56a0c528478ac238dd6f74d3422ab0036c6b5c8
|
|
174
|
+
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
|
|
175
|
+
keccak (1.3.3) sha256=970dcb1e78b8c3129ba2baff8a5baa5cebf391136db1f4018e8ccc9130794557
|
|
176
|
+
konstructor (1.0.2) sha256=fd6ac9eb1dadc1e520e06042aa2ef0122d6a070e06cde86db5a54c0c7bfe2e31
|
|
177
|
+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
178
|
+
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
179
|
+
mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289
|
|
180
|
+
openssl (3.3.2) sha256=7f4e01215dc9c4be1fca71d692406be3e6340b39c1f71a47fea9c497decd0f6c
|
|
181
|
+
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
182
|
+
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
|
183
|
+
pkg-config (1.6.5) sha256=33f9f81c5322983d22b439b8b672f27777b406fea23bfec74ff14bbeb42ec733
|
|
184
|
+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
185
|
+
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
|
186
|
+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
187
|
+
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
188
|
+
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
189
|
+
rbsecp256k1 (6.0.0) sha256=d38f563bfc6bcf3ce20c336a4798cc6990427f291e74c6817360363a72f11669
|
|
190
|
+
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
|
191
|
+
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
192
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
193
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
194
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
195
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
196
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
197
|
+
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
|
|
198
|
+
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
|
199
|
+
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
200
|
+
rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615
|
|
201
|
+
scrypt (3.1.0) sha256=67fde35bc7e3b7fe906c5be9c242acf95fe4a886c89572f405f6b11df30aa6af
|
|
202
|
+
simplecov (0.22.0) sha256=fe2622c7834ff23b98066bb0a854284b2729a569ac659f82621fc22ef36213a5
|
|
203
|
+
simplecov-html (0.13.2) sha256=bd0b8e54e7c2d7685927e8d6286466359b6f16b18cb0df47b508e8d73c777246
|
|
204
|
+
simplecov_json_formatter (0.1.4) sha256=529418fbe8de1713ac2b2d612aa3daa56d316975d307244399fa4838c601b428
|
|
205
|
+
siwe-rb (0.1.0)
|
|
206
|
+
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
207
|
+
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
208
|
+
webmock (3.26.2) sha256=774556f2ea6371846cca68c01769b2eac0d134492d21f6d0ab5dd643965a4c90
|
|
209
|
+
|
|
210
|
+
BUNDLED WITH
|
|
211
|
+
4.0.3
|
data/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EthID.org
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# siwe-rb
|
|
2
|
+
|
|
3
|
+
Sign-In with Ethereum (EIP-4361) for Ruby — message construction, parsing, and signature verification, with built-in support for ERC-1271 and EIP-6492 smart contract wallets.
|
|
4
|
+
|
|
5
|
+
`siwe-rb` is the Ruby companion to the [TypeScript](https://github.com/signinwithethereum/siwe), [Python](https://github.com/signinwithethereum/siwe-py), and [Rust](https://github.com/signinwithethereum/siwe-rs) implementations under the [@signinwithethereum](https://github.com/signinwithethereum) organisation. It runs against the same shared test-vector suite.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
# Gemfile
|
|
11
|
+
gem "siwe-rb", "~> 0.1"
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
require "siwe"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Ruby ≥ 3.2.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Construct and sign a message
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "siwe"
|
|
26
|
+
require "eth"
|
|
27
|
+
|
|
28
|
+
key = Eth::Key.new
|
|
29
|
+
|
|
30
|
+
message = Siwe::Message.new(
|
|
31
|
+
domain: "example.com",
|
|
32
|
+
address: key.address.to_s,
|
|
33
|
+
uri: "https://example.com/login",
|
|
34
|
+
chain_id: 1,
|
|
35
|
+
nonce: Siwe.generate_nonce,
|
|
36
|
+
issued_at: Time.now.utc.iso8601,
|
|
37
|
+
statement: "Sign in to example.com"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
text = message.prepare_message # → EIP-4361 message string
|
|
41
|
+
signature = key.personal_sign(text)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Parse a message
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
message = Siwe::Message.parse(text)
|
|
48
|
+
message.domain # => "example.com"
|
|
49
|
+
message.address # => "0x..."
|
|
50
|
+
message.warnings # => [] (e.g. ["address is not EIP-55 checksummed - 0x…"])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Verify a signature
|
|
54
|
+
|
|
55
|
+
`verify` returns a `Siwe::Response`; `verify!` raises a `Siwe::Error` on failure.
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
response = message.verify(
|
|
59
|
+
signature: signature,
|
|
60
|
+
domain: "example.com",
|
|
61
|
+
nonce: message.nonce
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if response.success?
|
|
65
|
+
# signed in
|
|
66
|
+
else
|
|
67
|
+
Rails.logger.warn("siwe failed: #{response.error.type}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# or, idiomatic Ruby:
|
|
71
|
+
message.verify!(signature: signature, domain: "example.com", nonce: message.nonce)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Smart-wallet support (ERC-1271, EIP-6492)
|
|
75
|
+
|
|
76
|
+
Configure an Ethereum RPC URL once at boot, and `verify` will automatically fall through to a single deploy-and-call against the EIP-6492 universal validator when EOA recovery fails. This handles both deployed ERC-1271 wallets (e.g. Safe) and counterfactual EIP-6492-wrapped signatures (e.g. Coinbase Smart Wallet) in one call.
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
Siwe.configure do |c|
|
|
80
|
+
c.rpc_url = ENV["ETH_RPC_URL"] # e.g. https://ethereum-rpc.publicnode.com
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
message.verify!(signature: sig, domain: "example.com", nonce: message.nonce)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
You can also pass an RPC client per call, or inject your own client (anything responding to `eth_call(to:, data:, block:)`):
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
custom_rpc = MyOwnRpcClient.new(...)
|
|
90
|
+
config = Siwe::Config.new(rpc: custom_rpc)
|
|
91
|
+
message.verify!(signature: sig, domain: domain, nonce: nonce, config: config)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Error handling
|
|
95
|
+
|
|
96
|
+
All failures raise (or, for `verify`, surface as `response.error`) a single `Siwe::Error` carrying a `type` symbol from `Siwe::ErrorType`:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
begin
|
|
100
|
+
message.verify!(signature: sig, domain: domain, nonce: nonce)
|
|
101
|
+
rescue Siwe::Error => e
|
|
102
|
+
case e.type
|
|
103
|
+
when :expired_message then render_expired
|
|
104
|
+
when :nonce_mismatch then render_replay
|
|
105
|
+
when :invalid_signature then render_unauthorized
|
|
106
|
+
when :rpc_error then retry_or_fail
|
|
107
|
+
else render_generic_error
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The full set of error types mirrors `SiweErrorType` in the TypeScript reference (27 codes including the Ruby-specific `:rpc_error`). See `lib/siwe/error_type.rb`.
|
|
113
|
+
|
|
114
|
+
## Comparison with the TypeScript implementation
|
|
115
|
+
|
|
116
|
+
| Feature | TS | Ruby |
|
|
117
|
+
| ---------------------------------- | ------------- | -------------- |
|
|
118
|
+
| EIP-4361 v1 parsing & rendering | ✓ | ✓ |
|
|
119
|
+
| Optional `scheme` field | ✓ | ✓ |
|
|
120
|
+
| EIP-55 checksum + warning | ✓ | ✓ |
|
|
121
|
+
| 17-char alphanumeric nonce | ✓ | ✓ |
|
|
122
|
+
| `verify` / response object | Promise | sync `Response`|
|
|
123
|
+
| EOA verification | ✓ | ✓ |
|
|
124
|
+
| ERC-1271 verification | ✓ (via viem) | ✓ (built-in) |
|
|
125
|
+
| EIP-6492 verification | ✓ (via viem) | ✓ (built-in) |
|
|
126
|
+
| Pluggable provider | viem / ethers | duck-typed RPC |
|
|
127
|
+
| Shared test-vector suite | ✓ | ✓ |
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git submodule update --init # pulls in the shared test-vectors repo
|
|
133
|
+
bundle install
|
|
134
|
+
bundle exec rake # runs rspec + rubocop
|
|
135
|
+
|
|
136
|
+
# Live RPC integration tests (Argent, Loopring, EIP-6492 universal validator):
|
|
137
|
+
SIWE_RPC_URL=https://ethereum-rpc.publicnode.com bundle exec rspec --tag live_rpc
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT or Apache-2.0, at your option.
|
data/Rakefile
ADDED
data/lib/siwe/adapter.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "eth"
|
|
4
|
+
|
|
5
|
+
require_relative "error"
|
|
6
|
+
require_relative "error_type"
|
|
7
|
+
|
|
8
|
+
module Siwe
|
|
9
|
+
module Adapter
|
|
10
|
+
# Default crypto adapter using the eth gem.
|
|
11
|
+
# Implements the SiweConfig interface from the TS reference implementation:
|
|
12
|
+
# verify_message → recover signer address from EIP-191 signed message
|
|
13
|
+
# hash_message → EIP-191 personal_sign hash
|
|
14
|
+
# get_address → normalize to EIP-55 checksum address
|
|
15
|
+
class EthGem
|
|
16
|
+
def verify_message(message, signature)
|
|
17
|
+
public_key = Eth::Signature.personal_recover(message, signature)
|
|
18
|
+
Eth::Util.public_key_to_address(public_key).to_s
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def hash_message(message)
|
|
22
|
+
prefixed = Eth::Signature.prefix_message(message)
|
|
23
|
+
Eth::Util.keccak256(prefixed)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get_address(addr)
|
|
27
|
+
Eth::Address.new(addr).to_s
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
DEFAULT = EthGem.new
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/siwe/config.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "adapter"
|
|
4
|
+
|
|
5
|
+
module Siwe
|
|
6
|
+
# Verification config — pluggable adapter (crypto), RPC URL or RPC client (smart wallet),
|
|
7
|
+
# and optional verification fallback.
|
|
8
|
+
#
|
|
9
|
+
# Set globally via Siwe.configure { |c| ... } or per-call as `verify(config: ...)`.
|
|
10
|
+
class Config
|
|
11
|
+
attr_reader :rpc_url, :rpc, :adapter, :verification_fallback
|
|
12
|
+
|
|
13
|
+
def initialize(rpc_url: nil, rpc: nil, adapter: nil, verification_fallback: nil)
|
|
14
|
+
@rpc_url = rpc_url
|
|
15
|
+
@rpc = rpc
|
|
16
|
+
@adapter = adapter || Siwe::Adapter::DEFAULT
|
|
17
|
+
@verification_fallback = verification_fallback
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def to_h
|
|
21
|
+
{
|
|
22
|
+
rpc_url: @rpc_url,
|
|
23
|
+
rpc: @rpc,
|
|
24
|
+
adapter: @adapter,
|
|
25
|
+
verification_fallback: @verification_fallback
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Mutable struct used inside Siwe.configure { |c| c.rpc_url = ... }.
|
|
30
|
+
# Caller mutates fields, then build returns a frozen Config.
|
|
31
|
+
class Builder
|
|
32
|
+
attr_accessor :rpc_url, :rpc, :adapter, :verification_fallback
|
|
33
|
+
|
|
34
|
+
def initialize(rpc_url: nil, rpc: nil, adapter: nil, verification_fallback: nil)
|
|
35
|
+
@rpc_url = rpc_url
|
|
36
|
+
@rpc = rpc
|
|
37
|
+
@adapter = adapter
|
|
38
|
+
@verification_fallback = verification_fallback
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def build
|
|
42
|
+
Config.new(
|
|
43
|
+
rpc_url: @rpc_url,
|
|
44
|
+
rpc: @rpc,
|
|
45
|
+
adapter: @adapter,
|
|
46
|
+
verification_fallback: @verification_fallback
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/siwe/eip6492.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Siwe
|
|
4
|
+
# EIP-6492 — Signatures for Predeploy Contracts.
|
|
5
|
+
#
|
|
6
|
+
# Wraps an inner ERC-1271 signature with a deploy-and-call shim so that signers
|
|
7
|
+
# whose contract has not yet been deployed can still produce verifiable signatures.
|
|
8
|
+
# See https://eips.ethereum.org/EIPS/eip-6492.
|
|
9
|
+
module Eip6492
|
|
10
|
+
# 32-byte magic suffix appended to EIP-6492 wrapped signatures.
|
|
11
|
+
MAGIC_SUFFIX = "6492649264926492649264926492649264926492649264926492649264926492"
|
|
12
|
+
|
|
13
|
+
# ERC-1271 magic value returned by `isValidSignature(bytes32,bytes)`.
|
|
14
|
+
EIP1271_MAGIC_VALUE = "1626ba7e"
|
|
15
|
+
|
|
16
|
+
# Off-chain universal signature validator. Deployed via eth_call (no actual
|
|
17
|
+
# deployment) to verify EOA, ERC-1271, and EIP-6492 signatures in one call.
|
|
18
|
+
#
|
|
19
|
+
# Constructor: (address signer, bytes32 hash, bytes signature)
|
|
20
|
+
# Returns: 0x01 if valid, 0x00 if invalid.
|
|
21
|
+
#
|
|
22
|
+
# Source: viem `erc6492SignatureValidatorByteCode`. Mirror of
|
|
23
|
+
# libs/ts/packages/siwe/lib/eip6492.ts:16. Without the 0x prefix.
|
|
24
|
+
# rubocop:disable Layout/LineLength
|
|
25
|
+
VALIDATOR_BYTECODE = "608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f64926492649264926492649264926492649264926492649264926492649264926100748361040c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b60000361016057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e7610425565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b0381111561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572"
|
|
26
|
+
# rubocop:enable Layout/LineLength
|
|
27
|
+
|
|
28
|
+
# Predicate: does this signature carry the EIP-6492 magic suffix?
|
|
29
|
+
def self.signature?(hex)
|
|
30
|
+
return false if hex.nil?
|
|
31
|
+
|
|
32
|
+
stripped = hex.start_with?("0x") ? hex[2..] : hex
|
|
33
|
+
return false if stripped.length < MAGIC_SUFFIX.length
|
|
34
|
+
|
|
35
|
+
stripped.end_with?(MAGIC_SUFFIX)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/siwe/error.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "error_type"
|
|
4
|
+
|
|
5
|
+
module Siwe
|
|
6
|
+
class Error < StandardError
|
|
7
|
+
attr_reader :type, :expected, :received
|
|
8
|
+
|
|
9
|
+
def initialize(type, expected: nil, received: nil, message: nil)
|
|
10
|
+
raise ArgumentError, "unknown SIWE error type: #{type.inspect}" unless ErrorType::MESSAGES.key?(type)
|
|
11
|
+
|
|
12
|
+
@type = type
|
|
13
|
+
@expected = expected
|
|
14
|
+
@received = received
|
|
15
|
+
super(message || ErrorType::MESSAGES.fetch(type))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{ type: @type, expected: @expected, received: @received, message: message }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Siwe
|
|
4
|
+
module ErrorType
|
|
5
|
+
EXPIRED_MESSAGE = :expired_message
|
|
6
|
+
INVALID_DOMAIN = :invalid_domain
|
|
7
|
+
SCHEME_MISMATCH = :scheme_mismatch
|
|
8
|
+
DOMAIN_MISMATCH = :domain_mismatch
|
|
9
|
+
NONCE_MISMATCH = :nonce_mismatch
|
|
10
|
+
URI_MISMATCH = :uri_mismatch
|
|
11
|
+
CHAIN_ID_MISMATCH = :chain_id_mismatch
|
|
12
|
+
REQUEST_ID_MISMATCH = :request_id_mismatch
|
|
13
|
+
INVALID_ADDRESS = :invalid_address
|
|
14
|
+
INVALID_URI = :invalid_uri
|
|
15
|
+
INVALID_NONCE = :invalid_nonce
|
|
16
|
+
NOT_YET_VALID_MESSAGE = :not_yet_valid_message
|
|
17
|
+
INVALID_SIGNATURE = :invalid_signature
|
|
18
|
+
INVALID_SIGNATURE_CHAIN_ID = :invalid_signature_chain_id
|
|
19
|
+
INVALID_TIME_FORMAT = :invalid_time_format
|
|
20
|
+
INVALID_MESSAGE_VERSION = :invalid_message_version
|
|
21
|
+
UNABLE_TO_PARSE = :unable_to_parse
|
|
22
|
+
MISSING_DOMAIN = :missing_domain
|
|
23
|
+
MISSING_NONCE = :missing_nonce
|
|
24
|
+
MISSING_URI = :missing_uri
|
|
25
|
+
MISSING_CHAIN_ID = :missing_chain_id
|
|
26
|
+
MISSING_CONFIG = :missing_config
|
|
27
|
+
MISSING_PROVIDER_LIBRARY = :missing_provider_library
|
|
28
|
+
NONCE_GENERATION_FAILED = :nonce_generation_failed
|
|
29
|
+
INVALID_PARAMS = :invalid_params
|
|
30
|
+
MALFORMED_MESSAGE = :malformed_message
|
|
31
|
+
RPC_ERROR = :rpc_error
|
|
32
|
+
|
|
33
|
+
MESSAGES = {
|
|
34
|
+
EXPIRED_MESSAGE => "Expired message.",
|
|
35
|
+
INVALID_DOMAIN => "Invalid domain.",
|
|
36
|
+
SCHEME_MISMATCH => "Scheme does not match provided scheme for verification.",
|
|
37
|
+
DOMAIN_MISMATCH => "Domain does not match provided domain for verification.",
|
|
38
|
+
NONCE_MISMATCH => "Nonce does not match provided nonce for verification.",
|
|
39
|
+
URI_MISMATCH => "URI does not match provided URI for verification.",
|
|
40
|
+
CHAIN_ID_MISMATCH => "Chain ID does not match provided chain ID for verification.",
|
|
41
|
+
REQUEST_ID_MISMATCH => "Request ID does not match provided request ID for verification.",
|
|
42
|
+
INVALID_ADDRESS => "Invalid address.",
|
|
43
|
+
INVALID_URI => "URI does not conform to RFC 3986.",
|
|
44
|
+
INVALID_NONCE => "Nonce size smaller than 8 characters or is not alphanumeric.",
|
|
45
|
+
NOT_YET_VALID_MESSAGE => "Message is not valid yet.",
|
|
46
|
+
INVALID_SIGNATURE => "Signature does not match address of the message.",
|
|
47
|
+
INVALID_SIGNATURE_CHAIN_ID => "Contract wallet verification provider chain does not match message chain ID.",
|
|
48
|
+
INVALID_TIME_FORMAT => "Invalid time format.",
|
|
49
|
+
INVALID_MESSAGE_VERSION => "Invalid message version.",
|
|
50
|
+
UNABLE_TO_PARSE => "Unable to parse the message.",
|
|
51
|
+
MISSING_DOMAIN => "Domain is required for verification.",
|
|
52
|
+
MISSING_NONCE => "Nonce is required for verification.",
|
|
53
|
+
MISSING_URI => "URI is required in strict mode.",
|
|
54
|
+
MISSING_CHAIN_ID => "Chain ID is required in strict mode.",
|
|
55
|
+
MISSING_CONFIG => "No verification configuration found.",
|
|
56
|
+
MISSING_PROVIDER_LIBRARY => "Required provider library is not installed.",
|
|
57
|
+
NONCE_GENERATION_FAILED => "Nonce generation failed.",
|
|
58
|
+
INVALID_PARAMS => "Invalid parameters passed to verify.",
|
|
59
|
+
MALFORMED_MESSAGE => "Message could not be prepared for signing.",
|
|
60
|
+
RPC_ERROR => "RPC call failed."
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
ALL = MESSAGES.keys.freeze
|
|
64
|
+
end
|
|
65
|
+
end
|