eth 0.4.12 → 0.5.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.
@@ -1,128 +1,205 @@
1
- require 'json'
2
- require 'securerandom'
3
-
4
- class Eth::Key::Encrypter
5
- include Eth::Utils
6
-
7
- def self.perform(key, password, options = {})
8
- new(key, options).perform(password)
9
- end
10
-
11
- def initialize(key, options = {})
12
- @key = key
13
- @options = options
14
- end
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
15
40
 
16
- def perform(password)
17
- derive_key password
18
- encrypt
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
19
63
 
20
- data.to_json
21
- end
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
22
73
 
23
- def data
24
- {
25
- crypto: {
26
- cipher: cipher_name,
27
- cipherparams: {
28
- iv: bin_to_hex(iv),
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),
29
107
  },
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]
108
+ id: id,
109
+ version: 3,
110
+ }
44
111
  end
45
- end
46
112
 
47
- def id
48
- @id ||= options[:id] || SecureRandom.uuid
49
- end
113
+ private
50
114
 
115
+ attr_reader :derived_key, :encrypted_key, :key, :options
51
116
 
52
- private
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
123
+ end
53
124
 
54
- attr_reader :derived_key, :encrypted_key, :key, :options
125
+ def digest
126
+ @digest ||= OpenSSL::Digest.new digest_name
127
+ end
55
128
 
56
- def cipher
57
- @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
58
- cipher.encrypt
59
- cipher.iv = iv
60
- cipher.key = derived_key[0, (key_length/2)]
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
61
135
  end
62
- end
63
136
 
64
- def digest
65
- @digest ||= OpenSSL::Digest.new digest_name
66
- end
137
+ def encrypt
138
+ @encrypted_key = cipher.update(Util.hex_to_bin key.private_hex) + cipher.final
139
+ end
67
140
 
68
- def derive_key(password)
69
- @derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
70
- end
141
+ def mac
142
+ Util.keccak256(derived_key[(key_length / 2), key_length] + encrypted_key)
143
+ end
71
144
 
72
- def encrypt
73
- @encrypted_key = cipher.update(hex_to_bin key) + cipher.final
74
- end
145
+ def kdf
146
+ options[:kdf] || "pbkdf2"
147
+ end
75
148
 
76
- def mac
77
- keccak256(derived_key[(key_length/2), key_length] + encrypted_key)
78
- end
149
+ def cipher_name
150
+ "aes-128-ctr"
151
+ end
79
152
 
80
- def cipher_name
81
- "aes-128-ctr"
82
- end
153
+ def digest_name
154
+ "sha256"
155
+ end
83
156
 
84
- def digest_name
85
- "sha256"
86
- end
157
+ def prf
158
+ "hmac-#{digest_name}"
159
+ end
87
160
 
88
- def prf
89
- "hmac-#{digest_name}"
90
- end
161
+ def key_length
162
+ 32
163
+ end
91
164
 
92
- def key_length
93
- 32
94
- end
165
+ def salt_length
166
+ 32
167
+ end
95
168
 
96
- def salt_length
97
- 32
98
- end
169
+ def iv_length
170
+ 16
171
+ end
99
172
 
100
- def iv_length
101
- 16
102
- end
173
+ def id
174
+ @id ||= options[:id] || SecureRandom.uuid
175
+ end
103
176
 
104
- def iterations
105
- options[:iterations] || 262_144
106
- end
177
+ def iterations
178
+ options[:iterations] || 262_144
179
+ end
107
180
 
108
- def salt
109
- @salt ||= if options[:salt]
110
- hex_to_bin options[:salt]
111
- else
112
- SecureRandom.random_bytes(salt_length)
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
113
187
  end
114
- end
115
188
 
116
- def iv
117
- @iv ||= if options[:iv]
118
- hex_to_bin options[:iv]
119
- else
120
- SecureRandom.random_bytes(iv_length)
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
121
195
  end
122
- end
123
196
 
124
- def address
125
- Eth::Key.new(priv: key).address
126
- end
197
+ def parallelization
198
+ options[:parallelization] || 8
199
+ end
127
200
 
201
+ def block_size
202
+ options[:block_size] || 1
203
+ end
204
+ end
128
205
  end
data/lib/eth/key.rb CHANGED
@@ -1,79 +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
3
- autoload :Decrypter, 'eth/key/decrypter'
4
- autoload :Encrypter, 'eth/key/encrypter'
5
26
 
6
- attr_reader :private_key, :public_key
27
+ # The Eth::Key::Decrypter class to handle PBKDF2-SHA-256 decryption.
28
+ autoload :Decrypter, "eth/key/decrypter"
7
29
 
8
- def self.encrypt(key, password)
9
- key = new(priv: key) unless key.is_a?(Key)
30
+ # The Eth::Key::Encrypter class to handle PBKDF2-SHA-256 encryption.
31
+ autoload :Encrypter, "eth/key/encrypter"
10
32
 
11
- Encrypter.perform key.private_hex, password
12
- end
33
+ # The `Secp256k1::PrivateKey` of the `Eth::Key` pair.
34
+ attr_reader :private_key
13
35
 
14
- def self.decrypt(data, password)
15
- priv = Decrypter.perform data, password
16
- new priv: priv
17
- end
18
-
19
- def self.personal_recover(message, signature)
20
- bin_signature = Utils.hex_to_bin(signature).bytes.rotate(-1).pack('c*')
21
- OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), bin_signature)
22
- end
36
+ # The `Secp256k1::PublicKey` of the `Eth::Key` pair.
37
+ attr_reader :public_key
23
38
 
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).
24
43
  def initialize(priv: nil)
25
- @private_key = MoneyTree::PrivateKey.new key: priv
26
- @public_key = MoneyTree::PublicKey.new private_key, compressed: false
27
- end
28
44
 
29
- def private_hex
30
- private_key.to_hex
31
- end
45
+ # Creates a new, randomized libsecp256k1 context.
46
+ ctx = Secp256k1::Context.new context_randomization_bytes: SecureRandom.random_bytes(32)
32
47
 
33
- def public_bytes
34
- public_key.to_bytes
35
- end
48
+ # Creates a new random key pair (public, private).
49
+ key = ctx.generate_key_pair
36
50
 
37
- def public_hex
38
- public_key.to_hex
51
+ unless priv.nil?
52
+
53
+ # Converts hex private keys to binary strings.
54
+ priv = Util.hex_to_bin priv if Util.is_hex? priv
55
+
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
39
63
  end
40
64
 
41
- def address
42
- Utils.public_key_to_address public_hex
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*"
43
82
  end
44
- alias_method :to_address, :address
45
83
 
46
- def sign(message)
47
- sign_hash message_hash(message)
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
48
95
  end
49
96
 
50
- def sign_hash(hash)
51
- loop do
52
- signature = OpenSsl.sign_compact hash, private_hex, public_hex
53
- return signature if valid_s? signature
54
- end
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
55
107
  end
56
108
 
57
- def verify_signature(message, signature)
58
- hash = message_hash(message)
59
- public_hex == OpenSsl.recover_compact(hash, signature)
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
60
114
  end
61
115
 
62
- def personal_sign(message)
63
- Utils.bin_to_hex(sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*'))
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
64
122
  end
65
123
 
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
130
+ end
66
131
 
67
- private
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
138
+ end
68
139
 
69
- def message_hash(message)
70
- Utils.keccak256 message
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
71
146
  end
72
147
 
73
- def valid_s?(signature)
74
- s_value = Utils.v_r_s_for(signature).last
75
- s_value <= Secp256k1::N/2 && s_value != 0
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
76
153
  end
77
154
 
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
160
+ end
78
161
  end
79
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