ciri 0.0.0 → 0.0.1

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +14 -0
  3. data/.rspec +2 -1
  4. data/.travis.yml +11 -4
  5. data/Gemfile.lock +3 -0
  6. data/README.md +44 -34
  7. data/Rakefile +47 -4
  8. data/ciri.gemspec +13 -12
  9. data/docker/Base +34 -0
  10. data/lib/ciri/actor.rb +223 -0
  11. data/lib/ciri/chain.rb +293 -0
  12. data/lib/ciri/chain/block.rb +47 -0
  13. data/lib/ciri/chain/header.rb +62 -0
  14. data/lib/ciri/chain/transaction.rb +145 -0
  15. data/lib/ciri/crypto.rb +58 -5
  16. data/lib/ciri/db/backend/memory.rb +68 -0
  17. data/lib/ciri/db/backend/rocks.rb +104 -0
  18. data/lib/ciri/db/backend/rocks_db.rb +278 -0
  19. data/lib/ciri/devp2p/peer.rb +10 -2
  20. data/lib/ciri/devp2p/protocol.rb +11 -3
  21. data/lib/ciri/devp2p/protocol_io.rb +6 -3
  22. data/lib/ciri/devp2p/rlpx.rb +1 -0
  23. data/lib/ciri/devp2p/rlpx/encryption_handshake.rb +1 -1
  24. data/lib/ciri/devp2p/rlpx/frame_io.rb +1 -1
  25. data/lib/ciri/devp2p/rlpx/message.rb +4 -4
  26. data/lib/ciri/devp2p/server.rb +14 -13
  27. data/lib/ciri/eth.rb +33 -0
  28. data/lib/ciri/eth/peer.rb +64 -0
  29. data/lib/ciri/eth/protocol_manage.rb +122 -0
  30. data/lib/ciri/eth/protocol_messages.rb +158 -0
  31. data/lib/ciri/eth/synchronizer.rb +188 -0
  32. data/lib/ciri/ethash.rb +123 -0
  33. data/lib/ciri/evm.rb +140 -0
  34. data/lib/ciri/evm/account.rb +50 -0
  35. data/lib/ciri/evm/block_info.rb +31 -0
  36. data/lib/ciri/evm/forks/frontier.rb +183 -0
  37. data/lib/ciri/evm/instruction.rb +92 -0
  38. data/lib/ciri/evm/machine_state.rb +81 -0
  39. data/lib/ciri/evm/op.rb +536 -0
  40. data/lib/ciri/evm/serialize.rb +60 -0
  41. data/lib/ciri/evm/sub_state.rb +64 -0
  42. data/lib/ciri/evm/vm.rb +379 -0
  43. data/lib/ciri/forks.rb +38 -0
  44. data/lib/ciri/forks/frontier.rb +43 -0
  45. data/lib/ciri/key.rb +7 -1
  46. data/lib/ciri/pow.rb +95 -0
  47. data/lib/ciri/rlp.rb +3 -53
  48. data/lib/ciri/rlp/decode.rb +100 -40
  49. data/lib/ciri/rlp/encode.rb +95 -34
  50. data/lib/ciri/rlp/serializable.rb +61 -91
  51. data/lib/ciri/types/address.rb +70 -0
  52. data/lib/ciri/types/errors.rb +36 -0
  53. data/lib/ciri/utils.rb +45 -13
  54. data/lib/ciri/utils/lib_c.rb +46 -0
  55. data/lib/ciri/utils/logger.rb +99 -0
  56. data/lib/ciri/utils/number.rb +67 -0
  57. data/lib/ciri/version.rb +1 -1
  58. metadata +67 -7
  59. data/lib/ciri/devp2p/actor.rb +0 -224
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require_relative 'forks/frontier'
25
+
26
+ module Ciri
27
+ module Forks
28
+
29
+ # Fork configure
30
+ ForkConfig = Struct.new(:cost_of_operation, :cost_of_memory, :intrinsic_gas_of_transaction, keyword_init: true)
31
+
32
+ def self.detect_fork(header: nil, number: nil)
33
+ number ||= header.number
34
+ Frontier.fork_config
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/evm/forks/frontier'
25
+
26
+ module Ciri
27
+ module Forks
28
+ module Frontier
29
+
30
+ class << self
31
+ def fork_config
32
+ ForkConfig.new(
33
+ cost_of_operation: proc {|vm| EVM::Forks::Frontier::Cost.cost_of_operation vm},
34
+ cost_of_memory: proc {|i| EVM::Forks::Frontier::Cost.cost_of_memory i},
35
+ intrinsic_gas_of_transaction: proc {|t| EVM::Forks::Frontier::Cost.intrinsic_gas_of_transaction t}
36
+ # transaction_klass: nil
37
+ )
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -74,8 +74,14 @@ module Ciri
74
74
  end
75
75
 
76
76
  private
77
+
77
78
  def secp256k1_key
78
- @secp256k1_key ||= Crypto.ensure_secp256k1_key(privkey: ec_key.private_key.to_s(2))
79
+ privkey = ec_key.private_key.to_s(2)
80
+ # some times below error will occurs, raise error with more detail
81
+ unless privkey.instance_of?(String) && privkey.size == 32
82
+ raise ArgumentError, "privkey must be composed of 32 bytes, #{bytes}: #{privkey.size} privkey: #{Utils.data_to_hex privkey}"
83
+ end
84
+ @secp256k1_key ||= Crypto.ensure_secp256k1_key(privkey: privkey)
79
85
  end
80
86
  end
81
87
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/utils'
25
+ require_relative 'ethash'
26
+ require 'lru_redux'
27
+ require 'concurrent'
28
+
29
+ module Ciri
30
+
31
+ # POW
32
+ # see py-evm https://github.com/ethereum/py-evm/blob/026553da69bbea314fe26c8c34d453f66bfb4d30/evm/consensus/pow.py
33
+ module POW
34
+
35
+ extend self
36
+
37
+ class Error < StandardError
38
+ end
39
+ class InvalidError < Error
40
+ end
41
+
42
+ class GivingUpError < Error
43
+ end
44
+
45
+ # thread safe caches
46
+ @cache_seeds = Concurrent::Array.new(['\x00'.b * 32])
47
+ @cache_by_seed = LruRedux::ThreadSafeCache.new(10)
48
+
49
+ def get_cache(block_number)
50
+ epoch = block_number / Ethash::EPOCH_LENGTH
51
+ while @cache_seeds.size <= epoch
52
+ @cache_seeds.append(Utils.sha3(@cache_seeds[-1]))
53
+ end
54
+
55
+ seed = @cache_seeds[epoch]
56
+
57
+ @cache_by_seed.getset(seed) do
58
+ Ethash.mkcache_bytes(block_number)
59
+ end
60
+ end
61
+
62
+ def check_pow(block_number, mining_hash, mix_hash, nonce_bytes, difficulty)
63
+ raise ArgumentError.new "mix_hash.length must equal to 32" if mix_hash.size != 32
64
+ raise ArgumentError.new "mining_hash.length must equal to 32" if mining_hash.size != 32
65
+ raise ArgumentError.new "nonce.length must equal to 8" if nonce_bytes.size != 8
66
+
67
+ cache = get_cache(block_number)
68
+ out_mix_hash, out_result = Ethash.hashimoto_light(block_number, cache, mining_hash, Utils.big_endian_decode(nonce_bytes))
69
+
70
+ if out_mix_hash != mix_hash
71
+ raise InvalidError.new("mix hash mismatch; #{Utils.data_to_hex(out_mix_hash)} != #{Utils.data_to_hex(mix_hash)}")
72
+ end
73
+
74
+ result = Utils.big_endian_decode(out_result)
75
+ unless result < 2 ** 256 / difficulty
76
+ raise InvalidError.new("difficulty not enough, need difficulty #{difficulty}, but result #{result}")
77
+ end
78
+ end
79
+
80
+ MAX_TEST_MINE_ATTEMPTS = 1000
81
+
82
+ def mine_pow_nonce(block_number, mining_hash, difficulty)
83
+ cache = get_cache(block_number)
84
+ MAX_TEST_MINE_ATTEMPTS.times do |nonce|
85
+ out_mix_hash, out_result = Ethash.hashimoto_light(block_number, cache, mining_hash, nonce)
86
+ result = Utils.big_endian_decode(out_result)
87
+ result_cap = 2 ** 256 / difficulty
88
+ return [out_mix_hash, Utils.big_endian_encode(nonce).rjust(8, "\x00")] if result <= result_cap
89
+ end
90
+
91
+ raise GivingUpError.new("tries too many times, giving up")
92
+ end
93
+
94
+ end
95
+ end
@@ -27,62 +27,12 @@ require_relative 'rlp/serializable'
27
27
 
28
28
  module Ciri
29
29
  module RLP
30
+
30
31
  class InvalidValueError < StandardError
31
32
  end
32
33
 
33
- class << self
34
-
35
- # Decode input from rlp encoding, only produce string or array
36
- #
37
- # Examples:
38
- #
39
- # Ciri::RLP.decode(input)
40
- #
41
- def decode(input, type = nil)
42
- output = Decode.decode(input)
43
- if type
44
- output = decode_with_type(output, type)
45
- end
46
- output
47
- end
48
-
49
- # Encode input to rlp encoding, only allow string or array
50
- #
51
- # Examples:
52
- #
53
- # Ciri::RLP.encode("hello world")
54
- #
55
- def encode(input, type = nil)
56
- if type
57
- input = encode_with_type(input, type)
58
- end
59
- Encode.encode(input)
60
- end
34
+ extend Encode
35
+ extend Decode
61
36
 
62
- # Use this method before RLP.encode, this method encode ruby objects to rlp friendly format, string or array.
63
- # see Ciri::RLP::Serializable::TYPES for supported types
64
- #
65
- # Examples:
66
- #
67
- # item = Ciri::RLP.encode_with_type(number, :int, zero: "\x00".b)
68
- # encoded_text = Ciri::RLP.encode(item)
69
- #
70
- def encode_with_type(item, type, zero: '')
71
- Serializable.encode_with_type(item, type, zero: zero)
72
- end
73
-
74
- # Use this method after RLP.decode, decode values from string or array to specific types
75
- # see Ciri::RLP::Serializable::TYPES for supported types
76
- #
77
- # Examples:
78
- #
79
- # item = Ciri::RLP.decode(encoded_text)
80
- # number = Ciri::RLP.decode_with_type(item, :int)
81
- #
82
- def decode_with_type(item, type)
83
- Serializable.decode_with_type(item, type)
84
- end
85
-
86
- end
87
37
  end
88
38
  end
@@ -27,55 +27,115 @@ module Ciri
27
27
  module RLP
28
28
  module Decode
29
29
 
30
- class InvalidInput < StandardError
30
+ # Decode input from rlp encoding, only produce string or array
31
+ #
32
+ # Examples:
33
+ #
34
+ # Ciri::RLP.decode(input)
35
+ #
36
+ def decode(input, type = Raw)
37
+ decode_with_type(input, type)
31
38
  end
32
39
 
33
- class << self
34
- def decode(input)
35
- s = StringIO.new(input).binmode
40
+ # Use this method after RLP.decode, decode values from string or array to specific types
41
+ # see Ciri::RLP::Serializable::TYPES for supported types
42
+ #
43
+ # Examples:
44
+ #
45
+ # item = Ciri::RLP.decode(encoded_text)
46
+ # decode_with_type(item, Integer)
47
+ #
48
+ def decode_with_type(s, type)
49
+ s = StringIO.new(s) if s.is_a?(String)
50
+ if type == Integer
51
+ item = s.read(1)
52
+ if item.nil?
53
+ raise InvalidValueError.new "invalid bool value nil"
54
+ elsif item == "\x80".b || item.empty?
55
+ 0
56
+ elsif item.ord < 0x80
57
+ item.ord
58
+ else
59
+ size = item[0].ord - 0x80
60
+ Ciri::Utils.big_endian_decode(s.read(size))
61
+ end
62
+ elsif type == Bool
63
+ item = s.read(1)
64
+ if item == Bool::ENCODED_TRUE
65
+ true
66
+ elsif item == Bool::ENCODED_FALSE
67
+ false
68
+ else
69
+ raise InvalidValueError.new "invalid bool value #{item}"
70
+ end
71
+ elsif type.is_a?(Class) && type < Serializable
72
+ type.rlp_decode!(s)
73
+ elsif type.is_a?(Array)
74
+ decode_list(s) do |list, s2|
75
+ until s2.eof?
76
+ list << decode_with_type(s2, type[0])
77
+ end
78
+ end
79
+ elsif type == Raw
36
80
  decode_stream(s)
81
+ else
82
+ raise RLP::InvalidValueError.new "unknown type #{type}"
37
83
  end
84
+ rescue
85
+ STDERR.puts "when decoding #{s} into #{type}"
86
+ raise
87
+ end
38
88
 
39
- private
40
- def decode_stream(s)
41
- c = s.read(1)
42
- case c.ord
43
- when 0x00..0x7f
44
- c
45
- when 0x80..0xb7
46
- length = c.ord - 0x80
47
- s.read(length)
48
- when 0xb8..0xbf
49
- length_binary = s.read(c.ord - 0xb7)
50
- length = int_from_binary(length_binary)
51
- s.read(length)
52
- when 0xc0..0xf7
53
- length = c.ord - 0xc0
54
- s2 = StringIO.new s.read(length)
55
- list = []
56
- until s2.eof?
57
- list << decode_stream(s2)
58
- end
59
- list
60
- when 0xf8..0xff
61
- length_binary = s.read(c.ord - 0xf7)
62
- length = int_from_binary(length_binary)
63
- s2 = StringIO.new s.read(length)
64
- list = []
65
- until s2.eof?
66
- list << decode_stream(s2)
67
- end
68
- list
69
- else
70
- raise InvalidInput.new("invalid char #{c}")
71
- end
72
- end
89
+ protected
90
+
91
+ def decode_list(s, first_char = nil, &decoder)
92
+ s = StringIO.new(s) if s.is_a?(String)
93
+ c = first_char || s.read(1)
94
+ list = []
73
95
 
74
- def int_from_binary(input)
75
- Ciri::Utils.big_endian_decode(input)
96
+ sub_s = case c.ord
97
+ when 0xc0..0xf7
98
+ length = c.ord - 0xc0
99
+ s.read(length)
100
+ when 0xf8..0xff
101
+ length_binary = s.read(c.ord - 0xf7)
102
+ length = int_from_binary(length_binary)
103
+ s.read(length)
104
+ else
105
+ raise InvalidValueError.new("invalid char #{c}")
106
+ end
107
+
108
+ decoder.call(list, StringIO.new(sub_s))
109
+ list
110
+ end
111
+
112
+ private
113
+
114
+ def decode_stream(s)
115
+ c = s.read(1)
116
+ case c.ord
117
+ when 0x00..0x7f
118
+ c
119
+ when 0x80..0xb7
120
+ length = c.ord - 0x80
121
+ s.read(length)
122
+ when 0xb8..0xbf
123
+ length_binary = s.read(c.ord - 0xb7)
124
+ length = int_from_binary(length_binary)
125
+ s.read(length)
126
+ else
127
+ decode_list(s, c) do |list, s2|
128
+ until s2.eof?
129
+ list << decode_stream(s2)
130
+ end
131
+ end
76
132
  end
133
+ end
77
134
 
135
+ def int_from_binary(input)
136
+ Ciri::Utils.big_endian_decode(input)
78
137
  end
138
+
79
139
  end
80
140
  end
81
141
  end
@@ -28,52 +28,113 @@ module Ciri
28
28
  class InputOverflow < StandardError
29
29
  end
30
30
 
31
- class << self
31
+ # Encode input to rlp encoding, only allow string or array
32
+ #
33
+ # Examples:
34
+ #
35
+ # Ciri::RLP.encode("hello world")
36
+ #
37
+ def encode(input, type = Raw)
38
+ encode_with_type(input, type)
39
+ end
32
40
 
33
- def encode(input)
34
- result = if input.is_a?(String)
35
- encode_string(input)
36
- elsif input.is_a?(Array)
37
- encode_list(input)
38
- else
39
- raise ArgumentError.new('input must be a String or Array')
40
- end
41
- result.b
41
+ def encode_simple(input)
42
+ if input.is_a?(Array)
43
+ encode_list(input) {|i| encode_simple(i)}
44
+ elsif input.is_a?(Integer)
45
+ encode(input, Integer)
46
+ elsif input.is_a?(Serializable)
47
+ input.rlp_encode!
48
+ else
49
+ encode(input)
42
50
  end
51
+ end
43
52
 
44
- private
45
- def encode_string(input)
46
- length = input.length
47
- if length == 1 && input.ord < 0x80
48
- input
49
- elsif length < 56
50
- to_binary(0x80 + length) + input
51
- elsif length < 256 ** 8
52
- binary_length = to_binary(length)
53
- to_binary(0xb7 + binary_length.size) + binary_length + input
53
+ # Use this method before RLP.encode, this method encode ruby objects to rlp friendly format, string or array.
54
+ # see Ciri::RLP::Serializable::TYPES for supported types
55
+ #
56
+ # Examples:
57
+ #
58
+ # item = Ciri::RLP.encode_with_type(number, :int, zero: "\x00".b)
59
+ # encoded_text = Ciri::RLP.encode(item)
60
+ #
61
+ def encode_with_type(item, type, zero: '')
62
+ if type == Integer
63
+ if item == 0
64
+ "\x80".b
65
+ elsif item < 0x80
66
+ Ciri::Utils.big_endian_encode(item, zero)
54
67
  else
55
- raise InputOverflow.new("input length #{input.size} is too long")
68
+ buf = Ciri::Utils.big_endian_encode(item, zero)
69
+ [0x80 + buf.size].pack("c*") + buf
56
70
  end
71
+ elsif type == Bool
72
+ item ? Bool::ENCODED_TRUE : Bool::ENCODED_FALSE
73
+ elsif type.is_a?(Class) && type < Serializable
74
+ item.rlp_encode!
75
+ elsif type.is_a?(Array)
76
+ if type.size == 1 # array type
77
+ encode_list(item) {|i| encode_with_type(i, type[0])}
78
+ else # unknown
79
+ raise RLP::InvalidValueError.new "type size should be 1, got #{type}"
80
+ end
81
+ elsif type == Raw
82
+ encode_raw(item)
83
+ else
84
+ raise RLP::InvalidValueError.new "unknown type #{type}"
57
85
  end
86
+ rescue
87
+ STDERR.puts "when encoding #{Utils.data_to_hex item.to_s} into #{type}"
88
+ raise
89
+ end
58
90
 
59
- def encode_list(input)
60
- output = input.map {|item| encode(item)}.join
61
- length = output.length
62
- if length < 56
63
- to_binary(0xc0 + length) + output
64
- elsif length < 256 ** 8
65
- binary_length = to_binary(length)
66
- to_binary(0xf7 + binary_length.size) + binary_length + output
67
- else
68
- raise InputOverflow.new("input length #{input.size} is too long")
69
- end
91
+ protected
92
+
93
+ def encode_raw(input)
94
+ result = if input.is_a?(String)
95
+ encode_string(input)
96
+ elsif input.is_a?(Array)
97
+ encode_list(input) {|item| encode(item)}
98
+ else
99
+ raise ArgumentError.new("input must be a String or Array, #{input.inspect}")
100
+ end
101
+ result.b
102
+ end
103
+
104
+ def encode_string(input)
105
+ length = input.length
106
+ if length == 1 && input.ord < 0x80
107
+ input
108
+ elsif length < 56
109
+ to_binary(0x80 + length) + input
110
+ elsif length < 256 ** 8
111
+ binary_length = to_binary(length)
112
+ to_binary(0xb7 + binary_length.size) + binary_length + input
113
+ else
114
+ raise InputOverflow.new("input length #{input.size} is too long")
70
115
  end
116
+ end
71
117
 
72
- def to_binary(n)
73
- Ciri::Utils.big_endian_encode(n)
118
+ def encode_list(input, &encoder)
119
+ input ||= [] # allow nil list
120
+ output = encoder ? input.map {|item| encoder.call(item)}.join : input.join
121
+ length = output.length
122
+ if length < 56
123
+ to_binary(0xc0 + length) + output
124
+ elsif length < 256 ** 8
125
+ binary_length = to_binary(length)
126
+ to_binary(0xf7 + binary_length.size) + binary_length + output
127
+ else
128
+ raise InputOverflow.new("input length #{input.size} is too long")
74
129
  end
130
+ end
131
+
132
+ private
75
133
 
134
+ def to_binary(n)
135
+ Ciri::Utils.big_endian_encode(n)
76
136
  end
137
+
77
138
  end
78
139
  end
79
140
  end