eth 0.4.18 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +6 -2
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.github/workflows/spec.yml +52 -0
  5. data/.gitignore +24 -24
  6. data/.gitmodules +3 -3
  7. data/.yardopts +1 -0
  8. data/AUTHORS.txt +27 -0
  9. data/CHANGELOG.md +63 -13
  10. data/Gemfile +12 -4
  11. data/LICENSE.txt +202 -22
  12. data/README.md +231 -76
  13. data/bin/console +4 -4
  14. data/bin/setup +5 -4
  15. data/codecov.yml +6 -0
  16. data/eth.gemspec +23 -19
  17. data/lib/eth/abi/type.rb +178 -0
  18. data/lib/eth/abi.rb +396 -0
  19. data/lib/eth/address.rb +57 -10
  20. data/lib/eth/api.rb +223 -0
  21. data/lib/eth/chain.rb +151 -0
  22. data/lib/eth/client/http.rb +63 -0
  23. data/lib/eth/client/ipc.rb +50 -0
  24. data/lib/eth/client.rb +232 -0
  25. data/lib/eth/constant.rb +71 -0
  26. data/lib/eth/eip712.rb +184 -0
  27. data/lib/eth/key/decrypter.rb +121 -85
  28. data/lib/eth/key/encrypter.rb +180 -99
  29. data/lib/eth/key.rb +134 -45
  30. data/lib/eth/rlp/decoder.rb +114 -0
  31. data/lib/eth/rlp/encoder.rb +78 -0
  32. data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
  33. data/lib/eth/rlp/sedes/binary.rb +97 -0
  34. data/lib/eth/rlp/sedes/list.rb +84 -0
  35. data/lib/eth/rlp/sedes.rb +74 -0
  36. data/lib/eth/rlp.rb +63 -0
  37. data/lib/eth/signature.rb +163 -0
  38. data/lib/eth/solidity.rb +75 -0
  39. data/lib/eth/tx/eip1559.rb +337 -0
  40. data/lib/eth/tx/eip2930.rb +329 -0
  41. data/lib/eth/tx/legacy.rb +297 -0
  42. data/lib/eth/tx.rb +269 -146
  43. data/lib/eth/unit.rb +49 -0
  44. data/lib/eth/util.rb +235 -0
  45. data/lib/eth/version.rb +18 -1
  46. data/lib/eth.rb +34 -67
  47. metadata +47 -95
  48. data/.github/workflows/build.yml +0 -36
  49. data/lib/eth/gas.rb +0 -7
  50. data/lib/eth/open_ssl.rb +0 -395
  51. data/lib/eth/secp256k1.rb +0 -5
  52. data/lib/eth/sedes.rb +0 -39
  53. 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
- attr_reader :private_key, :public_key
33
+ # The `Secp256k1::PrivateKey` of the {Eth::Key} pair.
34
+ attr_reader :private_key
7
35
 
8
- def self.encrypt(key, password)
9
- key = new(priv: key) unless key.is_a?(Key)
36
+ # The `Secp256k1::PublicKey` of the {Eth::Key} pair.
37
+ attr_reader :public_key
10
38
 
11
- Encrypter.perform key.private_hex, password
12
- end
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
- def self.decrypt(data, password)
15
- priv = Decrypter.perform data, password
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
- def self.personal_recover(message, signature)
20
- bin_signature = Utils.hex_to_bin(signature).bytes.rotate(-1).pack("c*")
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
- def initialize(priv: nil)
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
- def private_hex
30
- private_key.to_hex
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
- def public_bytes
34
- public_key.to_bytes
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
- def public_hex
38
- public_key.to_hex
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
- def address
42
- Utils.public_key_to_address public_hex
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
- alias_method :to_address, :address
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
- def sign(message)
48
- sign_hash message_hash(message)
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
- def sign_hash(hash)
52
- loop do
53
- signature = OpenSsl.sign_compact hash, private_hex, public_hex
54
- return signature if valid_s? signature
55
- end
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
- def verify_signature(message, signature)
59
- hash = message_hash(message)
60
- public_hex == OpenSsl.recover_compact(hash, signature)
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
- def personal_sign(message)
64
- Utils.bin_to_hex(sign(Utils.prefix_message(message)).bytes.rotate(1).pack("c*"))
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
- private
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
- def message_hash(message)
70
- Utils.keccak256 message
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
- def valid_s?(signature)
74
- s_value = Utils.v_r_s_for(signature).last
75
- s_value <= Secp256k1::N / 2 && s_value != 0
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