eth 0.4.12 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,113 +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
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
- 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
24
42
 
25
- 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
26
52
 
27
- def derive_key(password)
28
- case kdf
29
- when 'pbkdf2'
30
- @key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
31
- when 'scrypt'
32
- # 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)
33
- @key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
34
- else
35
- 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
36
68
  end
37
- end
38
69
 
39
- def check_macs
40
- mac1 = keccak256(key[(key_length/2), key_length] + ciphertext)
41
- 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"]
42
73
 
43
- if mac1 != mac2
44
- raise "Message Authentications Codes do not match!"
74
+ if mac1 != mac2
75
+ raise DecrypterError, "Message Authentications Codes do not match!"
76
+ end
45
77
  end
46
- end
47
78
 
48
- def decrypted_data
49
- @decrypted_data ||= cipher.update(ciphertext) + cipher.final
50
- end
79
+ def decrypted_data
80
+ @decrypted_data ||= cipher.update(ciphertext) + cipher.final
81
+ end
51
82
 
52
- def crypto_data
53
- @crypto_data ||= data['crypto'] || data['Crypto']
54
- end
83
+ def crypto_data
84
+ @crypto_data ||= data["crypto"] || data["Crypto"]
85
+ end
55
86
 
56
- def ciphertext
57
- hex_to_bin crypto_data['ciphertext']
58
- end
87
+ def ciphertext
88
+ Util.hex_to_bin crypto_data["ciphertext"]
89
+ end
59
90
 
60
- def cipher_name
61
- "aes-128-ctr"
62
- end
91
+ def cipher_name
92
+ "aes-128-ctr"
93
+ end
63
94
 
64
- def cipher
65
- @cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
66
- cipher.decrypt
67
- cipher.key = key[0, (key_length/2)]
68
- 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
69
101
  end
70
- end
71
102
 
72
- def iv
73
- hex_to_bin crypto_data['cipherparams']['iv']
74
- end
103
+ def iv
104
+ Util.hex_to_bin crypto_data["cipherparams"]["iv"]
105
+ end
75
106
 
76
- def salt
77
- hex_to_bin crypto_data['kdfparams']['salt']
78
- end
107
+ def salt
108
+ Util.hex_to_bin crypto_data["kdfparams"]["salt"]
109
+ end
79
110
 
80
- def iterations
81
- crypto_data['kdfparams']['c'].to_i
82
- end
111
+ def iterations
112
+ crypto_data["kdfparams"]["c"].to_i
113
+ end
83
114
 
84
- def kdf
85
- crypto_data['kdf']
86
- end
115
+ def kdf
116
+ crypto_data["kdf"]
117
+ end
87
118
 
88
- def key_length
89
- crypto_data['kdfparams']['dklen'].to_i
90
- end
119
+ def key_length
120
+ crypto_data["kdfparams"]["dklen"].to_i
121
+ end
91
122
 
92
- def n
93
- crypto_data['kdfparams']['n'].to_i
94
- end
123
+ def n
124
+ crypto_data["kdfparams"]["n"].to_i
125
+ end
95
126
 
96
- def r
97
- crypto_data['kdfparams']['r'].to_i
98
- end
127
+ def r
128
+ crypto_data["kdfparams"]["r"].to_i
129
+ end
99
130
 
100
- def p
101
- crypto_data['kdfparams']['p'].to_i
102
- end
131
+ def p
132
+ crypto_data["kdfparams"]["p"].to_i
133
+ end
103
134
 
104
- def digest
105
- OpenSSL::Digest.new digest_name
106
- end
135
+ def digest
136
+ OpenSSL::Digest.new digest_name
137
+ end
107
138
 
108
- def digest_name
109
- "sha256"
139
+ def digest_name
140
+ "sha256"
141
+ end
110
142
  end
111
-
112
-
113
143
  end