eth 0.4.18 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql.yml +6 -2
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/spec.yml +52 -0
- data/.gitignore +24 -24
- data/.gitmodules +3 -3
- data/.yardopts +1 -0
- data/AUTHORS.txt +27 -0
- data/CHANGELOG.md +63 -13
- data/Gemfile +12 -4
- data/LICENSE.txt +202 -22
- data/README.md +231 -76
- data/bin/console +4 -4
- data/bin/setup +5 -4
- data/codecov.yml +6 -0
- data/eth.gemspec +23 -19
- data/lib/eth/abi/type.rb +178 -0
- data/lib/eth/abi.rb +396 -0
- data/lib/eth/address.rb +57 -10
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +151 -0
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +232 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +121 -85
- data/lib/eth/key/encrypter.rb +180 -99
- data/lib/eth/key.rb +134 -45
- 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 +269 -146
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +235 -0
- data/lib/eth/version.rb +18 -1
- data/lib/eth.rb +34 -67
- metadata +47 -95
- data/.github/workflows/build.yml +0 -36
- data/lib/eth/gas.rb +0 -7
- data/lib/eth/open_ssl.rb +0 -395
- data/lib/eth/secp256k1.rb +0 -5
- data/lib/eth/sedes.rb +0 -39
- data/lib/eth/utils.rb +0 -126
data/lib/eth/key.rb
CHANGED
@@ -1,78 +1,167 @@
|
|
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
|
+
require "json"
|
16
|
+
require "openssl"
|
17
|
+
require "rbsecp256k1"
|
18
|
+
require "scrypt"
|
19
|
+
require "securerandom"
|
20
|
+
|
21
|
+
# Provides the {Eth} module.
|
1
22
|
module Eth
|
23
|
+
|
24
|
+
# The {Eth::Key} class to handle Secp256k1 private/public key-pairs.
|
2
25
|
class Key
|
26
|
+
|
27
|
+
# The {Eth::Key::Decrypter} class to handle PBKDF2-SHA-256 decryption.
|
3
28
|
autoload :Decrypter, "eth/key/decrypter"
|
29
|
+
|
30
|
+
# The {Eth::Key::Encrypter} class to handle PBKDF2-SHA-256 encryption.
|
4
31
|
autoload :Encrypter, "eth/key/encrypter"
|
5
32
|
|
6
|
-
|
33
|
+
# The `Secp256k1::PrivateKey` of the {Eth::Key} pair.
|
34
|
+
attr_reader :private_key
|
7
35
|
|
8
|
-
|
9
|
-
|
36
|
+
# The `Secp256k1::PublicKey` of the {Eth::Key} pair.
|
37
|
+
attr_reader :public_key
|
10
38
|
|
11
|
-
|
12
|
-
|
39
|
+
# Constructor of the {Eth::Key} class. Creates a new random key-pair
|
40
|
+
# if no `priv` key is provided.
|
41
|
+
#
|
42
|
+
# @param priv [String] binary string of private key data.
|
43
|
+
def initialize(priv: nil)
|
13
44
|
|
14
|
-
|
15
|
-
|
16
|
-
new priv: priv
|
17
|
-
end
|
45
|
+
# Creates a new, randomized libsecp256k1 context.
|
46
|
+
ctx = Secp256k1::Context.new context_randomization_bytes: SecureRandom.random_bytes(32)
|
18
47
|
|
19
|
-
|
20
|
-
|
21
|
-
OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), bin_signature)
|
22
|
-
end
|
48
|
+
# Creates a new random key pair (public, private).
|
49
|
+
key = ctx.generate_key_pair
|
23
50
|
|
24
|
-
|
25
|
-
@private_key = MoneyTree::PrivateKey.new key: priv
|
26
|
-
@public_key = MoneyTree::PublicKey.new private_key, compressed: false
|
27
|
-
end
|
51
|
+
unless priv.nil?
|
28
52
|
|
29
|
-
|
30
|
-
|
31
|
-
end
|
53
|
+
# Converts hex private keys to binary strings.
|
54
|
+
priv = Util.hex_to_bin priv if Util.is_hex? priv
|
32
55
|
|
33
|
-
|
34
|
-
|
56
|
+
# Creates a keypair from existing private key data.
|
57
|
+
key = ctx.key_pair_from_private_key priv
|
58
|
+
end
|
59
|
+
|
60
|
+
# Sets the attributes.
|
61
|
+
@private_key = key.private_key
|
62
|
+
@public_key = key.public_key
|
35
63
|
end
|
36
64
|
|
37
|
-
|
38
|
-
|
65
|
+
# Signs arbitrary data without validation. Should not be used unless really
|
66
|
+
# desired. See also: {Key.personal_sign}, {Key.sign_typed_data}, and
|
67
|
+
# {Signature.recover}.
|
68
|
+
#
|
69
|
+
# @param blob [Object] that arbitrary data to be signed.
|
70
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
71
|
+
# @return [String] a hexa-decimal signature.
|
72
|
+
def sign(blob, chain_id = nil)
|
73
|
+
context = Secp256k1::Context.new
|
74
|
+
compact, recovery_id = context.sign_recoverable(@private_key, blob).compact
|
75
|
+
signature = compact.bytes
|
76
|
+
v = Chain.to_v recovery_id, chain_id
|
77
|
+
is_leading_zero = true
|
78
|
+
[v].pack("N").unpack("C*").each do |byte|
|
79
|
+
is_leading_zero = false if byte > 0 and is_leading_zero
|
80
|
+
signature.append byte unless is_leading_zero and byte === 0
|
81
|
+
end
|
82
|
+
Util.bin_to_hex signature.pack "c*"
|
39
83
|
end
|
40
84
|
|
41
|
-
|
42
|
-
|
85
|
+
# Prefixes a message with `\x19Ethereum Signed Message:` and signs
|
86
|
+
# it in the common way used by many web3 wallets. Complies with
|
87
|
+
# EIP-191 prefix `0x19` and version byte `0x45` (`E`). See also
|
88
|
+
# {Signature.personal_recover}.
|
89
|
+
# Ref: https://eips.ethereum.org/EIPS/eip-191
|
90
|
+
#
|
91
|
+
# @param message [String] the message string to be prefixed and signed.
|
92
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
93
|
+
# @return [String] an EIP-191 conform, hexa-decimal signature.
|
94
|
+
def personal_sign(message, chain_id = nil)
|
95
|
+
prefixed_message = Signature.prefix_message message
|
96
|
+
hashed_message = Util.keccak256 prefixed_message
|
97
|
+
sign hashed_message, chain_id
|
43
98
|
end
|
44
99
|
|
45
|
-
|
100
|
+
# Prefixes, hashes, and signes a typed data structure in the common
|
101
|
+
# way used by many web3 wallets. Complies with EIP-191 prefix `0x19`
|
102
|
+
# and EIP-712 version byte `0x01`. Supports `V3`, `V4`. See also
|
103
|
+
# {Signature.recover_typed_data}.
|
104
|
+
# Ref: https://eips.ethereum.org/EIPS/eip-712
|
105
|
+
#
|
106
|
+
# @param typed_data [Array] all the data in the typed data structure to be signed.
|
107
|
+
# @param chain_id [Integer] the chain id the signature should be generated on.
|
108
|
+
# @return [String] an EIP-712 conform, hexa-decimal signature.
|
109
|
+
def sign_typed_data(typed_data, chain_id = nil)
|
110
|
+
hash_to_sign = Eip712.hash typed_data
|
111
|
+
sign hash_to_sign, chain_id
|
112
|
+
end
|
46
113
|
|
47
|
-
|
48
|
-
|
114
|
+
# Converts the private key data into a hexa-decimal string.
|
115
|
+
#
|
116
|
+
# @return [String] private key as hexa-decimal string.
|
117
|
+
def private_hex
|
118
|
+
Util.bin_to_hex @private_key.data
|
49
119
|
end
|
50
120
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
121
|
+
# Exports the private key bytes in a wrapper function to maintain
|
122
|
+
# backward-compatibility with older versions of {Eth::Key}.
|
123
|
+
#
|
124
|
+
# @return [String] private key as packed byte-string.
|
125
|
+
def private_bytes
|
126
|
+
@private_key.data
|
56
127
|
end
|
57
128
|
|
58
|
-
|
59
|
-
|
60
|
-
|
129
|
+
# Converts the public key data into an uncompressed
|
130
|
+
# hexa-decimal string.
|
131
|
+
#
|
132
|
+
# @return [String] public key as uncompressed hexa-decimal string.
|
133
|
+
def public_hex
|
134
|
+
Util.bin_to_hex @public_key.uncompressed
|
61
135
|
end
|
62
136
|
|
63
|
-
|
64
|
-
|
137
|
+
# Converts the public key data into an compressed
|
138
|
+
# hexa-decimal string.
|
139
|
+
#
|
140
|
+
# @return [String] public key as compressed hexa-decimal string.
|
141
|
+
def public_hex_compressed
|
142
|
+
Util.bin_to_hex @public_key.compressed
|
65
143
|
end
|
66
144
|
|
67
|
-
|
145
|
+
# Exports the uncompressed public key bytes in a wrapper function to
|
146
|
+
# maintain backward-compatibility with older versions of {Eth::Key}.
|
147
|
+
#
|
148
|
+
# @return [String] uncompressed public key as packed byte-string.
|
149
|
+
def public_bytes
|
150
|
+
@public_key.uncompressed
|
151
|
+
end
|
68
152
|
|
69
|
-
|
70
|
-
|
153
|
+
# Exports the compressed public key bytes.
|
154
|
+
#
|
155
|
+
# @return [String] compressed public key as packed byte-string.
|
156
|
+
def public_bytes_compressed
|
157
|
+
@public_key.compressed
|
71
158
|
end
|
72
159
|
|
73
|
-
|
74
|
-
|
75
|
-
|
160
|
+
# Exports the checksummed public address.
|
161
|
+
#
|
162
|
+
# @return [Eth::Address] compressed address as packed hex prefixed string.
|
163
|
+
def address
|
164
|
+
Util.public_key_to_address public_bytes
|
76
165
|
end
|
77
166
|
end
|
78
167
|
end
|
@@ -0,0 +1,114 @@
|
|
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 an recursive-length prefix (RLP) encoder and decoder.
|
21
|
+
module Rlp
|
22
|
+
|
23
|
+
# Provides an RLP-decoder.
|
24
|
+
module Decoder
|
25
|
+
extend self
|
26
|
+
|
27
|
+
# Decodes an RLP-encoded object.
|
28
|
+
#
|
29
|
+
# @param rlp [String] an RLP-encoded object.
|
30
|
+
# @return [Object] the decoded and maybe deserialized object.
|
31
|
+
# @raise [Eth::Rlp::DecodingError] if the input string does not end after
|
32
|
+
# the root item.
|
33
|
+
def perform(rlp)
|
34
|
+
rlp = Util.hex_to_bin rlp if Util.is_hex? rlp
|
35
|
+
rlp = Util.str_to_bytes rlp
|
36
|
+
begin
|
37
|
+
item, next_start = consume_item rlp, 0
|
38
|
+
rescue Exception => e
|
39
|
+
raise DecodingError, "Cannot decode rlp string: #{e}"
|
40
|
+
end
|
41
|
+
raise DecodingError, "RLP string ends with #{rlp.size - next_start} superfluous bytes" if next_start != rlp.size
|
42
|
+
return item
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Consume an RLP-encoded item from the given start.
|
48
|
+
def consume_item(rlp, start)
|
49
|
+
t, l, s = consume_length_prefix rlp, start
|
50
|
+
consume_payload rlp, s, t, l
|
51
|
+
end
|
52
|
+
|
53
|
+
# Consume an RLP length prefix at the given position.
|
54
|
+
def consume_length_prefix(rlp, start)
|
55
|
+
b0 = rlp[start].ord
|
56
|
+
if b0 < Constant::PRIMITIVE_PREFIX_OFFSET
|
57
|
+
|
58
|
+
# single byte
|
59
|
+
[:str, 1, start]
|
60
|
+
elsif b0 < Constant::PRIMITIVE_PREFIX_OFFSET + Constant::SHORT_LENGTH_LIMIT
|
61
|
+
raise DecodingError, "Encoded as short string although single byte was possible" if (b0 - Constant::PRIMITIVE_PREFIX_OFFSET == 1) && rlp[start + 1].ord < Constant::PRIMITIVE_PREFIX_OFFSET
|
62
|
+
|
63
|
+
# short string
|
64
|
+
[:str, b0 - Constant::PRIMITIVE_PREFIX_OFFSET, start + 1]
|
65
|
+
elsif b0 < Constant::LIST_PREFIX_OFFSET
|
66
|
+
enforce_no_zero_bytes rlp, start
|
67
|
+
|
68
|
+
# long string
|
69
|
+
ll = b0 - Constant::PRIMITIVE_PREFIX_OFFSET - Constant::SHORT_LENGTH_LIMIT + 1
|
70
|
+
l = Util.big_endian_to_int rlp[(start + 1)...(start + 1 + ll)]
|
71
|
+
raise DecodingError, "Long string prefix used for short string" if l < Constant::SHORT_LENGTH_LIMIT
|
72
|
+
[:str, l, start + 1 + ll]
|
73
|
+
elsif b0 < Constant::LIST_PREFIX_OFFSET + Constant::SHORT_LENGTH_LIMIT
|
74
|
+
|
75
|
+
# short list
|
76
|
+
[:list, b0 - Constant::LIST_PREFIX_OFFSET, start + 1]
|
77
|
+
else
|
78
|
+
enforce_no_zero_bytes rlp, start
|
79
|
+
|
80
|
+
# long list
|
81
|
+
ll = b0 - Constant::LIST_PREFIX_OFFSET - Constant::SHORT_LENGTH_LIMIT + 1
|
82
|
+
l = Util.big_endian_to_int rlp[(start + 1)...(start + 1 + ll)]
|
83
|
+
raise DecodingError, "Long list prefix used for short list" if l < Constant::SHORT_LENGTH_LIMIT
|
84
|
+
[:list, l, start + 1 + ll]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Enforce RLP slices to not start with empty bytes.
|
89
|
+
def enforce_no_zero_bytes(rlp, start)
|
90
|
+
raise DecodingError, "Length starts with zero bytes" if rlp.slice(start + 1) == Constant::BYTE_ZERO
|
91
|
+
end
|
92
|
+
|
93
|
+
# Consume an RLP payload at the given position of given type and size.
|
94
|
+
def consume_payload(rlp, start, type, length)
|
95
|
+
case type
|
96
|
+
when :str
|
97
|
+
[rlp[start...(start + length)], start + length]
|
98
|
+
when :list
|
99
|
+
items = []
|
100
|
+
next_item_start = start
|
101
|
+
payload_end = next_item_start + length
|
102
|
+
while next_item_start < payload_end
|
103
|
+
item, next_item_start = consume_item rlp, next_item_start
|
104
|
+
items.push item
|
105
|
+
end
|
106
|
+
raise DecodingError, "List length prefix announced a too small length" if next_item_start > payload_end
|
107
|
+
[items, next_item_start]
|
108
|
+
else
|
109
|
+
raise TypeError, "Type must be either :str or :list"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,78 @@
|
|
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 an recursive-length prefix (RLP) encoder and decoder.
|
21
|
+
module Rlp
|
22
|
+
|
23
|
+
# Provides an RLP-encoder.
|
24
|
+
module Encoder
|
25
|
+
extend self
|
26
|
+
|
27
|
+
# Encodes a Ruby object in RLP format.
|
28
|
+
#
|
29
|
+
# @param obj [Object] a Ruby object.
|
30
|
+
# @return [String] the RLP encoded item.
|
31
|
+
# @raise [Eth::Rlp::EncodingError] in the rather unlikely case that the item
|
32
|
+
# is too big to encode (will not happen).
|
33
|
+
# @raise [Eth::Rlp::SerializationError] if the serialization fails.
|
34
|
+
def perform(obj)
|
35
|
+
item = Sedes.infer(obj).serialize(obj)
|
36
|
+
result = encode_raw item
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Encodes the raw item.
|
42
|
+
def encode_raw(item)
|
43
|
+
return item if item.instance_of? Rlp::Data
|
44
|
+
return encode_primitive item if Util.is_primitive? item
|
45
|
+
return encode_list item if Util.is_list? item
|
46
|
+
raise EncodingError "Cannot encode object of type #{item.class.name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Encodes a single primitive.
|
50
|
+
def encode_primitive(item)
|
51
|
+
return Util.str_to_bytes item if item.size == 1 && item.ord < Constant::PRIMITIVE_PREFIX_OFFSET
|
52
|
+
payload = Util.str_to_bytes item
|
53
|
+
prefix = length_prefix payload.size, Constant::PRIMITIVE_PREFIX_OFFSET
|
54
|
+
"#{prefix}#{payload}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Encodes a single list.
|
58
|
+
def encode_list(list)
|
59
|
+
payload = list.map { |item| encode_raw item }.join
|
60
|
+
prefix = length_prefix payload.size, Constant::LIST_PREFIX_OFFSET
|
61
|
+
"#{prefix}#{payload}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Determines a length prefix.
|
65
|
+
def length_prefix(length, offset)
|
66
|
+
if length < Constant::SHORT_LENGTH_LIMIT
|
67
|
+
(offset + length).chr
|
68
|
+
elsif length < Constant::LONG_LENGTH_LIMIT
|
69
|
+
length_string = Util.int_to_big_endian length
|
70
|
+
length_len = (offset + Constant::SHORT_LENGTH_LIMIT - 1 + length_string.size).chr
|
71
|
+
"#{length_len}#{length_string}"
|
72
|
+
else
|
73
|
+
raise EncodingError, "Length greater than 256**8: #{length}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,66 @@
|
|
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 an recursive-length prefix (RLP) encoder and decoder.
|
21
|
+
module Rlp
|
22
|
+
|
23
|
+
# Provides serializable and deserializable types (SeDes).
|
24
|
+
module Sedes
|
25
|
+
|
26
|
+
# A serializable, big-endian, unsigned integer type.
|
27
|
+
class BigEndianInt
|
28
|
+
|
29
|
+
# Create a serializable, big-endian, unsigned integer.
|
30
|
+
#
|
31
|
+
# @param size [Integer] the size of the big endian.
|
32
|
+
def initialize(size = nil)
|
33
|
+
@size = size
|
34
|
+
end
|
35
|
+
|
36
|
+
# Serialize a big-endian integer.
|
37
|
+
#
|
38
|
+
# @param obj [Integer] the integer to be serialized.
|
39
|
+
# @return [String] a serialized big-endian integer.
|
40
|
+
# @raise [SerializationError] if provided object is not an integer.
|
41
|
+
# @raise [SerializationError] if provided integer is negative.
|
42
|
+
# @raise [SerializationError] if provided integer is too big for @size.
|
43
|
+
def serialize(obj)
|
44
|
+
raise SerializationError, "Can only serialize integers" unless obj.is_a?(Integer)
|
45
|
+
raise SerializationError, "Cannot serialize negative integers" if obj < 0
|
46
|
+
raise SerializationError, "Integer too large (does not fit in #{@size} bytes)" if @size && obj >= 256 ** @size
|
47
|
+
s = obj == 0 ? Constant::BYTE_EMPTY : Util.int_to_big_endian(obj)
|
48
|
+
@size ? "#{Constant::BYTE_ZERO * [0, @size - s.size].max}#{s}" : s
|
49
|
+
end
|
50
|
+
|
51
|
+
# Deserializes an unsigned integer.
|
52
|
+
#
|
53
|
+
# @param serial [String] the serialized integer.
|
54
|
+
# @return [Integer] a number.
|
55
|
+
# @raise [DeserializationError] if provided serial is of wrong size.
|
56
|
+
# @raise [DeserializationError] if provided serial is not of minimal length.
|
57
|
+
def deserialize(serial)
|
58
|
+
raise DeserializationError, "Invalid serialization (wrong size)" if @size && serial.size != @size
|
59
|
+
raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == Constant::BYTE_ZERO
|
60
|
+
serial = serial || Constant::BYTE_ZERO
|
61
|
+
Util.big_endian_to_int(serial)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,97 @@
|
|
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 an recursive-length prefix (RLP) encoder and decoder.
|
21
|
+
module Rlp
|
22
|
+
|
23
|
+
# Provides serializable and deserializable types (SeDes).
|
24
|
+
module Sedes
|
25
|
+
|
26
|
+
# A sedes type for binary values.
|
27
|
+
class Binary
|
28
|
+
|
29
|
+
# A singleton class for binary values of fixed length.
|
30
|
+
class << self
|
31
|
+
|
32
|
+
# Create a serializable bianry of fixed size.
|
33
|
+
#
|
34
|
+
# @param l [Integer] the fixed size of the binary.
|
35
|
+
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
36
|
+
# @return [Eth::Rlp::Sedes::Binary] a serializable binary of fixed size.
|
37
|
+
def fixed_length(l, allow_empty: false)
|
38
|
+
new(min_length: l, max_length: l, allow_empty: allow_empty)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks wether the given object is of a valid binary type.
|
42
|
+
#
|
43
|
+
# @param obj [Object] the supposed binary item to check.
|
44
|
+
# @return [Boolean] true if valid.
|
45
|
+
def valid_type?(obj)
|
46
|
+
obj.instance_of? String
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a serializable bianry of variable size.
|
51
|
+
#
|
52
|
+
# @param min_length [Integer] the minimum size of the binary.
|
53
|
+
# @param max_length [Integer] the maximum size of the binary.
|
54
|
+
# @param allow_empty [Boolean] indicator wether empty binaries should be allowed.
|
55
|
+
def initialize(min_length: 0, max_length: Constant::INFINITY, allow_empty: false)
|
56
|
+
@min_length = min_length
|
57
|
+
@max_length = max_length
|
58
|
+
@allow_empty = allow_empty
|
59
|
+
end
|
60
|
+
|
61
|
+
# Serializes a binary.
|
62
|
+
#
|
63
|
+
# @param obj [String] the binary to serialize.
|
64
|
+
# @return [Object] a serialized binary.
|
65
|
+
# @raise [SerializationError] if provided object is of invalid type.
|
66
|
+
# @raise [SerializationError] if provided binary is of invalid length.
|
67
|
+
def serialize(obj)
|
68
|
+
raise SerializationError, "Object is not a serializable (#{obj.class})" unless self.class.valid_type? obj
|
69
|
+
serial = Util.str_to_bytes obj
|
70
|
+
raise SerializationError, "Object has invalid length" unless valid_length? serial.size
|
71
|
+
serial
|
72
|
+
end
|
73
|
+
|
74
|
+
# Deserializes a binary.
|
75
|
+
#
|
76
|
+
# @param serial [Object] the serialized binary.
|
77
|
+
# @return [String] a deserialized binary.
|
78
|
+
# @raise [DeserializationError] if provided serial is of wrong type.
|
79
|
+
# @raise [DeserializationError] if provided serial is of wrong length.
|
80
|
+
def deserialize(serial)
|
81
|
+
raise DeserializationError, "Objects of type #{serial.class} cannot be deserialized" unless Util.is_primitive? serial
|
82
|
+
raise DeserializationError, "#{serial.class} has invalid length" unless valid_length? serial.size
|
83
|
+
serial
|
84
|
+
end
|
85
|
+
|
86
|
+
# Checks wether the given length fits the defined size boundaries of the
|
87
|
+
# binary type.
|
88
|
+
#
|
89
|
+
# @param length [Integer] the supposed length of the binary item.
|
90
|
+
# @return [Boolean] true if valid.
|
91
|
+
def valid_length?(length)
|
92
|
+
(@min_length <= length && length <= @max_length) || (@allow_empty && length == 0)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,84 @@
|
|
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 an recursive-length prefix (RLP) encoder and decoder.
|
21
|
+
module Rlp
|
22
|
+
|
23
|
+
# Provides serializable and deserializable types (SeDes).
|
24
|
+
module Sedes
|
25
|
+
|
26
|
+
# A sedes type for lists of fixed length.
|
27
|
+
class List < Array
|
28
|
+
|
29
|
+
# Create a serializable list of fixed size.
|
30
|
+
#
|
31
|
+
# @param elements [Array] an array indicating the structure of the list.
|
32
|
+
# @param strict [Boolean] an option to enforce the given structure.
|
33
|
+
def initialize(elements: [], strict: true)
|
34
|
+
super()
|
35
|
+
@strict = strict
|
36
|
+
elements.each do |e|
|
37
|
+
if Sedes.is_sedes?(e)
|
38
|
+
push e
|
39
|
+
elsif Util.is_list?(e)
|
40
|
+
push List.new(elements: e)
|
41
|
+
else
|
42
|
+
raise TypeError, "Instances of List must only contain sedes objects or nested sequences thereof."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Serialize an array.
|
48
|
+
#
|
49
|
+
# @param obj [Array] the array to be serialized.
|
50
|
+
# @return [Array] a serialized list.
|
51
|
+
# @raise [SerializationError] if provided array is not a sequence.
|
52
|
+
# @raise [SerializationError] if provided array is of wrong length.
|
53
|
+
def serialize(obj)
|
54
|
+
raise SerializationError, "Can only serialize sequences" unless Util.is_list?(obj)
|
55
|
+
raise SerializationError, "List has wrong length" if (@strict && self.size != obj.size) || self.size < obj.size
|
56
|
+
result = []
|
57
|
+
obj.zip(self).each_with_index do |(element, sedes), i|
|
58
|
+
result.push sedes.serialize(element)
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
# Deserializes a list.
|
64
|
+
#
|
65
|
+
# @param serial [Array] the serialized list.
|
66
|
+
# @return [Array] a deserialized list.
|
67
|
+
# @raise [DeserializationError] if provided serial is not a sequence.
|
68
|
+
# @raise [DeserializationError] if provided serial is of wrong length.
|
69
|
+
def deserialize(serial)
|
70
|
+
raise DeserializationError, "Can only deserialize sequences" unless Util.is_list?(serial)
|
71
|
+
raise DeserializationError, "List has wrong length" if @strict && serial.size != self.size
|
72
|
+
result = []
|
73
|
+
len = [serial.size, self.size].min
|
74
|
+
len.times do |i|
|
75
|
+
sedes = self[i]
|
76
|
+
element = serial[i]
|
77
|
+
result.push sedes.deserialize(element)
|
78
|
+
end
|
79
|
+
result.freeze
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|