eth 0.4.18 → 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.
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
@@ -1,110 +1,143 @@
1
- require "json"
2
- require "scrypt"
3
-
4
- class Eth::Key::Decrypter
5
- include Eth::Utils
6
-
7
- def self.perform(data, password)
8
- new(data, password).perform
9
- end
10
-
11
- def initialize(data, password)
12
- @data = JSON.parse(data)
13
- @password = password
14
- end
15
-
16
- def perform
17
- derive_key password
18
- check_macs
19
- bin_to_hex decrypted_data
20
- 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::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
21
32
 
22
- private
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
23
42
 
24
- attr_reader :data, :key, :password
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
25
52
 
26
- def derive_key(password)
27
- case kdf
28
- when "pbkdf2"
29
- @key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
30
- when "scrypt"
31
- # OpenSSL 1.1 inclues OpenSSL::KDF.scrypt, but it is not available usually, otherwise we could do: OpenSSL::KDF.scrypt(password, salt: salt, N: n, r: r, p: p, length: key_length)
32
- @key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
33
- else
34
- raise "Unsupported key derivation function: #{kdf}!"
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
35
68
  end
36
- end
37
69
 
38
- def check_macs
39
- mac1 = keccak256(key[(key_length / 2), key_length] + ciphertext)
40
- mac2 = hex_to_bin crypto_data["mac"]
70
+ def check_macs
71
+ mac1 = Util.keccak256(key[(key_length / 2), key_length] + ciphertext)
72
+ mac2 = Util.hex_to_bin crypto_data["mac"]
41
73
 
42
- if mac1 != mac2
43
- raise "Message Authentications Codes do not match!"
74
+ if mac1 != mac2
75
+ raise DecrypterError, "Message Authentications Codes do not match!"
76
+ end
44
77
  end
45
- end
46
78
 
47
- def decrypted_data
48
- @decrypted_data ||= cipher.update(ciphertext) + cipher.final
49
- end
79
+ def decrypted_data
80
+ @decrypted_data ||= cipher.update(ciphertext) + cipher.final
81
+ end
50
82
 
51
- def crypto_data
52
- @crypto_data ||= data["crypto"] || data["Crypto"]
53
- end
83
+ def crypto_data
84
+ @crypto_data ||= data["crypto"] || data["Crypto"]
85
+ end
54
86
 
55
- def ciphertext
56
- hex_to_bin crypto_data["ciphertext"]
57
- end
87
+ def ciphertext
88
+ Util.hex_to_bin crypto_data["ciphertext"]
89
+ end
58
90
 
59
- def cipher_name
60
- "aes-128-ctr"
61
- end
91
+ def cipher_name
92
+ "aes-128-ctr"
93
+ end
62
94
 
63
- def cipher
64
- @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
65
- cipher.decrypt
66
- cipher.key = key[0, (key_length / 2)]
67
- cipher.iv = iv
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
68
101
  end
69
- end
70
102
 
71
- def iv
72
- hex_to_bin crypto_data["cipherparams"]["iv"]
73
- end
103
+ def iv
104
+ Util.hex_to_bin crypto_data["cipherparams"]["iv"]
105
+ end
74
106
 
75
- def salt
76
- hex_to_bin crypto_data["kdfparams"]["salt"]
77
- end
107
+ def salt
108
+ Util.hex_to_bin crypto_data["kdfparams"]["salt"]
109
+ end
78
110
 
79
- def iterations
80
- crypto_data["kdfparams"]["c"].to_i
81
- end
111
+ def iterations
112
+ crypto_data["kdfparams"]["c"].to_i
113
+ end
82
114
 
83
- def kdf
84
- crypto_data["kdf"]
85
- end
115
+ def kdf
116
+ crypto_data["kdf"]
117
+ end
86
118
 
87
- def key_length
88
- crypto_data["kdfparams"]["dklen"].to_i
89
- end
119
+ def key_length
120
+ crypto_data["kdfparams"]["dklen"].to_i
121
+ end
90
122
 
91
- def n
92
- crypto_data["kdfparams"]["n"].to_i
93
- end
123
+ def n
124
+ crypto_data["kdfparams"]["n"].to_i
125
+ end
94
126
 
95
- def r
96
- crypto_data["kdfparams"]["r"].to_i
97
- end
127
+ def r
128
+ crypto_data["kdfparams"]["r"].to_i
129
+ end
98
130
 
99
- def p
100
- crypto_data["kdfparams"]["p"].to_i
101
- end
131
+ def p
132
+ crypto_data["kdfparams"]["p"].to_i
133
+ end
102
134
 
103
- def digest
104
- OpenSSL::Digest.new digest_name
105
- end
135
+ def digest
136
+ OpenSSL::Digest.new digest_name
137
+ end
106
138
 
107
- def digest_name
108
- "sha256"
139
+ def digest_name
140
+ "sha256"
141
+ end
109
142
  end
110
143
  end