eth-custom 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,207 @@
|
|
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::Encrypter} class to handle PBKDF2-SHA-256 encryption.
|
19
|
+
class Key::Encrypter
|
20
|
+
|
21
|
+
# Provides a specific encrypter error if decryption fails.
|
22
|
+
class EncrypterError < StandardError; end
|
23
|
+
|
24
|
+
# Class method {Eth::Key::Encrypter.perform} to performa an key-store
|
25
|
+
# encryption.
|
26
|
+
#
|
27
|
+
# @param key [Eth::Key] representing a secret key-pair used for encryption.
|
28
|
+
# @param options [Hash] the options to encrypt with.
|
29
|
+
# @option options [String] :kdf key derivation function defaults to pbkdf2.
|
30
|
+
# @option options [String] :id uuid given to the secret key.
|
31
|
+
# @option options [String] :iterations number of iterations for the hash function.
|
32
|
+
# @option options [String] :salt passed to PBKDF.
|
33
|
+
# @option options [String] :iv 128-bit initialisation vector for the cipher.
|
34
|
+
# @option options [Integer] :parallelization parallelization factor for scrypt, defaults to 8.
|
35
|
+
# @option options [Integer] :block_size for scrypt, defaults to 1.
|
36
|
+
# @return [JSON] formatted with encrypted key (cyphertext) and [other identifying data](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition#pbkdf2-sha-256).
|
37
|
+
def self.perform(key, password, options = {})
|
38
|
+
new(key, options).perform(password)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Constructor of the {Eth::Key::Encrypter} class for secret key
|
42
|
+
# encryption. Should not be used; use {Eth::Key::Encrypter.perform}
|
43
|
+
# instead.
|
44
|
+
#
|
45
|
+
# @param key [Eth::Key] representing a secret key-pair used for encryption.
|
46
|
+
# @param options [Hash] the options to encrypt with.
|
47
|
+
# @option options [String] :kdf key derivation function defaults to pbkdf2.
|
48
|
+
# @option options [String] :id uuid given to the secret key.
|
49
|
+
# @option options [String] :iterations number of iterations for the hash function.
|
50
|
+
# @option options [String] :salt passed to PBKDF.
|
51
|
+
# @option options [String] :iv 128-bit initialisation vector for the cipher.
|
52
|
+
# @option options [Integer] :parallelization parallelization factor for scrypt, defaults to 8.
|
53
|
+
# @option options [Integer] :block_size for scrypt, defaults to 1.
|
54
|
+
def initialize(key, options = {})
|
55
|
+
key = Key.new(priv: key) if key.is_a? String
|
56
|
+
@key = key
|
57
|
+
@options = options
|
58
|
+
|
59
|
+
# the key derivation functions default to pbkdf2 if no option is specified
|
60
|
+
# however, if an option is given then it must be either pbkdf2 or scrypt
|
61
|
+
if kdf != "scrypt" && kdf != "pbkdf2"
|
62
|
+
raise EncrypterError, "Unsupported key derivation function: #{kdf}!"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Encrypt the key with a given password.
|
67
|
+
#
|
68
|
+
# @param password [String] a secret key used for encryption
|
69
|
+
# @return [String] a JSON-formatted keystore string.
|
70
|
+
def perform(password)
|
71
|
+
derive_key password
|
72
|
+
encrypt
|
73
|
+
data.to_json
|
74
|
+
end
|
75
|
+
|
76
|
+
# Output containing the encrypted key and
|
77
|
+
# [other identifying data](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition#pbkdf2-sha-256)
|
78
|
+
#
|
79
|
+
# @return [Hash] the encrypted keystore data.
|
80
|
+
def data
|
81
|
+
# default to pbkdf2
|
82
|
+
kdfparams = if kdf == "scrypt"
|
83
|
+
{
|
84
|
+
dklen: 32,
|
85
|
+
n: iterations,
|
86
|
+
p: parallelization,
|
87
|
+
r: block_size,
|
88
|
+
salt: Util.bin_to_hex(salt),
|
89
|
+
}
|
90
|
+
else
|
91
|
+
{
|
92
|
+
c: iterations,
|
93
|
+
dklen: 32,
|
94
|
+
prf: prf,
|
95
|
+
salt: Util.bin_to_hex(salt),
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
{
|
100
|
+
crypto: {
|
101
|
+
cipher: cipher_name,
|
102
|
+
cipherparams: {
|
103
|
+
iv: Util.bin_to_hex(iv),
|
104
|
+
},
|
105
|
+
ciphertext: Util.bin_to_hex(encrypted_key),
|
106
|
+
kdf: kdf,
|
107
|
+
kdfparams: kdfparams,
|
108
|
+
mac: Util.bin_to_hex(mac),
|
109
|
+
},
|
110
|
+
id: id,
|
111
|
+
version: 3,
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
attr_reader :derived_key, :encrypted_key, :key, :options
|
118
|
+
|
119
|
+
def cipher
|
120
|
+
@cipher ||= OpenSSL::Cipher.new(cipher_name).tap do |cipher|
|
121
|
+
cipher.encrypt
|
122
|
+
cipher.iv = iv
|
123
|
+
cipher.key = derived_key[0, (key_length / 2)]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def digest
|
128
|
+
@digest ||= OpenSSL::Digest.new digest_name
|
129
|
+
end
|
130
|
+
|
131
|
+
def derive_key(password)
|
132
|
+
if kdf == "scrypt"
|
133
|
+
@derived_key = SCrypt::Engine.scrypt(password, salt, iterations, block_size, parallelization, key_length)
|
134
|
+
else
|
135
|
+
@derived_key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def encrypt
|
140
|
+
@encrypted_key = cipher.update(Util.hex_to_bin key.private_hex) + cipher.final
|
141
|
+
end
|
142
|
+
|
143
|
+
def mac
|
144
|
+
Util.keccak256(derived_key[(key_length / 2), key_length] + encrypted_key)
|
145
|
+
end
|
146
|
+
|
147
|
+
def kdf
|
148
|
+
options[:kdf] || "pbkdf2"
|
149
|
+
end
|
150
|
+
|
151
|
+
def cipher_name
|
152
|
+
"aes-128-ctr"
|
153
|
+
end
|
154
|
+
|
155
|
+
def digest_name
|
156
|
+
"sha256"
|
157
|
+
end
|
158
|
+
|
159
|
+
def prf
|
160
|
+
"hmac-#{digest_name}"
|
161
|
+
end
|
162
|
+
|
163
|
+
def key_length
|
164
|
+
32
|
165
|
+
end
|
166
|
+
|
167
|
+
def salt_length
|
168
|
+
32
|
169
|
+
end
|
170
|
+
|
171
|
+
def iv_length
|
172
|
+
16
|
173
|
+
end
|
174
|
+
|
175
|
+
def id
|
176
|
+
@id ||= options[:id] || SecureRandom.uuid
|
177
|
+
end
|
178
|
+
|
179
|
+
def iterations
|
180
|
+
options[:iterations] || 262_144
|
181
|
+
end
|
182
|
+
|
183
|
+
def salt
|
184
|
+
@salt ||= if options[:salt]
|
185
|
+
Util.hex_to_bin options[:salt]
|
186
|
+
else
|
187
|
+
SecureRandom.random_bytes(salt_length)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def iv
|
192
|
+
@iv ||= if options[:iv]
|
193
|
+
Util.hex_to_bin options[:iv]
|
194
|
+
else
|
195
|
+
SecureRandom.random_bytes(iv_length)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def parallelization
|
200
|
+
options[:parallelization] || 8
|
201
|
+
end
|
202
|
+
|
203
|
+
def block_size
|
204
|
+
options[:block_size] || 1
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/eth/key.rb
ADDED
@@ -0,0 +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.
|
22
|
+
module Eth
|
23
|
+
|
24
|
+
# The {Eth::Key} class to handle Secp256k1 private/public key-pairs.
|
25
|
+
class Key
|
26
|
+
|
27
|
+
# The {Eth::Key::Decrypter} class to handle PBKDF2-SHA-256 decryption.
|
28
|
+
autoload :Decrypter, "eth/key/decrypter"
|
29
|
+
|
30
|
+
# The {Eth::Key::Encrypter} class to handle PBKDF2-SHA-256 encryption.
|
31
|
+
autoload :Encrypter, "eth/key/encrypter"
|
32
|
+
|
33
|
+
# The `Secp256k1::PrivateKey` of the {Eth::Key} pair.
|
34
|
+
attr_reader :private_key
|
35
|
+
|
36
|
+
# The `Secp256k1::PublicKey` of the {Eth::Key} pair.
|
37
|
+
attr_reader :public_key
|
38
|
+
|
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)
|
44
|
+
|
45
|
+
# Creates a new, randomized libsecp256k1 context.
|
46
|
+
ctx = Secp256k1::Context.new context_randomization_bytes: SecureRandom.random_bytes(32)
|
47
|
+
|
48
|
+
# Creates a new random key pair (public, private).
|
49
|
+
key = ctx.generate_key_pair
|
50
|
+
|
51
|
+
unless priv.nil?
|
52
|
+
|
53
|
+
# Converts hex private keys to binary strings.
|
54
|
+
priv = Util.hex_to_bin priv if Util.is_hex? priv
|
55
|
+
|
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
|
63
|
+
end
|
64
|
+
|
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*"
|
83
|
+
end
|
84
|
+
|
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
|
98
|
+
end
|
99
|
+
|
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
|
113
|
+
|
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
|
119
|
+
end
|
120
|
+
|
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
|
127
|
+
end
|
128
|
+
|
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
|
135
|
+
end
|
136
|
+
|
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
|
143
|
+
end
|
144
|
+
|
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
|
152
|
+
|
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
|
158
|
+
end
|
159
|
+
|
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
|
165
|
+
end
|
166
|
+
end
|
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
|