eth 0.4.12 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/codeql.yml +44 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +41 -0
- data/.gitignore +42 -9
- data/.gitmodules +3 -3
- data/AUTHORS.txt +16 -0
- data/CHANGELOG.md +18 -13
- data/Gemfile +15 -2
- data/LICENSE.txt +202 -21
- data/README.md +157 -81
- data/bin/console +4 -5
- data/bin/setup +4 -2
- data/eth.gemspec +46 -24
- 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 +48 -11
- data/lib/eth/chain.rb +148 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +118 -88
- data/lib/eth/key/encrypter.rb +176 -99
- data/lib/eth/key.rb +131 -48
- 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 +274 -143
- 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 +50 -61
- data/.travis.yml +0 -10
- data/lib/eth/gas.rb +0 -9
- data/lib/eth/open_ssl.rb +0 -264
- data/lib/eth/secp256k1.rb +0 -7
- data/lib/eth/sedes.rb +0 -40
- data/lib/eth/utils.rb +0 -130
data/lib/eth/eip712.rb
ADDED
@@ -0,0 +1,184 @@
|
|
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
|
+
# Defines handy tools for encoding typed structured data as per EIP-712.
|
19
|
+
# ref: https://eips.ethereum.org/EIPS/eip-712
|
20
|
+
module Eip712
|
21
|
+
extend self
|
22
|
+
|
23
|
+
# Provides a special typed-data error if data structure fails basic
|
24
|
+
# verification.
|
25
|
+
class TypedDataError < StandardError; end
|
26
|
+
|
27
|
+
# Scans all dependencies of a given type recursively and returns
|
28
|
+
# either all dependencies or none if not found.
|
29
|
+
#
|
30
|
+
# @param primary_type [String] the primary type which we want to scan.
|
31
|
+
# @param types [Array] all existing types in the data structure.
|
32
|
+
# @param result [Array] found results from previous recursions.
|
33
|
+
# @return [Array] all dependent types for the given primary type.
|
34
|
+
def type_dependencies(primary_type, types, result = [])
|
35
|
+
if result.include? primary_type
|
36
|
+
|
37
|
+
# ignore if we already have the give type in results
|
38
|
+
return result
|
39
|
+
elsif types[primary_type.to_sym].nil?
|
40
|
+
|
41
|
+
# ignore if the type is not used, e.g., a string or address.
|
42
|
+
return result
|
43
|
+
else
|
44
|
+
|
45
|
+
# we found something
|
46
|
+
result.push primary_type
|
47
|
+
|
48
|
+
# recursively look for further nested dependencies
|
49
|
+
types[primary_type.to_sym].each do |t|
|
50
|
+
dependency = type_dependencies t[:type], types, result
|
51
|
+
end
|
52
|
+
return result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Encode types as an EIP-712 confrom string, e.g.,
|
57
|
+
# `MyType(string attribute)`.
|
58
|
+
#
|
59
|
+
# @param primary_type [String] the type which we want to encode.
|
60
|
+
# @param types [Array] all existing types in the data structure.
|
61
|
+
# @return [String] an EIP-712 encoded type-string.
|
62
|
+
# @raise [TypedDataError] if non-primary type found.
|
63
|
+
def encode_type(primary_type, types)
|
64
|
+
|
65
|
+
# get all used types
|
66
|
+
all_dependencies = type_dependencies primary_type, types
|
67
|
+
|
68
|
+
# remove primary types and sort the rest alphabetically
|
69
|
+
filtered_dependencies = all_dependencies.delete_if { |type| type.to_s == primary_type }
|
70
|
+
sorted_dependencies = filtered_dependencies.sort
|
71
|
+
dependencies = [primary_type]
|
72
|
+
sorted_dependencies.each do |sorted|
|
73
|
+
dependencies.push sorted
|
74
|
+
end
|
75
|
+
|
76
|
+
# join them all in a string with types and field names
|
77
|
+
result = ""
|
78
|
+
dependencies.each do |type|
|
79
|
+
|
80
|
+
# dependencies should not have non-primary types (such as string, address)
|
81
|
+
raise TypedDataError, "Non-primary type found: #{type}!" if types[type.to_sym].nil?
|
82
|
+
|
83
|
+
result += "#{type}("
|
84
|
+
result += types[type.to_sym].map { |t| "#{t[:type]} #{t[:name]}" }.join(",")
|
85
|
+
result += ")"
|
86
|
+
end
|
87
|
+
return result
|
88
|
+
end
|
89
|
+
|
90
|
+
# Hashes an EIP-712 confrom type-string.
|
91
|
+
#
|
92
|
+
# @param primary_type [String] the type which we want to hash.
|
93
|
+
# @param types [Array] all existing types in the data structure.
|
94
|
+
# @return [String] a Keccak-256 hash of an EIP-712 encoded type-string.
|
95
|
+
def hash_type(primary_type, types)
|
96
|
+
encoded_type = encode_type primary_type, types
|
97
|
+
return Util.keccak256 encoded_type
|
98
|
+
end
|
99
|
+
|
100
|
+
# Recursively ABI-encodes all data and types according to EIP-712.
|
101
|
+
#
|
102
|
+
# @param primary_type [String] the primary type which we want to encode.
|
103
|
+
# @param data [Array] the data in the data structure we want to encode.
|
104
|
+
# @param types [Array] all existing types in the data structure.
|
105
|
+
# @return [String] an ABI-encoded representation of the data and the types.
|
106
|
+
def encode_data(primary_type, data, types)
|
107
|
+
|
108
|
+
# first data field is the type hash
|
109
|
+
encoded_types = ["bytes32"]
|
110
|
+
encoded_values = [hash_type(primary_type, types)]
|
111
|
+
|
112
|
+
# adds field contents
|
113
|
+
types[primary_type.to_sym].each do |field|
|
114
|
+
value = data[field[:name].to_sym]
|
115
|
+
type = field[:type]
|
116
|
+
raise NotImplementedError, "Arrays currently unimplemented for EIP-712." if type.end_with? "]"
|
117
|
+
if type == "string" or type == "bytes"
|
118
|
+
encoded_types.push "bytes32"
|
119
|
+
encoded_values.push Util.keccak256 value
|
120
|
+
elsif !types[type.to_sym].nil?
|
121
|
+
encoded_types.push "bytes32"
|
122
|
+
value = encode_data type, value, types
|
123
|
+
encoded_values.push Util.keccak256 value
|
124
|
+
else
|
125
|
+
encoded_types.push type
|
126
|
+
encoded_values.push value
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# all data is abi-encoded
|
131
|
+
return Abi.encode encoded_types, encoded_values
|
132
|
+
end
|
133
|
+
|
134
|
+
# Recursively ABI-encodes and hashes all data and types.
|
135
|
+
#
|
136
|
+
# @param primary_type [String] the primary type which we want to hash.
|
137
|
+
# @param data [Array] the data in the data structure we want to hash.
|
138
|
+
# @param types [Array] all existing types in the data structure.
|
139
|
+
# @return [String] a Keccak-256 hash of the ABI-encoded data and types.
|
140
|
+
def hash_data(primary_type, data, types)
|
141
|
+
encoded_data = encode_data primary_type, data, types
|
142
|
+
return Util.keccak256 encoded_data
|
143
|
+
end
|
144
|
+
|
145
|
+
# Enforces basic properties to be represented in the EIP-712 typed
|
146
|
+
# data structure: types, domain, message, etc.
|
147
|
+
#
|
148
|
+
# @param data [Array] the data in the data structure we want to hash.
|
149
|
+
# @return [Array] the data in the data structure we want to hash.
|
150
|
+
# @raise [TypedDataError] if the data fails validation.
|
151
|
+
def enforce_typed_data(data)
|
152
|
+
data = JSON.parse data if Util.is_hex? data
|
153
|
+
raise TypedDataError, "Data is missing, try again with data." if data.nil? or data.empty?
|
154
|
+
raise TypedDataError, "Data types are missing." if data[:types].nil? or data[:types].empty?
|
155
|
+
raise TypedDataError, "Data primaryType is missing." if data[:primaryType].nil? or data[:primaryType].empty?
|
156
|
+
raise TypedDataError, "Data domain is missing." if data[:domain].nil?
|
157
|
+
raise TypedDataError, "Data message is missing." if data[:message].nil? or data[:message].empty?
|
158
|
+
raise TypedDataError, "Data EIP712Domain is missing." if data[:types][:EIP712Domain].nil?
|
159
|
+
return data
|
160
|
+
end
|
161
|
+
|
162
|
+
# Hashes a typed data structure with Keccak-256 to prepare a signed
|
163
|
+
# typed data operation respecting EIP-712.
|
164
|
+
#
|
165
|
+
# @param data [Array] all the data in the typed data structure.
|
166
|
+
# @return [String] a Keccak-256 hash of the EIP-712-encoded typed data.
|
167
|
+
def hash(data)
|
168
|
+
data = enforce_typed_data data
|
169
|
+
|
170
|
+
# EIP-191 prefix byte
|
171
|
+
buffer = Signature::EIP191_PREFIX_BYTE
|
172
|
+
|
173
|
+
# EIP-712 version byte
|
174
|
+
buffer += Signature::EIP712_VERSION_BYTE
|
175
|
+
|
176
|
+
# hashed domain data
|
177
|
+
buffer += hash_data "EIP712Domain", data[:domain], data[:types]
|
178
|
+
|
179
|
+
# hashed message data
|
180
|
+
buffer += hash_data data[:primaryType], data[:message], data[:types]
|
181
|
+
return Util.keccak256 buffer
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/eth/key/decrypter.rb
CHANGED
@@ -1,113 +1,143 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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::Decrypter class to handle PBKDF2-SHA-256 decryption.
|
19
|
+
class Key::Decrypter
|
20
|
+
|
21
|
+
# Provides a specific decrypter error if decryption fails.
|
22
|
+
class DecrypterError < StandardError; end
|
23
|
+
|
24
|
+
# Class method `Eth::Key::Decrypter.perform`
|
25
|
+
#
|
26
|
+
# @param data [JSON] encryption data including cypherkey
|
27
|
+
# @param password [String] password to decrypt the key
|
28
|
+
# @return [Eth::Key] decrypted key-pair
|
29
|
+
def self.perform(data, password)
|
30
|
+
new(data, password).perform
|
31
|
+
end
|
22
32
|
|
23
|
-
|
33
|
+
# Constructor of the `Eth::Key::Decrypter` class for secret key
|
34
|
+
# encryption.
|
35
|
+
#
|
36
|
+
# @param data [JSON] encryption data including cypherkey
|
37
|
+
# @param password [String] password to decrypt the key
|
38
|
+
def initialize(data, password)
|
39
|
+
@data = JSON.parse(data)
|
40
|
+
@password = password
|
41
|
+
end
|
24
42
|
|
25
|
-
|
43
|
+
# Method to decrypt key using password
|
44
|
+
#
|
45
|
+
# @return [String] decrypted key
|
46
|
+
def perform
|
47
|
+
derive_key password
|
48
|
+
check_macs
|
49
|
+
private_key = Util.bin_to_hex decrypted_data
|
50
|
+
Eth::Key.new priv: private_key
|
51
|
+
end
|
26
52
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :data
|
56
|
+
attr_reader :key
|
57
|
+
attr_reader :password
|
58
|
+
|
59
|
+
def derive_key(password)
|
60
|
+
case kdf
|
61
|
+
when "pbkdf2"
|
62
|
+
@key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
63
|
+
when "scrypt"
|
64
|
+
@key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
|
65
|
+
else
|
66
|
+
raise DecrypterError, "Unsupported key derivation function: #{kdf}!"
|
67
|
+
end
|
36
68
|
end
|
37
|
-
end
|
38
69
|
|
39
|
-
|
40
|
-
|
41
|
-
|
70
|
+
def check_macs
|
71
|
+
mac1 = Util.keccak256(key[(key_length / 2), key_length] + ciphertext)
|
72
|
+
mac2 = Util.hex_to_bin crypto_data["mac"]
|
42
73
|
|
43
|
-
|
44
|
-
|
74
|
+
if mac1 != mac2
|
75
|
+
raise DecrypterError, "Message Authentications Codes do not match!"
|
76
|
+
end
|
45
77
|
end
|
46
|
-
end
|
47
78
|
|
48
|
-
|
49
|
-
|
50
|
-
|
79
|
+
def decrypted_data
|
80
|
+
@decrypted_data ||= cipher.update(ciphertext) + cipher.final
|
81
|
+
end
|
51
82
|
|
52
|
-
|
53
|
-
|
54
|
-
|
83
|
+
def crypto_data
|
84
|
+
@crypto_data ||= data["crypto"] || data["Crypto"]
|
85
|
+
end
|
55
86
|
|
56
|
-
|
57
|
-
|
58
|
-
|
87
|
+
def ciphertext
|
88
|
+
Util.hex_to_bin crypto_data["ciphertext"]
|
89
|
+
end
|
59
90
|
|
60
|
-
|
61
|
-
|
62
|
-
|
91
|
+
def cipher_name
|
92
|
+
"aes-128-ctr"
|
93
|
+
end
|
63
94
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
95
|
+
def cipher
|
96
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
97
|
+
cipher.decrypt
|
98
|
+
cipher.key = key[0, (key_length / 2)]
|
99
|
+
cipher.iv = iv
|
100
|
+
end
|
69
101
|
end
|
70
|
-
end
|
71
102
|
|
72
|
-
|
73
|
-
|
74
|
-
|
103
|
+
def iv
|
104
|
+
Util.hex_to_bin crypto_data["cipherparams"]["iv"]
|
105
|
+
end
|
75
106
|
|
76
|
-
|
77
|
-
|
78
|
-
|
107
|
+
def salt
|
108
|
+
Util.hex_to_bin crypto_data["kdfparams"]["salt"]
|
109
|
+
end
|
79
110
|
|
80
|
-
|
81
|
-
|
82
|
-
|
111
|
+
def iterations
|
112
|
+
crypto_data["kdfparams"]["c"].to_i
|
113
|
+
end
|
83
114
|
|
84
|
-
|
85
|
-
|
86
|
-
|
115
|
+
def kdf
|
116
|
+
crypto_data["kdf"]
|
117
|
+
end
|
87
118
|
|
88
|
-
|
89
|
-
|
90
|
-
|
119
|
+
def key_length
|
120
|
+
crypto_data["kdfparams"]["dklen"].to_i
|
121
|
+
end
|
91
122
|
|
92
|
-
|
93
|
-
|
94
|
-
|
123
|
+
def n
|
124
|
+
crypto_data["kdfparams"]["n"].to_i
|
125
|
+
end
|
95
126
|
|
96
|
-
|
97
|
-
|
98
|
-
|
127
|
+
def r
|
128
|
+
crypto_data["kdfparams"]["r"].to_i
|
129
|
+
end
|
99
130
|
|
100
|
-
|
101
|
-
|
102
|
-
|
131
|
+
def p
|
132
|
+
crypto_data["kdfparams"]["p"].to_i
|
133
|
+
end
|
103
134
|
|
104
|
-
|
105
|
-
|
106
|
-
|
135
|
+
def digest
|
136
|
+
OpenSSL::Digest.new digest_name
|
137
|
+
end
|
107
138
|
|
108
|
-
|
109
|
-
|
139
|
+
def digest_name
|
140
|
+
"sha256"
|
141
|
+
end
|
110
142
|
end
|
111
|
-
|
112
|
-
|
113
143
|
end
|