eth-custom 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +18 -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/.rspec +4 -0
- data/.yardopts +1 -0
- data/AUTHORS.txt +29 -0
- data/CHANGELOG.md +218 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +202 -0
- data/README.md +347 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/codecov.yml +6 -0
- data/eth.gemspec +51 -0
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +178 -0
- data/lib/eth/abi.rb +446 -0
- data/lib/eth/address.rb +106 -0
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +157 -0
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +499 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/contract/event.rb +42 -0
- data/lib/eth/contract/function.rb +57 -0
- data/lib/eth/contract/function_input.rb +38 -0
- data/lib/eth/contract/function_output.rb +37 -0
- data/lib/eth/contract/initializer.rb +47 -0
- data/lib/eth/contract.rb +143 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +146 -0
- data/lib/eth/key/encrypter.rb +207 -0
- data/lib/eth/key.rb +167 -0
- data/lib/eth/rlp/decoder.rb +114 -0
- data/lib/eth/rlp/encoder.rb +78 -0
- data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
- data/lib/eth/rlp/sedes/binary.rb +97 -0
- data/lib/eth/rlp/sedes/list.rb +84 -0
- data/lib/eth/rlp/sedes.rb +74 -0
- data/lib/eth/rlp.rb +63 -0
- data/lib/eth/signature.rb +163 -0
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +337 -0
- data/lib/eth/tx/eip2930.rb +329 -0
- data/lib/eth/tx/legacy.rb +297 -0
- data/lib/eth/tx.rb +322 -0
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +235 -0
- data/lib/eth/version.rb +20 -0
- data/lib/eth.rb +35 -0
- metadata +184 -0
@@ -0,0 +1,38 @@
|
|
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 Eth
|
19
|
+
|
20
|
+
# Provide classes for contract function input.
|
21
|
+
class Contract::FunctionInput
|
22
|
+
attr_accessor :type, :raw_type, :name
|
23
|
+
|
24
|
+
# Constructor of the {Eth::Contract::FunctionInput} class.
|
25
|
+
#
|
26
|
+
# @param data [Hash] contract abi data.
|
27
|
+
def initialize(data)
|
28
|
+
@raw_type = data["type"]
|
29
|
+
@type = Eth::Abi::Type.parse(data["type"])
|
30
|
+
@name = data["name"]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns complete types with subtypes, e.g., `uint256`.
|
34
|
+
def type
|
35
|
+
@type.base_type + @type.sub_type + @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
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 Eth
|
19
|
+
|
20
|
+
# Provide classes for contract function output.
|
21
|
+
class Contract::FunctionOutput
|
22
|
+
attr_accessor :type, :name
|
23
|
+
|
24
|
+
# Constructor of the {Eth::Contract::FunctionOutput} class.
|
25
|
+
#
|
26
|
+
# @param data [Hash] contract abi data.
|
27
|
+
def initialize(data)
|
28
|
+
@type = Eth::Abi::Type.parse(data["type"])
|
29
|
+
@name = data["name"]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns complete types with subtypes, e.g., `uint256`.
|
33
|
+
def type
|
34
|
+
@type.base_type + @type.sub_type + @type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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 Eth
|
19
|
+
|
20
|
+
# Provide classes for contract initializer.
|
21
|
+
class Contract::Initializer
|
22
|
+
attr_accessor :contracts, :file
|
23
|
+
|
24
|
+
# Constructor of the {Eth::Contract::Initializer} class.
|
25
|
+
#
|
26
|
+
# @param file [String] file path to solidity code.
|
27
|
+
def initialize(file)
|
28
|
+
sol_output = Eth::Solidity.new.compile(file)
|
29
|
+
contracts = sol_output.keys
|
30
|
+
|
31
|
+
@contracts = []
|
32
|
+
contracts.each do |contract|
|
33
|
+
abi = sol_output[contract]["abi"]
|
34
|
+
name = contract
|
35
|
+
code = sol_output[contract]["bin"]
|
36
|
+
@contracts << Contract.new(name, code, abi)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Builds and returns all contracts.
|
41
|
+
def build_all
|
42
|
+
@contracts.each do |contract|
|
43
|
+
contract.build
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/eth/contract.rb
ADDED
@@ -0,0 +1,143 @@
|
|
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 Eth
|
19
|
+
|
20
|
+
# Provides classes to access smart contracts
|
21
|
+
class Contract
|
22
|
+
attr_reader :address
|
23
|
+
attr_accessor :key
|
24
|
+
attr_accessor :gas_limit, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :nonce
|
25
|
+
attr_accessor :bin, :name, :abi, :class_object
|
26
|
+
attr_accessor :events, :functions, :constructor_inputs
|
27
|
+
|
28
|
+
# Constructor of the {Eth::Contract} class.
|
29
|
+
#
|
30
|
+
# @param name [String] contract name.
|
31
|
+
# @param bin [String] contract bin string.
|
32
|
+
# @param abi [String] contract abi string.
|
33
|
+
def initialize(name, bin, abi)
|
34
|
+
@name = name
|
35
|
+
@bin = bin
|
36
|
+
@abi = abi
|
37
|
+
@constructor_inputs, @functions, @events = parse_abi(abi)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates a contract wrapper from a Solidity file.
|
41
|
+
#
|
42
|
+
# @param file [String] solidity file path.
|
43
|
+
# @param contract_index [Number] specify contract.
|
44
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
45
|
+
# @raise [ArgumentError] if the file path is empty or no contracts were compiled.
|
46
|
+
def self.from_file(file:, contract_index: 0)
|
47
|
+
raise ArgumentError, "Cannot find the contract at #{file.to_s}!" if !File.exist?(file.to_s)
|
48
|
+
contracts = Eth::Contract::Initializer.new(file).build_all
|
49
|
+
raise ArgumentError, "No contracts compiled." if contracts.empty?
|
50
|
+
contracts[contract_index].class_object.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a contract wrapper from ABI and address.
|
54
|
+
#
|
55
|
+
# @param abi [String] contract abi string.
|
56
|
+
# @param address [String] contract address.
|
57
|
+
# @param name [String] name of contract.
|
58
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
59
|
+
# @raise [JSON::ParserError] if the json format is wrong.
|
60
|
+
# @raise [ArgumentError] if ABI, address, or name is missing.
|
61
|
+
def self.from_abi(abi:, address:, name:)
|
62
|
+
abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
|
63
|
+
contract = Eth::Contract.new(name, nil, abi)
|
64
|
+
contract.build
|
65
|
+
contract = contract.class_object.new
|
66
|
+
contract.address = address
|
67
|
+
contract
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a contract wrapper from binary and ABI.
|
71
|
+
#
|
72
|
+
# @param bin [String] contract bin string.
|
73
|
+
# @param abi [String] contract abi string.
|
74
|
+
# @param name [String] name of contract.
|
75
|
+
# @return [Eth::Contract::Object] Returns the class of the smart contract.
|
76
|
+
# @raise [JSON::ParserError] if the json format is wrong.
|
77
|
+
# @raise [ArgumentError] if ABI, binary, or name is missing.
|
78
|
+
def self.from_bin(bin:, abi:, name:)
|
79
|
+
abi = abi.is_a?(Array) ? abi : JSON.parse(abi)
|
80
|
+
contract = Eth::Contract.new(name, bin, abi)
|
81
|
+
contract.build
|
82
|
+
contract.class_object.new
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the address of the smart contract.
|
86
|
+
#
|
87
|
+
# @param addr [String|Eth::Address] contract address string.
|
88
|
+
def address=(addr)
|
89
|
+
if addr.is_a? Eth::Address
|
90
|
+
@address = addr.to_s
|
91
|
+
else
|
92
|
+
@address = Eth::Address.new(addr).to_s
|
93
|
+
end
|
94
|
+
@events.each do |event|
|
95
|
+
event.set_address(@address)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Create meta classes for smart contracts.
|
100
|
+
def build
|
101
|
+
class_name = @name
|
102
|
+
parent = self
|
103
|
+
class_methods = Class.new do
|
104
|
+
extend Forwardable
|
105
|
+
def_delegators :parent, :key, :key=
|
106
|
+
def_delegators :parent, :name, :abi, :bin
|
107
|
+
def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
|
108
|
+
def_delegators :parent, :max_fee_per_gas, :max_fee_per_gas=, :max_priority_fee_per_gas, :max_priority_fee_per_gas=
|
109
|
+
def_delegators :parent, :events
|
110
|
+
def_delegators :parent, :address, :address=
|
111
|
+
def_delegator :parent, :functions
|
112
|
+
def_delegator :parent, :constructor_inputs
|
113
|
+
define_method :parent do
|
114
|
+
parent
|
115
|
+
end
|
116
|
+
end
|
117
|
+
Eth::Contract.send(:remove_const, class_name) if Eth::Contract.const_defined?(class_name, false)
|
118
|
+
Eth::Contract.const_set(class_name, class_methods)
|
119
|
+
@class_object = class_methods
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def parse_abi(abi)
|
125
|
+
constructor = abi.detect { |x| x["type"] == "constructor" }
|
126
|
+
if !constructor.nil?
|
127
|
+
constructor_inputs = constructor["inputs"].map { |input| Eth::Contract::FunctionInput.new(input) }
|
128
|
+
else
|
129
|
+
constructor_inputs = []
|
130
|
+
end
|
131
|
+
functions = abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) }
|
132
|
+
events = abi.select { |x| x["type"] == "event" }.map { |evt| Eth::Contract::Event.new(evt) }
|
133
|
+
[constructor_inputs, functions, events]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Load the contract/* libraries
|
139
|
+
require "eth/contract/event"
|
140
|
+
require "eth/contract/function"
|
141
|
+
require "eth/contract/function_input"
|
142
|
+
require "eth/contract/function_output"
|
143
|
+
require "eth/contract/initializer"
|
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
|
@@ -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 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} 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
|