eth 0.4.12 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,293 @@
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
+ # Provides the `Tx` module supporting various transaction types.
19
+ module Tx
20
+
21
+ # Provides legacy support for transactions on blockchains that do not
22
+ # implement EIP-1559, EIP-2718, or EIP-2930.
23
+ class Legacy
24
+
25
+ # The transaction nonce provided by the signer.
26
+ attr_reader :signer_nonce
27
+
28
+ # The gas price for the transaction in Wei.
29
+ attr_reader :gas_price
30
+
31
+ # The gas limit for the transaction.
32
+ attr_reader :gas_limit
33
+
34
+ # The recipient address.
35
+ attr_reader :destination
36
+
37
+ # The transaction amount in Wei.
38
+ attr_reader :amount
39
+
40
+ # The transaction data payload.
41
+ attr_reader :payload
42
+
43
+ # The signature `v` byte.
44
+ attr_reader :signature_v
45
+
46
+ # The signature `r` value.
47
+ attr_reader :signature_r
48
+
49
+ # The signature `s` value.
50
+ attr_reader :signature_s
51
+
52
+ # The EIP-155 chain ID field.
53
+ attr_reader :chain_id
54
+
55
+ # The sender address.
56
+ attr_reader :sender
57
+
58
+ # The transaction type.
59
+ attr_reader :type
60
+
61
+ # Create a legacy transaction object that can be prepared for
62
+ # signature and broadcast. Should not be used unless there is
63
+ # no EIP-1559 support.
64
+ #
65
+ # @param params [Hash] all necessary transaction fields.
66
+ # @option params [Integer] :nonce the signer nonce.
67
+ # @option params [Integer] :gas_price the gas price.
68
+ # @option params [Integer] :gas_limit the gas limit.
69
+ # @option params [Eth::Address] :from the sender address.
70
+ # @option params [Eth::Address] :to the reciever address.
71
+ # @option params [Integer] :value the transaction value.
72
+ # @option params [String] :data the transaction data payload.
73
+ # @param chain_id [Integer] the EIP-155 Chain ID.
74
+ def initialize(params, chain_id = Chain::ETHEREUM)
75
+ fields = { v: chain_id, r: 0, s: 0 }.merge params
76
+
77
+ # populate optional fields with serializable empty values
78
+ fields[:value] = Tx.sanitize_amount fields[:value]
79
+ fields[:from] = Tx.sanitize_address fields[:from]
80
+ fields[:to] = Tx.sanitize_address fields[:to]
81
+ fields[:data] = Tx.sanitize_data fields[:data]
82
+
83
+ # ensure sane values for all mandatory fields
84
+ fields = Tx.validate_legacy_params fields
85
+
86
+ # ensure gas limit is not too low
87
+ minimum_cost = Tx.estimate_intrinsic_gas fields[:data]
88
+ raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost
89
+
90
+ # populate class attributes
91
+ @signer_nonce = fields[:nonce].to_i
92
+ @gas_price = fields[:gas_price].to_i
93
+ @gas_limit = fields[:gas_limit].to_i
94
+ @sender = fields[:from].to_s
95
+ @destination = fields[:to].to_s
96
+ @amount = fields[:value].to_i
97
+ @payload = fields[:data]
98
+
99
+ # the signature v is set to the chain id for unsigned transactions
100
+ @signature_v = fields[:v]
101
+ @chain_id = chain_id
102
+
103
+ # the signature fields are empty for unsigned transactions.
104
+ @signature_r = fields[:r]
105
+ @signature_s = fields[:s]
106
+
107
+ # last but not least, set the type.
108
+ @type = TYPE_LEGACY
109
+ end
110
+
111
+ # overloads the constructor for decoding raw transactions and creating unsigned copies
112
+ konstructor :decode, :unsigned_copy
113
+
114
+ # Decodes a raw transaction hex into an Eth::Tx::Legacy
115
+ # transaction object.
116
+ #
117
+ # @param hex [String] the raw transaction hex-string.
118
+ # @return [Eth::Tx::Legacy] transaction object.
119
+ # @raise [ParameterError] if transaction misses fields.
120
+ def decode(hex)
121
+ bin = Util.hex_to_bin hex
122
+ tx = RLP.decode(bin)
123
+
124
+ # decoded transactions always have 9 fields, even if they are empty or zero
125
+ raise ParameterError, "Transaction missing fields!" if tx.size < 9
126
+
127
+ # populate the 9 fields
128
+ nonce = Util.deserialize_big_endian_to_int tx[0]
129
+ gas_price = Util.deserialize_big_endian_to_int tx[1]
130
+ gas_limit = Util.deserialize_big_endian_to_int tx[2]
131
+ to = Util.bin_to_hex tx[3]
132
+ value = Util.deserialize_big_endian_to_int tx[4]
133
+ data = tx[5]
134
+ v = Util.bin_to_hex tx[6]
135
+ r = Util.bin_to_hex tx[7]
136
+ s = Util.bin_to_hex tx[8]
137
+
138
+ # try to recover the chain id from v
139
+ chain_id = Chain.to_chain_id Util.deserialize_big_endian_to_int tx[6]
140
+
141
+ # populate class attributes
142
+ @signer_nonce = nonce.to_i
143
+ @gas_price = gas_price.to_i
144
+ @gas_limit = gas_limit.to_i
145
+ @destination = to.to_s
146
+ @amount = value.to_i
147
+ @payload = data
148
+ @chain_id = chain_id
149
+
150
+ # allows us to force-setting a signature if the transaction is signed already
151
+ _set_signature(v, r, s)
152
+
153
+ unless chain_id.nil?
154
+
155
+ # recover sender address
156
+ public_key = Signature.recover(unsigned_hash, "#{r}#{s}#{v}", chain_id)
157
+ address = Util.public_key_to_address(public_key).to_s
158
+ @sender = Tx.sanitize_address address
159
+ else
160
+
161
+ # keep the 'from' field blank
162
+ @sender = Tx.sanitize_address nil
163
+ end
164
+
165
+ # last but not least, set the type.
166
+ @type = TYPE_LEGACY
167
+ end
168
+
169
+ # Creates an unsigned copy of a transaction.
170
+ #
171
+ # @param tx [Eth::Tx::Legacy] an legacy transaction object.
172
+ # @return [Eth::Tx::Legacy] an unsigned transaction object.
173
+ # @raise [TransactionTypeError] if transaction type does not match.
174
+ def unsigned_copy(tx)
175
+
176
+ # not checking transaction validity unless it's of a different class
177
+ raise TransactionTypeError, "Cannot copy transaction of different type!" unless tx.instance_of? Tx::Legacy
178
+
179
+ # populate class attributes
180
+ @signer_nonce = tx.signer_nonce
181
+ @gas_price = tx.gas_price
182
+ @gas_limit = tx.gas_limit
183
+ @destination = tx.destination
184
+ @amount = tx.amount
185
+ @payload = tx.payload
186
+ @chain_id = tx.chain_id
187
+
188
+ # force-set signature to unsigned
189
+ _set_signature(tx.chain_id, 0, 0)
190
+
191
+ # keep the 'from' field blank
192
+ @sender = Tx.sanitize_address nil
193
+
194
+ # last but not least, set the type.
195
+ @type = TYPE_LEGACY
196
+ end
197
+
198
+ # Sign the transaction with a given key.
199
+ #
200
+ # @param key [Eth::Key] the key-pair to use for signing.
201
+ # @return [String] a transaction hash.
202
+ # @raise [SignatureError] if transaction is already signed.
203
+ # @raise [SignatureError] if sender address does not match signing key.
204
+ def sign(key)
205
+ if Tx.is_signed? self
206
+ raise Signature::SignatureError, "Transaction is already signed!"
207
+ end
208
+
209
+ # ensure the sender address matches the given key
210
+ unless @sender.nil? or sender.empty?
211
+ signer_address = Tx.sanitize_address key.address.to_s
212
+ from_address = Tx.sanitize_address @sender
213
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
214
+ end
215
+
216
+ # sign a keccak hash of the unsigned, encoded transaction
217
+ signature = key.sign(unsigned_hash, @chain_id)
218
+ r, s, v = Signature.dissect signature
219
+ @signature_v = v
220
+ @signature_r = r
221
+ @signature_s = s
222
+ return hash
223
+ end
224
+
225
+ # Encodes a raw transaction object.
226
+ #
227
+ # @return [String] a raw, RLP-encoded legacy transaction.
228
+ # @raise [SignatureError] if the transaction is not yet signed.
229
+ def encoded
230
+ unless Tx.is_signed? self
231
+ raise Signature::SignatureError, "Transaction is not signed!"
232
+ end
233
+ tx_data = []
234
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
235
+ tx_data.push Util.serialize_int_to_big_endian @gas_price
236
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
237
+ tx_data.push Util.hex_to_bin @destination
238
+ tx_data.push Util.serialize_int_to_big_endian @amount
239
+ tx_data.push @payload
240
+ tx_data.push Util.serialize_int_to_big_endian @signature_v
241
+ tx_data.push Util.serialize_int_to_big_endian @signature_r
242
+ tx_data.push Util.serialize_int_to_big_endian @signature_s
243
+ RLP.encode tx_data
244
+ end
245
+
246
+ # Gets the encoded, raw transaction hex.
247
+ #
248
+ # @return [String] the raw transaction hex.
249
+ def hex
250
+ Util.bin_to_hex encoded
251
+ end
252
+
253
+ # Gets the transaction hash.
254
+ #
255
+ # @return [String] the transaction hash.
256
+ def hash
257
+ Util.bin_to_hex Util.keccak256 encoded
258
+ end
259
+
260
+ # Encodes the unsigned transaction object, required for signing.
261
+ #
262
+ # @return [String] an RLP-encoded, unsigned transaction.
263
+ def unsigned_encoded
264
+ tx_data = []
265
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
266
+ tx_data.push Util.serialize_int_to_big_endian @gas_price
267
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
268
+ tx_data.push Util.hex_to_bin @destination
269
+ tx_data.push Util.serialize_int_to_big_endian @amount
270
+ tx_data.push @payload
271
+ tx_data.push Util.serialize_int_to_big_endian @chain_id
272
+ tx_data.push Util.serialize_int_to_big_endian 0
273
+ tx_data.push Util.serialize_int_to_big_endian 0
274
+ RLP.encode tx_data
275
+ end
276
+
277
+ # Gets the sign-hash required to sign a raw transaction.
278
+ #
279
+ # @return [String] a Keccak-256 hash of an unsigned transaction.
280
+ def unsigned_hash
281
+ Util.keccak256 unsigned_encoded
282
+ end
283
+
284
+ private
285
+
286
+ def _set_signature(v, r, s)
287
+ @signature_v = v
288
+ @signature_r = r
289
+ @signature_s = s
290
+ end
291
+ end
292
+ end
293
+ end