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.
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