ciri 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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