klay 0.0.1
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.
- checksums.yaml +7 -0
- data/.github/workflows/codeql.yml +48 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +52 -0
- data/.gitignore +43 -0
- data/.gitmodules +3 -0
- data/.yardopts +1 -0
- data/AUTHORS.txt +20 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +202 -0
- data/README.md +262 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/klay.gemspec +50 -0
- data/lib/klay/abi/type.rb +178 -0
- data/lib/klay/abi.rb +396 -0
- data/lib/klay/address.rb +106 -0
- data/lib/klay/api.rb +223 -0
- data/lib/klay/chain.rb +157 -0
- data/lib/klay/client/http.rb +63 -0
- data/lib/klay/client/ipc.rb +47 -0
- data/lib/klay/client.rb +232 -0
- data/lib/klay/constant.rb +71 -0
- data/lib/klay/eip712.rb +184 -0
- data/lib/klay/key/decrypter.rb +146 -0
- data/lib/klay/key/encrypter.rb +207 -0
- data/lib/klay/key.rb +167 -0
- data/lib/klay/rlp/decoder.rb +109 -0
- data/lib/klay/rlp/encoder.rb +78 -0
- data/lib/klay/rlp/sedes/big_endian_int.rb +66 -0
- data/lib/klay/rlp/sedes/binary.rb +97 -0
- data/lib/klay/rlp/sedes/list.rb +84 -0
- data/lib/klay/rlp/sedes.rb +74 -0
- data/lib/klay/rlp.rb +63 -0
- data/lib/klay/signature.rb +163 -0
- data/lib/klay/tx/eip1559.rb +336 -0
- data/lib/klay/tx/eip2930.rb +328 -0
- data/lib/klay/tx/legacy.rb +296 -0
- data/lib/klay/tx.rb +327 -0
- data/lib/klay/unit.rb +49 -0
- data/lib/klay/util.rb +235 -0
- data/lib/klay/version.rb +20 -0
- data/lib/klay.rb +35 -0
- metadata +164 -0
data/lib/klay/client.rb
ADDED
@@ -0,0 +1,232 @@
|
|
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 Klay
|
17
|
+
|
18
|
+
# Provides the {Eth::Client} super-class to connect to Ethereum
|
19
|
+
# network's RPC-API endpoints (IPC or HTTP).
|
20
|
+
class Client
|
21
|
+
|
22
|
+
# The client's RPC-request ID starting at 0.
|
23
|
+
attr_reader :id
|
24
|
+
|
25
|
+
# The connected network's chain ID.
|
26
|
+
attr_reader :chain_id
|
27
|
+
|
28
|
+
# The connected network's client coinbase.
|
29
|
+
attr_accessor :default_account
|
30
|
+
|
31
|
+
# The default transaction max priority fee per gas in Wei.
|
32
|
+
attr_accessor :max_priority_fee_per_gas
|
33
|
+
|
34
|
+
# The default transaction max fee per gas in Wei.
|
35
|
+
attr_accessor :max_fee_per_gas
|
36
|
+
|
37
|
+
# The default gas limit for the transaction.
|
38
|
+
attr_accessor :gas_limit
|
39
|
+
|
40
|
+
# Creates a new RPC-Client, either by providing an HTTP/S host or
|
41
|
+
# an IPC path.
|
42
|
+
#
|
43
|
+
# @param host [String] either an HTTP/S host or an IPC path.
|
44
|
+
# @return [Eth::Client::Ipc] an IPC client.
|
45
|
+
# @return [Eth::Client::Http] an HTTP client.
|
46
|
+
# @raise [ArgumentError] in case it cannot determine the client type.
|
47
|
+
def self.create(host)
|
48
|
+
return Client::Ipc.new host if host.end_with? ".ipc"
|
49
|
+
return Client::Http.new host if host.start_with? "http"
|
50
|
+
raise ArgumentError, "Unable to detect client type!"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Constructor for the {Eth::Client} super-class. Should not be used;
|
54
|
+
# use {Client.create} intead.
|
55
|
+
def initialize(_)
|
56
|
+
@id = 0
|
57
|
+
@max_priority_fee_per_gas = 0
|
58
|
+
@max_fee_per_gas = Tx::DEFAULT_GAS_PRICE
|
59
|
+
@gas_limit = Tx::DEFAULT_GAS_LIMIT
|
60
|
+
end
|
61
|
+
|
62
|
+
# Gets the default account (coinbase) of the connected client.
|
63
|
+
#
|
64
|
+
# @return [Eth::Address] the coinbase account address.
|
65
|
+
def default_account
|
66
|
+
@default_account ||= Address.new eth_coinbase["result"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Gets the chain ID of the connected network.
|
70
|
+
#
|
71
|
+
# @return [Integer] the chain ID.
|
72
|
+
def chain_id
|
73
|
+
@chain_id ||= eth_chain_id["result"].to_i 16
|
74
|
+
end
|
75
|
+
|
76
|
+
# Gets the balance for an address.
|
77
|
+
#
|
78
|
+
# @param address [Eth::Address] the address to get the balance for.
|
79
|
+
# @return [Integer] the balance in Wei.
|
80
|
+
def get_balance(address)
|
81
|
+
eth_get_balance(address)["result"].to_i 16
|
82
|
+
end
|
83
|
+
|
84
|
+
# Gets the next nonce for an address used to draft new transactions.
|
85
|
+
#
|
86
|
+
# @param address [Eth::Address] the address to get the nonce for.
|
87
|
+
# @return [Integer] the next nonce to be used.
|
88
|
+
def get_nonce(address)
|
89
|
+
eth_get_transaction_count(address, "pending")["result"].to_i 16
|
90
|
+
end
|
91
|
+
|
92
|
+
# Simply transfer Ether to an account and waits for it to be mined.
|
93
|
+
# Uses `eth_coinbase` and external signer if no sender key is
|
94
|
+
# provided.
|
95
|
+
#
|
96
|
+
# @param destination [Eth::Address] the destination address.
|
97
|
+
# @param amount [Integer] the transfer amount in Wei.
|
98
|
+
# @param sender_key [Eth::Key] the sender private key.
|
99
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
100
|
+
# @return [String] the transaction hash.
|
101
|
+
def transfer_and_wait(destination, amount, sender_key = nil, legacy = false)
|
102
|
+
wait_for_tx(transfer(destination, amount, sender_key, legacy))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Simply transfer Ether to an account without any call data or
|
106
|
+
# access lists attached. Uses `eth_coinbase` and external signer
|
107
|
+
# if no sender key is provided.
|
108
|
+
#
|
109
|
+
# @param destination [Eth::Address] the destination address.
|
110
|
+
# @param amount [Integer] the transfer amount in Wei.
|
111
|
+
# @param sender_key [Eth::Key] the sender private key.
|
112
|
+
# @param legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
113
|
+
# @return [String] the transaction hash.
|
114
|
+
def transfer(destination, amount, sender_key = nil, legacy = false)
|
115
|
+
params = {
|
116
|
+
value: amount,
|
117
|
+
to: destination,
|
118
|
+
gas_limit: gas_limit,
|
119
|
+
chain_id: chain_id,
|
120
|
+
}
|
121
|
+
if legacy
|
122
|
+
params.merge!({
|
123
|
+
gas_price: max_fee_per_gas,
|
124
|
+
})
|
125
|
+
else
|
126
|
+
params.merge!({
|
127
|
+
priority_fee: max_priority_fee_per_gas,
|
128
|
+
max_gas_fee: max_fee_per_gas,
|
129
|
+
})
|
130
|
+
end
|
131
|
+
unless sender_key.nil?
|
132
|
+
|
133
|
+
# use the provided key as sender and signer
|
134
|
+
params.merge!({
|
135
|
+
from: sender_key.address,
|
136
|
+
nonce: get_nonce(sender_key.address),
|
137
|
+
})
|
138
|
+
tx = Eth::Tx.new(params)
|
139
|
+
tx.sign sender_key
|
140
|
+
return eth_send_raw_transaction(tx.hex)["result"]
|
141
|
+
else
|
142
|
+
|
143
|
+
# use the default account as sender and external signer
|
144
|
+
params.merge!({
|
145
|
+
from: default_account,
|
146
|
+
nonce: get_nonce(default_account),
|
147
|
+
})
|
148
|
+
return eth_send_transaction(params)["result"]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Gives control over resetting the RPC request ID back to zero.
|
153
|
+
# Usually not needed.
|
154
|
+
#
|
155
|
+
# @return [Integer] 0
|
156
|
+
def reset_id
|
157
|
+
@id = 0
|
158
|
+
end
|
159
|
+
|
160
|
+
# Checkes wether a transaction is mined or not.
|
161
|
+
#
|
162
|
+
# @param hash [String] the transaction hash.
|
163
|
+
# @return [Boolean] true if included in a block.
|
164
|
+
def is_mined_tx?(hash)
|
165
|
+
mined_tx = eth_get_transaction_by_hash hash
|
166
|
+
!mined_tx.nil? && !mined_tx["result"].nil? && !mined_tx["result"]["blockNumber"].nil?
|
167
|
+
end
|
168
|
+
|
169
|
+
# Waits for an transaction to be mined by the connected chain.
|
170
|
+
#
|
171
|
+
# @param hash [String] the transaction hash.
|
172
|
+
# @return [String] the transactin hash once the transaction is mined.
|
173
|
+
# @raise [Timeout::Error] if it's not mined within 5 minutes.
|
174
|
+
def wait_for_tx(hash)
|
175
|
+
start_time = Time.now
|
176
|
+
timeout = 300
|
177
|
+
retry_rate = 0.1
|
178
|
+
loop do
|
179
|
+
raise Timeout::Error if ((Time.now - start_time) > timeout)
|
180
|
+
return hash if is_mined_tx? hash
|
181
|
+
sleep retry_rate
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Metafunction to provide all known RPC commands defined in
|
186
|
+
# Eth::Api as snake_case methods to the Eth::Client classes.
|
187
|
+
Api::COMMANDS.each do |cmd|
|
188
|
+
method_name = cmd.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
189
|
+
define_method method_name do |*args|
|
190
|
+
send_command cmd, args
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# Prepares parameters and sends the command to the client.
|
197
|
+
def send_command(command, args)
|
198
|
+
args << "latest" if ["eth_getBalance", "eth_call"].include? command
|
199
|
+
payload = {
|
200
|
+
jsonrpc: "2.0",
|
201
|
+
method: command,
|
202
|
+
params: marshal(args),
|
203
|
+
id: next_id,
|
204
|
+
}
|
205
|
+
output = JSON.parse(send(payload.to_json))
|
206
|
+
raise IOError, output["error"]["message"] unless output["error"].nil?
|
207
|
+
return output
|
208
|
+
end
|
209
|
+
|
210
|
+
# Increments the request id.
|
211
|
+
def next_id
|
212
|
+
@id += 1
|
213
|
+
end
|
214
|
+
|
215
|
+
# Recursively marshals all request parameters.
|
216
|
+
def marshal(params)
|
217
|
+
if params.is_a? Array
|
218
|
+
return params.map! { |param| marshal(param) }
|
219
|
+
elsif params.is_a? Hash
|
220
|
+
return params.transform_values! { |param| marshal(param) }
|
221
|
+
elsif params.is_a? Numeric
|
222
|
+
return Util.prefix_hex "#{params.to_i.to_s(16)}"
|
223
|
+
elsif params.is_a? Address
|
224
|
+
return params.to_s
|
225
|
+
elsif Util.is_hex? params
|
226
|
+
return Util.prefix_hex params
|
227
|
+
else
|
228
|
+
return params
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,71 @@
|
|
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
|
+
# -*- encoding : ascii-8bit -*-
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Klay
|
19
|
+
|
20
|
+
# Provides commonly used constants, such as zero bytes or zero keys.
|
21
|
+
module Constant
|
22
|
+
|
23
|
+
# The empty byte is defined as "".
|
24
|
+
BYTE_EMPTY = "".freeze
|
25
|
+
|
26
|
+
# The zero byte is 0x00.
|
27
|
+
BYTE_ZERO = "\x00".freeze
|
28
|
+
|
29
|
+
# The byte one is 0x01.
|
30
|
+
BYTE_ONE = "\x01".freeze
|
31
|
+
|
32
|
+
# The size of a 32-bit number.
|
33
|
+
TT32 = (2 ** 32).freeze
|
34
|
+
|
35
|
+
# The size of a 256-bit number.
|
36
|
+
TT256 = (2 ** 256).freeze
|
37
|
+
|
38
|
+
# The maximum possible value of an UInt256.
|
39
|
+
UINT_MAX = (2 ** 256 - 1).freeze
|
40
|
+
|
41
|
+
# The minimum possible value of an UInt256.
|
42
|
+
UINT_MIN = 0.freeze
|
43
|
+
|
44
|
+
# The maximum possible value of an Int256.
|
45
|
+
INT_MAX = (2 ** 255 - 1).freeze
|
46
|
+
|
47
|
+
# The minimum possible value of an Int256.
|
48
|
+
INT_MIN = (-2 ** 255).freeze
|
49
|
+
|
50
|
+
# A hash containing only zeros.
|
51
|
+
HASH_ZERO = ("\x00" * 32).freeze
|
52
|
+
|
53
|
+
# The RLP short length limit.
|
54
|
+
SHORT_LENGTH_LIMIT = 56.freeze
|
55
|
+
|
56
|
+
# The RLP long length limit.
|
57
|
+
LONG_LENGTH_LIMIT = (256 ** 8).freeze
|
58
|
+
|
59
|
+
# The RLP primitive type offset.
|
60
|
+
PRIMITIVE_PREFIX_OFFSET = 0x80.freeze
|
61
|
+
|
62
|
+
# The RLP array type offset.
|
63
|
+
LIST_PREFIX_OFFSET = 0xc0.freeze
|
64
|
+
|
65
|
+
# The binary encoding is ASCII (8-bit).
|
66
|
+
BINARY_ENCODING = "ASCII-8BIT".freeze
|
67
|
+
|
68
|
+
# Infinity as constant for convenience.
|
69
|
+
INFINITY = (1.0 / 0.0).freeze
|
70
|
+
end
|
71
|
+
end
|
data/lib/klay/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 Klay
|
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
|
@@ -0,0 +1,146 @@
|
|
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 Klay
|
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} to perform an keystore
|
25
|
+
# decryption.
|
26
|
+
#
|
27
|
+
# @param data [JSON] encryption data including cypherkey.
|
28
|
+
# @param password [String] password to decrypt the key.
|
29
|
+
# @return [Eth::Key] decrypted key-pair.
|
30
|
+
def self.perform(data, password)
|
31
|
+
new(data, password).perform
|
32
|
+
end
|
33
|
+
|
34
|
+
# Constructor of the {Eth::Key::Decrypter} class for secret key
|
35
|
+
# decryption. Should not be used; use {Eth::Key::Decrypter.perform}
|
36
|
+
# instead.
|
37
|
+
#
|
38
|
+
# @param data [JSON] encryption data including cypherkey.
|
39
|
+
# @param password [String] password to decrypt the key.
|
40
|
+
def initialize(data, password)
|
41
|
+
data = JSON.parse(data) if data.is_a? String
|
42
|
+
@data = data
|
43
|
+
@password = password
|
44
|
+
end
|
45
|
+
|
46
|
+
# Method to decrypt key using password.
|
47
|
+
#
|
48
|
+
# @return [Eth::Key] decrypted key.
|
49
|
+
def perform
|
50
|
+
derive_key password
|
51
|
+
check_macs
|
52
|
+
private_key = Util.bin_to_hex decrypted_data
|
53
|
+
Eth::Key.new priv: private_key
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
attr_reader :data
|
59
|
+
attr_reader :key
|
60
|
+
attr_reader :password
|
61
|
+
|
62
|
+
def derive_key(password)
|
63
|
+
case kdf
|
64
|
+
when "pbkdf2"
|
65
|
+
@key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
66
|
+
when "scrypt"
|
67
|
+
@key = SCrypt::Engine.scrypt(password, salt, n, r, p, key_length)
|
68
|
+
else
|
69
|
+
raise DecrypterError, "Unsupported key derivation function: #{kdf}!"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def check_macs
|
74
|
+
mac1 = Util.keccak256(key[(key_length / 2), key_length] + ciphertext)
|
75
|
+
mac2 = Util.hex_to_bin crypto_data["mac"]
|
76
|
+
|
77
|
+
if mac1 != mac2
|
78
|
+
raise DecrypterError, "Message Authentications Codes do not match!"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def decrypted_data
|
83
|
+
@decrypted_data ||= cipher.update(ciphertext) + cipher.final
|
84
|
+
end
|
85
|
+
|
86
|
+
def crypto_data
|
87
|
+
@crypto_data ||= data["crypto"] || data["Crypto"]
|
88
|
+
end
|
89
|
+
|
90
|
+
def ciphertext
|
91
|
+
Util.hex_to_bin crypto_data["ciphertext"]
|
92
|
+
end
|
93
|
+
|
94
|
+
def cipher_name
|
95
|
+
"aes-128-ctr"
|
96
|
+
end
|
97
|
+
|
98
|
+
def cipher
|
99
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
100
|
+
cipher.decrypt
|
101
|
+
cipher.key = key[0, (key_length / 2)]
|
102
|
+
cipher.iv = iv
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def iv
|
107
|
+
Util.hex_to_bin crypto_data["cipherparams"]["iv"]
|
108
|
+
end
|
109
|
+
|
110
|
+
def salt
|
111
|
+
Util.hex_to_bin crypto_data["kdfparams"]["salt"]
|
112
|
+
end
|
113
|
+
|
114
|
+
def iterations
|
115
|
+
crypto_data["kdfparams"]["c"].to_i
|
116
|
+
end
|
117
|
+
|
118
|
+
def kdf
|
119
|
+
crypto_data["kdf"]
|
120
|
+
end
|
121
|
+
|
122
|
+
def key_length
|
123
|
+
crypto_data["kdfparams"]["dklen"].to_i
|
124
|
+
end
|
125
|
+
|
126
|
+
def n
|
127
|
+
crypto_data["kdfparams"]["n"].to_i
|
128
|
+
end
|
129
|
+
|
130
|
+
def r
|
131
|
+
crypto_data["kdfparams"]["r"].to_i
|
132
|
+
end
|
133
|
+
|
134
|
+
def p
|
135
|
+
crypto_data["kdfparams"]["p"].to_i
|
136
|
+
end
|
137
|
+
|
138
|
+
def digest
|
139
|
+
OpenSSL::Digest.new digest_name
|
140
|
+
end
|
141
|
+
|
142
|
+
def digest_name
|
143
|
+
"sha256"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|