ciri 0.0.0

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.
data/lib/ciri/key.rb ADDED
@@ -0,0 +1,81 @@
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 'openssl'
25
+ require_relative 'crypto'
26
+
27
+ module Ciri
28
+
29
+ # Ciri::Key represent private/public key pair, it support several encryption methods used in Ethereum
30
+ #
31
+ # Examples:
32
+ #
33
+ # key = Ciri::Key.random
34
+ # key.ecdsa_signature(data)
35
+ #
36
+ class Key
37
+
38
+ class << self
39
+ def ecdsa_recover(msg, signature)
40
+ raw_public_key = Crypto.ecdsa_recover(msg, signature, return_raw_key: true)
41
+ Ciri::Key.new(raw_public_key: raw_public_key)
42
+ end
43
+
44
+ def random
45
+ ec_key = OpenSSL::PKey::EC.new('secp256k1')
46
+ ec_key.generate_key
47
+ Ciri::Key.new(ec_key: ec_key)
48
+ end
49
+ end
50
+
51
+ attr_reader :ec_key
52
+
53
+ # initialized from ec_key or raw keys
54
+ # ec_key is a OpenSSL::PKey::EC object, raw keys is bytes presented keys
55
+ def initialize(ec_key: nil, raw_public_key: nil, raw_private_key: nil)
56
+ @ec_key = ec_key || Ciri::Utils.create_ec_pk(raw_privkey: raw_private_key, raw_pubkey: raw_public_key)
57
+ end
58
+
59
+ # raw public key
60
+ def raw_public_key
61
+ @raw_public_key ||= ec_key.public_key.to_bn.to_s(2)
62
+ end
63
+
64
+ def ecdsa_signature(data)
65
+ Crypto.ecdsa_signature(secp256k1_key, data)
66
+ end
67
+
68
+ def ecies_encrypt(message, shared_mac_data = '')
69
+ Crypto.ecies_encrypt(message, ec_key, shared_mac_data)
70
+ end
71
+
72
+ def ecies_decrypt(data, shared_mac_data = '')
73
+ Crypto.ecies_decrypt(data, ec_key, shared_mac_data)
74
+ end
75
+
76
+ private
77
+ def secp256k1_key
78
+ @secp256k1_key ||= Crypto.ensure_secp256k1_key(privkey: ec_key.private_key.to_s(2))
79
+ end
80
+ end
81
+ end
data/lib/ciri/rlp.rb ADDED
@@ -0,0 +1,88 @@
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 'rlp/decode'
25
+ require_relative 'rlp/encode'
26
+ require_relative 'rlp/serializable'
27
+
28
+ module Ciri
29
+ module RLP
30
+ class InvalidValueError < StandardError
31
+ end
32
+
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
61
+
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
+ end
88
+ end
@@ -0,0 +1,81 @@
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 'stringio'
25
+
26
+ module Ciri
27
+ module RLP
28
+ module Decode
29
+
30
+ class InvalidInput < StandardError
31
+ end
32
+
33
+ class << self
34
+ def decode(input)
35
+ s = StringIO.new(input).binmode
36
+ decode_stream(s)
37
+ end
38
+
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
73
+
74
+ def int_from_binary(input)
75
+ Ciri::Utils.big_endian_decode(input)
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,79 @@
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
+ module Ciri
25
+ module RLP
26
+ module Encode
27
+
28
+ class InputOverflow < StandardError
29
+ end
30
+
31
+ class << self
32
+
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
42
+ end
43
+
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
54
+ else
55
+ raise InputOverflow.new("input length #{input.size} is too long")
56
+ end
57
+ end
58
+
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
70
+ end
71
+
72
+ def to_binary(n)
73
+ Ciri::Utils.big_endian_encode(n)
74
+ end
75
+
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,268 @@
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
+
26
+ module Ciri
27
+ module RLP
28
+
29
+ # represent bool types: true | false
30
+ class Bool
31
+ ENCODED_TRUE = Ciri::Utils.big_endian_encode(0x01)
32
+ ENCODED_FALSE = Ciri::Utils.big_endian_encode(0x00)
33
+ end
34
+
35
+ # Serializable module allow ruby objects serialize/deserialize to or from RLP encoding.
36
+ # See Ciri::RLP::Serializable::TYPES for supported type.
37
+ #
38
+ # schema method define ordered data structure for class, and determine how to encoding objects.
39
+ #
40
+ # schema follow `{attr_name: type}` format,
41
+ # if attr is raw type(string or array of string), you can just use `:attr_name` to define it
42
+ # schema simple types include Integer, Bool, String, Array...
43
+ #
44
+ # schema also support complex types: array and serializable.
45
+ #
46
+ # array types represented as `{attr_name: [type]}`, for example: `{bills: [Integer]}` means value of bill attr is an array of integer
47
+ # serializable type represent value of attr is a RLP serializable object
48
+ #
49
+ #
50
+ # Examples:
51
+ #
52
+ # class AuthMsgV4
53
+ # include Ciri::RLP::Serializable
54
+ #
55
+ # # define schema
56
+ # schema [
57
+ # :signature, # raw type: string
58
+ # {initiator_pubkey: MySerializableKey}, # this attr is a RLP serializable object
59
+ # {nonce: [Integer]},
60
+ # {version: Integer}
61
+ # ]
62
+ #
63
+ # # default values
64
+ # default_data(got_plain: false)
65
+ # end
66
+ #
67
+ # msg = AuthMsgV4.new(signature: "\x00", initiator_pubkey: my_pubkey, nonce: [1, 2, 3], version: 4)
68
+ # encoded = msg.rlp_encode!
69
+ # msg2 = AuthMsgV4.rlp_decode!(encoded)
70
+ # msg == msg2 # true
71
+ #
72
+ module Serializable
73
+ # nil represent RLP raw value(string or array of string)
74
+ TYPES = [nil, Integer, Bool].map {|key| [key, true]}.to_h.freeze
75
+
76
+ # Schema specific columns types of classes, normally you should not use Serializable::Schema directly
77
+ #
78
+ class Schema
79
+ class InvalidSchemaError < StandardError
80
+ end
81
+
82
+ # keys return data columns array
83
+ attr_reader :keys
84
+
85
+ def initialize(schema)
86
+ keys = []
87
+ @_schema = {}
88
+
89
+ schema.each do |key|
90
+ key, type = key.is_a?(Hash) ? key.to_a[0] : [key, nil]
91
+ raise InvalidSchemaError.new("missing type #{type} for key #{key}") unless check_key_type(type)
92
+ keys << key
93
+ @_schema[key] = type
94
+ end
95
+
96
+ @_schema.freeze
97
+ @keys = keys.freeze
98
+ end
99
+
100
+ # Get column type, see Serializable::TYPES for supported type
101
+ def [](key)
102
+ @_schema[key]
103
+ end
104
+
105
+ # Validate data, data is a Hash
106
+ def validate!(data)
107
+ keys.each do |key|
108
+ raise InvalidSchemaError.new("missing key #{key}") unless data.key?(key)
109
+ end
110
+ end
111
+
112
+ def rlp_encode!(data, raw: true)
113
+ # pre-encode, encode data to rlp compatible format(only string or array)
114
+ data_list = keys.map do |key|
115
+ Serializable.encode_with_type(data[key], self[key])
116
+ end
117
+ raw ? RLP.encode(data_list) : data_list
118
+ end
119
+
120
+ def rlp_decode!(input, raw: true)
121
+ data = raw ? RLP.decode(input) : input
122
+ keys.each_with_index.map do |key, i|
123
+ # decode data by type
124
+ decoded_item = Serializable.decode_with_type(data[i], self[key])
125
+ [key, decoded_item]
126
+ end.to_h
127
+ end
128
+
129
+
130
+ private
131
+ def check_key_type(type)
132
+ return true if TYPES.key?(type)
133
+ return true if type.is_a?(Class) && type < Serializable
134
+
135
+ if type.is_a?(Array) && type.size == 1
136
+ check_key_type(type[0])
137
+ else
138
+ false
139
+ end
140
+ end
141
+ end
142
+
143
+ module ClassMethods
144
+ # Decode object from input
145
+ def rlp_decode(input, raw: true)
146
+ data = schema.rlp_decode!(input, raw: raw)
147
+ self.new(data)
148
+ end
149
+
150
+ alias rlp_decode! rlp_decode
151
+
152
+ def schema(data_schema = nil)
153
+ @data_schema ||= Schema.new(data_schema).tap do |schema|
154
+ # define attributes methods
155
+ define_attributes(schema)
156
+ end
157
+ end
158
+
159
+ def default_data(data = nil)
160
+ @default_data ||= data
161
+ end
162
+
163
+ private
164
+ def define_attributes(schema)
165
+ schema.keys.each do |attribute|
166
+ module_eval <<-ATTR_METHODS
167
+ def #{attribute}
168
+ data[:"#{attribute}"]
169
+ end
170
+
171
+ def #{attribute}=(value)
172
+ data[:"#{attribute}"] = value
173
+ end
174
+ ATTR_METHODS
175
+ end
176
+ end
177
+ end
178
+
179
+ class << self
180
+ def included(base)
181
+ base.send :extend, ClassMethods
182
+ end
183
+
184
+ # use this method before RLP.encode
185
+ # encode item to string or array
186
+ def encode_with_type(item, type, zero: '')
187
+ if type == Integer
188
+ if item == 0
189
+ "\x80".b
190
+ elsif item < 128
191
+ Ciri::Utils.big_endian_encode(item, zero)
192
+ else
193
+ buf = Ciri::Utils.big_endian_encode(item, zero)
194
+ [0x80 + buf.size].pack("c*") + buf
195
+ end
196
+ elsif type == Bool
197
+ item ? Bool::ENCODED_TRUE : Bool::ENCODED_FALSE
198
+ elsif type.is_a?(Class) && type < Serializable
199
+ item.rlp_encode!(raw: false)
200
+ elsif type.is_a?(Array)
201
+ if type.size == 1 # array type
202
+ item.map {|i| encode_with_type(i, type[0])}
203
+ else # unknown
204
+ raise InvalidValueError.new "type size should be 1, got #{type}"
205
+ end
206
+ else
207
+ raise InvalidValueError.new "unknown type #{type}" unless TYPES.key?(type)
208
+ item
209
+ end
210
+ end
211
+
212
+ # Use this method after RLP.decode, decode values from string or array to specific types
213
+ # see Ciri::RLP::Serializable::TYPES for supported types
214
+ #
215
+ # Examples:
216
+ #
217
+ # item = Ciri::RLP.decode(encoded_text)
218
+ # decode_with_type(item, Integer)
219
+ #
220
+ def decode_with_type(item, type)
221
+ if type == Integer
222
+ if item == "\x80".b || item.empty?
223
+ 0
224
+ elsif item[0].ord < 0x80
225
+ Ciri::Utils.big_endian_decode(item)
226
+ else
227
+ size = item[0].ord - 0x80
228
+ Ciri::Utils.big_endian_decode(item[1..size])
229
+ end
230
+ elsif type == Bool
231
+ if item == Bool::ENCODED_TRUE
232
+ true
233
+ elsif item == Bool::ENCODED_FALSE
234
+ false
235
+ else
236
+ raise InvalidValueError.new "invalid bool value #{item}"
237
+ end
238
+ elsif type.is_a?(Class) && type < Serializable
239
+ # already decoded from RLP encoding
240
+ type.rlp_decode!(item, raw: false)
241
+ elsif type.is_a?(Array)
242
+ item.map {|i| decode_with_type(i, type[0])}
243
+ else
244
+ raise InvalidValueError.new "unknown type #{type}" unless TYPES.key?(type)
245
+ item
246
+ end
247
+ end
248
+ end
249
+
250
+ attr_reader :data
251
+
252
+ def initialize(**data)
253
+ @data = (self.class.default_data || {}).merge(data)
254
+ self.class.schema.validate!(@data)
255
+ end
256
+
257
+ # Encode object to rlp encoding string
258
+ def rlp_encode!(raw: true)
259
+ self.class.schema.rlp_encode!(data, raw: raw)
260
+ end
261
+
262
+ def ==(other)
263
+ self.class == other.class && data == other.data
264
+ end
265
+
266
+ end
267
+ end
268
+ end