klay 0.0.1

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