eth 0.4.18 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql.yml +2 -2
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/{build.yml → spec.yml} +12 -7
- data/.gitignore +24 -24
- data/.gitmodules +3 -3
- data/AUTHORS.txt +16 -0
- data/Gemfile +12 -4
- data/LICENSE.txt +202 -22
- data/README.md +154 -84
- data/bin/console +4 -5
- data/bin/setup +4 -2
- data/eth.gemspec +24 -18
- data/lib/eth/abi/constant.rb +63 -0
- data/lib/eth/abi/type.rb +177 -0
- data/lib/eth/abi.rb +390 -0
- data/lib/eth/address.rb +50 -10
- data/lib/eth/chain.rb +148 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +118 -85
- data/lib/eth/key/encrypter.rb +178 -99
- data/lib/eth/key.rb +129 -45
- data/lib/eth/signature.rb +160 -0
- data/lib/eth/tx/eip1559.rb +329 -0
- data/lib/eth/tx/eip2930.rb +321 -0
- data/lib/eth/tx/legacy.rb +293 -0
- data/lib/eth/tx.rb +279 -147
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +178 -0
- data/lib/eth/version.rb +18 -1
- data/lib/eth.rb +27 -67
- metadata +35 -84
- data/lib/eth/gas.rb +0 -7
- data/lib/eth/open_ssl.rb +0 -395
- data/lib/eth/secp256k1.rb +0 -5
- data/lib/eth/sedes.rb +0 -39
- data/lib/eth/utils.rb +0 -126
data/lib/eth/key/encrypter.rb
CHANGED
@@ -1,126 +1,205 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# Copyright (c) 2016-2022 The Ruby-Eth Contributors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Provides the `Eth` module.
|
16
|
+
module Eth
|
17
|
+
|
18
|
+
# The Eth::Key::Encrypter class to handle PBKDF2-SHA-256 encryption.
|
19
|
+
class Key::Encrypter
|
20
|
+
|
21
|
+
# Provides a specific encrypter error if decryption fails.
|
22
|
+
class EncrypterError < StandardError; end
|
23
|
+
|
24
|
+
# Class method `Eth::Key::Encrypter.perform` to performa an key-store
|
25
|
+
# encryption.
|
26
|
+
#
|
27
|
+
# @param key [Eth::Key] representing a secret key-pair used for encryption
|
28
|
+
# @param options [Hash] the options to encrypt with
|
29
|
+
# @option options [String] :kdf key derivation function defaults to pbkdf2
|
30
|
+
# @option options [String] :id uuid given to the secret key
|
31
|
+
# @option options [String] :iterations number of iterations for the hash function
|
32
|
+
# @option options [String] :salt passed to PBKDF
|
33
|
+
# @option options [String] :iv 128-bit initialisation vector for the cipher
|
34
|
+
# @option options [Integer] :parallelization parallelization factor for scrypt, defaults to 8
|
35
|
+
# @option options [Integer] :block_size for scrypt, defaults to 1
|
36
|
+
# @return [JSON] formatted with encrypted key (cyphertext) and [other identifying data](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition#pbkdf2-sha-256)
|
37
|
+
def self.perform(key, password, options = {})
|
38
|
+
new(key, options).perform(password)
|
39
|
+
end
|
3
40
|
|
4
|
-
|
5
|
-
|
41
|
+
# Constructor of the `Eth::Key::Encrypter` class for secret key
|
42
|
+
# encryption.
|
43
|
+
#
|
44
|
+
# @param key [Eth::Key] representing a secret key-pair used for encryption
|
45
|
+
# @param options [Hash] the options to encrypt with
|
46
|
+
# @option options [String] :kdf key derivation function defaults to pbkdf2
|
47
|
+
# @option options [String] :id uuid given to the secret key
|
48
|
+
# @option options [String] :iterations number of iterations for the hash function
|
49
|
+
# @option options [String] :salt passed to PBKDF
|
50
|
+
# @option options [String] :iv 128-bit initialisation vector for the cipher
|
51
|
+
# @option options [Integer] :parallelization parallelization factor for scrypt, defaults to 8
|
52
|
+
# @option options [Integer] :block_size for scrypt, defaults to 1
|
53
|
+
def initialize(key, options = {})
|
54
|
+
@key = key
|
55
|
+
@options = options
|
56
|
+
|
57
|
+
# the key derivation functions default to pbkdf2 if no option is specified
|
58
|
+
# however, if an option is given then it must be either pbkdf2 or scrypt
|
59
|
+
if kdf != "scrypt" && kdf != "pbkdf2"
|
60
|
+
raise EncrypterError, "Unsupported key derivation function: #{kdf}!"
|
61
|
+
end
|
62
|
+
end
|
6
63
|
|
7
|
-
|
8
|
-
|
9
|
-
|
64
|
+
# Encrypt the key with a given password.
|
65
|
+
#
|
66
|
+
# @param password [String] a secret key used for encryption
|
67
|
+
# @return [String] a json-formatted keystore string.
|
68
|
+
def perform(password)
|
69
|
+
derive_key password
|
70
|
+
encrypt
|
71
|
+
data.to_json
|
72
|
+
end
|
10
73
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
74
|
+
# Output containing the encrypted key and
|
75
|
+
# [other identifying data](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition#pbkdf2-sha-256)
|
76
|
+
#
|
77
|
+
# @return [Hash] the encrypted keystore data.
|
78
|
+
def data
|
79
|
+
# default to pbkdf2
|
80
|
+
kdfparams = if kdf == "scrypt"
|
81
|
+
{
|
82
|
+
dklen: 32,
|
83
|
+
n: iterations,
|
84
|
+
p: parallelization,
|
85
|
+
r: block_size,
|
86
|
+
salt: Util.bin_to_hex(salt),
|
87
|
+
}
|
88
|
+
else
|
89
|
+
{
|
90
|
+
c: iterations,
|
91
|
+
dklen: 32,
|
92
|
+
prf: prf,
|
93
|
+
salt: Util.bin_to_hex(salt),
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
{
|
98
|
+
crypto: {
|
99
|
+
cipher: cipher_name,
|
100
|
+
cipherparams: {
|
101
|
+
iv: Util.bin_to_hex(iv),
|
102
|
+
},
|
103
|
+
ciphertext: Util.bin_to_hex(encrypted_key),
|
104
|
+
kdf: kdf,
|
105
|
+
kdfparams: kdfparams,
|
106
|
+
mac: Util.bin_to_hex(mac),
|
107
|
+
},
|
108
|
+
id: id,
|
109
|
+
version: 3,
|
110
|
+
}
|
111
|
+
end
|
15
112
|
|
16
|
-
|
17
|
-
derive_key password
|
18
|
-
encrypt
|
113
|
+
private
|
19
114
|
|
20
|
-
|
21
|
-
end
|
115
|
+
attr_reader :derived_key, :encrypted_key, :key, :options
|
22
116
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
cipher
|
27
|
-
|
28
|
-
|
29
|
-
},
|
30
|
-
ciphertext: bin_to_hex(encrypted_key),
|
31
|
-
kdf: "pbkdf2",
|
32
|
-
kdfparams: {
|
33
|
-
c: iterations,
|
34
|
-
dklen: 32,
|
35
|
-
prf: prf,
|
36
|
-
salt: bin_to_hex(salt),
|
37
|
-
},
|
38
|
-
mac: bin_to_hex(mac),
|
39
|
-
},
|
40
|
-
id: id,
|
41
|
-
version: 3,
|
42
|
-
}.tap do |data|
|
43
|
-
data[:address] = address unless options[:skip_address]
|
117
|
+
def cipher
|
118
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
119
|
+
cipher.encrypt
|
120
|
+
cipher.iv = iv
|
121
|
+
cipher.key = derived_key[0, (key_length / 2)]
|
122
|
+
end
|
44
123
|
end
|
45
|
-
end
|
46
124
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
private
|
125
|
+
def digest
|
126
|
+
@digest ||= OpenSSL::Digest.new digest_name
|
127
|
+
end
|
52
128
|
|
53
|
-
|
129
|
+
def derive_key(password)
|
130
|
+
if kdf == "scrypt"
|
131
|
+
@derived_key = SCrypt::Engine.scrypt(password, salt, iterations, block_size, parallelization, key_length)
|
132
|
+
else
|
133
|
+
@derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
134
|
+
end
|
135
|
+
end
|
54
136
|
|
55
|
-
|
56
|
-
|
57
|
-
cipher.encrypt
|
58
|
-
cipher.iv = iv
|
59
|
-
cipher.key = derived_key[0, (key_length / 2)]
|
137
|
+
def encrypt
|
138
|
+
@encrypted_key = cipher.update(Util.hex_to_bin key.private_hex) + cipher.final
|
60
139
|
end
|
61
|
-
end
|
62
140
|
|
63
|
-
|
64
|
-
|
65
|
-
|
141
|
+
def mac
|
142
|
+
Util.keccak256(derived_key[(key_length / 2), key_length] + encrypted_key)
|
143
|
+
end
|
66
144
|
|
67
|
-
|
68
|
-
|
69
|
-
|
145
|
+
def kdf
|
146
|
+
options[:kdf] || "pbkdf2"
|
147
|
+
end
|
70
148
|
|
71
|
-
|
72
|
-
|
73
|
-
|
149
|
+
def cipher_name
|
150
|
+
"aes-128-ctr"
|
151
|
+
end
|
74
152
|
|
75
|
-
|
76
|
-
|
77
|
-
|
153
|
+
def digest_name
|
154
|
+
"sha256"
|
155
|
+
end
|
78
156
|
|
79
|
-
|
80
|
-
|
81
|
-
|
157
|
+
def prf
|
158
|
+
"hmac-#{digest_name}"
|
159
|
+
end
|
82
160
|
|
83
|
-
|
84
|
-
|
85
|
-
|
161
|
+
def key_length
|
162
|
+
32
|
163
|
+
end
|
86
164
|
|
87
|
-
|
88
|
-
|
89
|
-
|
165
|
+
def salt_length
|
166
|
+
32
|
167
|
+
end
|
90
168
|
|
91
|
-
|
92
|
-
|
93
|
-
|
169
|
+
def iv_length
|
170
|
+
16
|
171
|
+
end
|
94
172
|
|
95
|
-
|
96
|
-
|
97
|
-
|
173
|
+
def id
|
174
|
+
@id ||= options[:id] || SecureRandom.uuid
|
175
|
+
end
|
98
176
|
|
99
|
-
|
100
|
-
|
101
|
-
|
177
|
+
def iterations
|
178
|
+
options[:iterations] || 262_144
|
179
|
+
end
|
102
180
|
|
103
|
-
|
104
|
-
|
105
|
-
|
181
|
+
def salt
|
182
|
+
@salt ||= if options[:salt]
|
183
|
+
Util.hex_to_bin options[:salt]
|
184
|
+
else
|
185
|
+
SecureRandom.random_bytes(salt_length)
|
186
|
+
end
|
187
|
+
end
|
106
188
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
189
|
+
def iv
|
190
|
+
@iv ||= if options[:iv]
|
191
|
+
Util.hex_to_bin options[:iv]
|
192
|
+
else
|
193
|
+
SecureRandom.random_bytes(iv_length)
|
194
|
+
end
|
195
|
+
end
|
114
196
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
else
|
119
|
-
SecureRandom.random_bytes(iv_length)
|
120
|
-
end
|
121
|
-
end
|
197
|
+
def parallelization
|
198
|
+
options[:parallelization] || 8
|
199
|
+
end
|
122
200
|
|
123
|
-
|
124
|
-
|
201
|
+
def block_size
|
202
|
+
options[:block_size] || 1
|
203
|
+
end
|
125
204
|
end
|
126
205
|
end
|
data/lib/eth/key.rb
CHANGED
@@ -1,78 +1,162 @@
|
|
1
|
+
# Copyright (c) 2016-2022 The Ruby-Eth Contributors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "json"
|
16
|
+
require "openssl"
|
17
|
+
require "rbsecp256k1"
|
18
|
+
require "scrypt"
|
19
|
+
require "securerandom"
|
20
|
+
|
21
|
+
# Provides the `Eth` module.
|
1
22
|
module Eth
|
23
|
+
|
24
|
+
# The `Eth::Key` class to handle Secp256k1 private/public key-pairs.
|
2
25
|
class Key
|
26
|
+
|
27
|
+
# The Eth::Key::Decrypter class to handle PBKDF2-SHA-256 decryption.
|
3
28
|
autoload :Decrypter, "eth/key/decrypter"
|
29
|
+
|
30
|
+
# The Eth::Key::Encrypter class to handle PBKDF2-SHA-256 encryption.
|
4
31
|
autoload :Encrypter, "eth/key/encrypter"
|
5
32
|
|
6
|
-
|
33
|
+
# The `Secp256k1::PrivateKey` of the `Eth::Key` pair.
|
34
|
+
attr_reader :private_key
|
7
35
|
|
8
|
-
|
9
|
-
|
36
|
+
# The `Secp256k1::PublicKey` of the `Eth::Key` pair.
|
37
|
+
attr_reader :public_key
|
10
38
|
|
11
|
-
|
12
|
-
|
39
|
+
# Constructor of the `Eth::Key` class. Creates a new random key-pair
|
40
|
+
# if no `priv` key is provided.
|
41
|
+
#
|
42
|
+
# @param priv [String] binary string of private key data (optional).
|
43
|
+
def initialize(priv: nil)
|
13
44
|
|
14
|
-
|
15
|
-
|
16
|
-
new priv: priv
|
17
|
-
end
|
45
|
+
# Creates a new, randomized libsecp256k1 context.
|
46
|
+
ctx = Secp256k1::Context.new context_randomization_bytes: SecureRandom.random_bytes(32)
|
18
47
|
|
19
|
-
|
20
|
-
|
21
|
-
OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), bin_signature)
|
22
|
-
end
|
48
|
+
# Creates a new random key pair (public, private).
|
49
|
+
key = ctx.generate_key_pair
|
23
50
|
|
24
|
-
|
25
|
-
@private_key = MoneyTree::PrivateKey.new key: priv
|
26
|
-
@public_key = MoneyTree::PublicKey.new private_key, compressed: false
|
27
|
-
end
|
51
|
+
unless priv.nil?
|
28
52
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
53
|
+
# Converts hex private keys to binary strings.
|
54
|
+
priv = Util.hex_to_bin priv if Util.is_hex? priv
|
32
55
|
|
33
|
-
|
34
|
-
|
56
|
+
# Creates a keypair from existing private key data.
|
57
|
+
key = ctx.key_pair_from_private_key priv
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sets the attributes.
|
61
|
+
@private_key = key.private_key
|
62
|
+
@public_key = key.public_key
|
35
63
|
end
|
36
64
|
|
37
|
-
|
38
|
-
|
65
|
+
# Signs arbitrary data without validation. Should not be used unless really
|
66
|
+
# desired. See also: personal_sign, sign_typed_data.
|
67
|
+
#
|
68
|
+
# @param blob [String] that arbitrary data to be signed.
|
69
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
70
|
+
# @return [String] a hexa-decimal signature.
|
71
|
+
def sign(blob, chain_id = nil)
|
72
|
+
context = Secp256k1::Context.new
|
73
|
+
compact, recovery_id = context.sign_recoverable(@private_key, blob).compact
|
74
|
+
signature = compact.bytes
|
75
|
+
v = Chain.to_v recovery_id, chain_id
|
76
|
+
is_leading_zero = true
|
77
|
+
[v].pack("N").unpack("C*").each do |byte|
|
78
|
+
is_leading_zero = false if byte > 0 and is_leading_zero
|
79
|
+
signature.append byte unless is_leading_zero and byte === 0
|
80
|
+
end
|
81
|
+
Util.bin_to_hex signature.pack "c*"
|
39
82
|
end
|
40
83
|
|
41
|
-
|
42
|
-
|
84
|
+
# Prefixes a message with "\x19Ethereum Signed Message:" and signs
|
85
|
+
# it in the common way used by many web3 wallets. Complies with
|
86
|
+
# EIP-191 prefix 0x19 and version byte 0x45 (E).
|
87
|
+
#
|
88
|
+
# @param message [String] the message string to be prefixed and signed.
|
89
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
90
|
+
# @return [String] an EIP-191 conform, hexa-decimal signature.
|
91
|
+
def personal_sign(message, chain_id = nil)
|
92
|
+
prefixed_message = Signature.prefix_message message
|
93
|
+
hashed_message = Util.keccak256 prefixed_message
|
94
|
+
sign hashed_message, chain_id
|
43
95
|
end
|
44
96
|
|
45
|
-
|
97
|
+
# Prefixes, hashes, and signes a typed data structure in the common
|
98
|
+
# way used by many web3 wallets. Complies with EIP-191 prefix 0x19
|
99
|
+
# and EIP-712 version byte 0x01. Supports `V3`, `V4`.
|
100
|
+
#
|
101
|
+
# @param typed_data [Array] all the data in the typed data structure to be signed.
|
102
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
103
|
+
# @return [String] an EIP-712 conform, hexa-decimal signature.
|
104
|
+
def sign_typed_data(typed_data, chain_id = nil)
|
105
|
+
hash_to_sign = Eip712.hash typed_data
|
106
|
+
sign hash_to_sign, chain_id
|
107
|
+
end
|
46
108
|
|
47
|
-
|
48
|
-
|
109
|
+
# Converts the private key data into a hexa-decimal string.
|
110
|
+
#
|
111
|
+
# @return [String] private key as hexa-decimal string.
|
112
|
+
def private_hex
|
113
|
+
Util.bin_to_hex @private_key.data
|
49
114
|
end
|
50
115
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
116
|
+
# Exports the private key bytes in a wrapper function to maintain
|
117
|
+
# backward-compatibility with older versions of `Eth::Key`.
|
118
|
+
#
|
119
|
+
# @return [String] private key as packed byte-string.
|
120
|
+
def private_bytes
|
121
|
+
@private_key.data
|
56
122
|
end
|
57
123
|
|
58
|
-
|
59
|
-
|
60
|
-
|
124
|
+
# Converts the public key data into an uncompressed
|
125
|
+
# hexa-decimal string.
|
126
|
+
#
|
127
|
+
# @return [String] public key as uncompressed hexa-decimal string.
|
128
|
+
def public_hex
|
129
|
+
Util.bin_to_hex @public_key.uncompressed
|
61
130
|
end
|
62
131
|
|
63
|
-
|
64
|
-
|
132
|
+
# Converts the public key data into an compressed
|
133
|
+
# hexa-decimal string.
|
134
|
+
#
|
135
|
+
# @return [String] public key as compressed hexa-decimal string.
|
136
|
+
def public_hex_compressed
|
137
|
+
Util.bin_to_hex @public_key.compressed
|
65
138
|
end
|
66
139
|
|
67
|
-
|
140
|
+
# Exports the uncompressed public key bytes in a wrapper function to
|
141
|
+
# maintain backward-compatibility with older versions of `Eth::Key`.
|
142
|
+
#
|
143
|
+
# @return [String] uncompressed public key as packed byte-string.
|
144
|
+
def public_bytes
|
145
|
+
@public_key.uncompressed
|
146
|
+
end
|
68
147
|
|
69
|
-
|
70
|
-
|
148
|
+
# Exports the compressed public key bytes.
|
149
|
+
#
|
150
|
+
# @return [String] compressed public key as packed byte-string.
|
151
|
+
def public_bytes_compressed
|
152
|
+
@public_key.compressed
|
71
153
|
end
|
72
154
|
|
73
|
-
|
74
|
-
|
75
|
-
|
155
|
+
# Exports the checksummed public address.
|
156
|
+
#
|
157
|
+
# @return [Eth::Address] compressed address as packed hex prefixed string.
|
158
|
+
def address
|
159
|
+
Util.public_key_to_address public_bytes
|
76
160
|
end
|
77
161
|
end
|
78
162
|
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# Copyright (c) 2016-2022 The Ruby-Eth Contributors
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "rbsecp256k1"
|
16
|
+
|
17
|
+
# Provides the `Eth` module.
|
18
|
+
module Eth
|
19
|
+
|
20
|
+
# Defines handy tools for verifying and recovering signatures.
|
21
|
+
module Signature
|
22
|
+
extend self
|
23
|
+
|
24
|
+
# Provides a special signature error if signature is invalid.
|
25
|
+
class SignatureError < StandardError; end
|
26
|
+
|
27
|
+
# EIP-191 prefix byte 0x19
|
28
|
+
EIP191_PREFIX_BYTE = "\x19".freeze
|
29
|
+
|
30
|
+
# EIP-712 version byte 0x01
|
31
|
+
EIP712_VERSION_BYTE = "\x01".freeze
|
32
|
+
|
33
|
+
# Prefix message as per EIP-191 with 0x19 to ensure the data is not
|
34
|
+
# valid RLP and thus not mistaken for a transaction.
|
35
|
+
# EIP-191 Version byte: 0x45 (E)
|
36
|
+
# ref: https://eips.ethereum.org/EIPS/eip-191
|
37
|
+
#
|
38
|
+
# @param message [String] the message string to be prefixed.
|
39
|
+
# @return [String] an EIP-191 prefixed string
|
40
|
+
def prefix_message(message)
|
41
|
+
"#{EIP191_PREFIX_BYTE}Ethereum Signed Message:\n#{message.size}#{message}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Dissects a signature blob of 65 bytes into its r, s, and v values.
|
45
|
+
#
|
46
|
+
# @param signature [String] a Secp256k1 signature.
|
47
|
+
# @return [String, String, String] the r, s, and v values.
|
48
|
+
# @raise [SignatureError] if signature is of unknown size.
|
49
|
+
def dissect(signature)
|
50
|
+
signature = Util.bin_to_hex signature unless Util.is_hex? signature
|
51
|
+
signature = Util.remove_hex_prefix signature
|
52
|
+
if signature.size < 130
|
53
|
+
raise SignatureError, "Unknown signature length #{signature.size}!"
|
54
|
+
end
|
55
|
+
r = signature[0, 64]
|
56
|
+
s = signature[64, 64]
|
57
|
+
v = signature[128..]
|
58
|
+
return r, s, v
|
59
|
+
end
|
60
|
+
|
61
|
+
# Recovers a signature from arbitrary data without validation on a given chain.
|
62
|
+
#
|
63
|
+
# @param blob [String] that arbitrary data to be recovered.
|
64
|
+
# @param signature [String] the hex string containing the signature.
|
65
|
+
# @param chain_id [Integer] the chain ID the signature should be recovered from.
|
66
|
+
# @return [String] a hexa-decimal, uncompressed public key.
|
67
|
+
# @raise [SignatureError] if signature is of invalid size or invalid v.
|
68
|
+
def recover(blob, signature, chain_id = Chain::ETHEREUM)
|
69
|
+
context = Secp256k1::Context.new
|
70
|
+
r, s, v = dissect signature
|
71
|
+
v = v.to_i(16)
|
72
|
+
raise SignatureError, "Invalid signature v byte #{v} for chain ID #{chain_id}!" if v < chain_id
|
73
|
+
recovery_id = Chain.to_recovery_id v, chain_id
|
74
|
+
signature_rs = Util.hex_to_bin "#{r}#{s}"
|
75
|
+
recoverable_signature = context.recoverable_signature_from_compact signature_rs, recovery_id
|
76
|
+
public_key = recoverable_signature.recover_public_key blob
|
77
|
+
Util.bin_to_hex public_key.uncompressed
|
78
|
+
end
|
79
|
+
|
80
|
+
# Recovers a public key from a prefixed, personal message and
|
81
|
+
# a signature on a given chain. (EIP-191)
|
82
|
+
#
|
83
|
+
# @param message [String] the message string.
|
84
|
+
# @param signature [String] the hex string containing the signature.
|
85
|
+
# @param chain_id [Integer] the chain ID the signature should be recovered from.
|
86
|
+
# @return [String] a hexa-decimal, uncompressed public key.
|
87
|
+
def personal_recover(message, signature, chain_id = Chain::ETHEREUM)
|
88
|
+
prefixed_message = prefix_message message
|
89
|
+
hashed_message = Util.keccak256 prefixed_message
|
90
|
+
recover hashed_message, signature, chain_id
|
91
|
+
end
|
92
|
+
|
93
|
+
# Recovers a public key from a typed data structure and a signature
|
94
|
+
# on a given chain. (EIP-712)
|
95
|
+
#
|
96
|
+
# @param typed_data [Array] all the data in the typed data structure to be recovered.
|
97
|
+
# @param signature [String] the hex string containing the signature.
|
98
|
+
# @param chain_id [Integer] the chain ID the signature should be recovered from.
|
99
|
+
# @return [String] a hexa-decimal, uncompressed public key.
|
100
|
+
def recover_typed_data(typed_data, signature, chain_id = Chain::ETHEREUM)
|
101
|
+
hash_to_sign = Eip712.hash typed_data
|
102
|
+
recover hash_to_sign, signature, chain_id
|
103
|
+
end
|
104
|
+
|
105
|
+
# Verifies a signature for a given public key or address.
|
106
|
+
#
|
107
|
+
# @param blob [String] that arbitrary data to be verified.
|
108
|
+
# @param signature [String] the hex string containing the signature.
|
109
|
+
# @param public_key [String] either a public key or an Ethereum address.
|
110
|
+
# @param chain_id [Integer] the chain ID used to sign.
|
111
|
+
# @return [Boolean] true if signature matches provided public key.
|
112
|
+
# @raise [SignatureError] if it cannot determine the type of data or public key.
|
113
|
+
def verify(blob, signature, public_key, chain_id = Chain::ETHEREUM)
|
114
|
+
recovered_key = nil
|
115
|
+
if blob.instance_of? Array or blob.instance_of? Hash
|
116
|
+
|
117
|
+
# recover Array from sign_typed_data
|
118
|
+
recovered_key = recover_typed_data blob, signature, chain_id
|
119
|
+
elsif blob.instance_of? String and blob.encoding != Encoding::ASCII_8BIT
|
120
|
+
|
121
|
+
# recover message from personal_sign
|
122
|
+
recovered_key = personal_recover blob, signature, chain_id
|
123
|
+
elsif blob.instance_of? String and (Util.is_hex? blob or blob.encoding == Encoding::ASCII_8BIT)
|
124
|
+
|
125
|
+
# if nothing else, recover from arbitrary signature
|
126
|
+
recovered_key = recover blob, signature, chain_id
|
127
|
+
end
|
128
|
+
|
129
|
+
# raise if we cannot determine the data format
|
130
|
+
raise SignatureError, "Unknown data format to verify: #{blob}" if recovered_key.nil?
|
131
|
+
|
132
|
+
if public_key.instance_of? Address
|
133
|
+
|
134
|
+
# recovering using an Eth::Address
|
135
|
+
address = public_key.to_s
|
136
|
+
recovered_address = Util.public_key_to_address(recovered_key).to_s
|
137
|
+
return address == recovered_address
|
138
|
+
elsif public_key.instance_of? Secp256k1::PublicKey
|
139
|
+
|
140
|
+
# recovering using an Secp256k1::PublicKey
|
141
|
+
public_hex = Util.bin_to_hex public_key.uncompressed
|
142
|
+
return public_hex == recovered_key
|
143
|
+
elsif public_key.size == 42
|
144
|
+
|
145
|
+
# recovering using an address String
|
146
|
+
address = Address.new(public_key).to_s
|
147
|
+
recovered_address = Util.public_key_to_address(recovered_key).to_s
|
148
|
+
return address == recovered_address
|
149
|
+
elsif public_key.size == 130
|
150
|
+
|
151
|
+
# recovering using an uncompressed public key String
|
152
|
+
return public_key == recovered_key
|
153
|
+
else
|
154
|
+
|
155
|
+
# raise if we cannot determine the public key format used
|
156
|
+
raise SignatureError, "Invalid public key or address supplied #{public_key}!"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|