cryptoconditions_ruby 0.5.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 (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +26 -0
  6. data/.travis.yml +5 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +201 -0
  10. data/README.md +22 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/cryptoconditions_ruby.gemspec +34 -0
  15. data/lib/cryptoconditions_ruby/condition.rb +129 -0
  16. data/lib/cryptoconditions_ruby/crypto.rb +168 -0
  17. data/lib/cryptoconditions_ruby/exceptions.rb +8 -0
  18. data/lib/cryptoconditions_ruby/fulfillment.rb +138 -0
  19. data/lib/cryptoconditions_ruby/type_registry.rb +27 -0
  20. data/lib/cryptoconditions_ruby/types/base_sha_256_fulfillment.rb +15 -0
  21. data/lib/cryptoconditions_ruby/types/ed25519_fulfillment.rb +79 -0
  22. data/lib/cryptoconditions_ruby/types/inverted_threshold_sha_256_fulfillment.rb +15 -0
  23. data/lib/cryptoconditions_ruby/types/preimage_sha_256_fulfillment.rb +64 -0
  24. data/lib/cryptoconditions_ruby/types/threshold_sha_256_fulfillment.rb +343 -0
  25. data/lib/cryptoconditions_ruby/types/timeout_fulfillment.rb +46 -0
  26. data/lib/cryptoconditions_ruby/utils/base16.rb +28 -0
  27. data/lib/cryptoconditions_ruby/utils/base58.rb +53 -0
  28. data/lib/cryptoconditions_ruby/utils/byte_array.rb +16 -0
  29. data/lib/cryptoconditions_ruby/utils/bytes.rb +13 -0
  30. data/lib/cryptoconditions_ruby/utils/hasher.rb +27 -0
  31. data/lib/cryptoconditions_ruby/utils/hexlify.rb +13 -0
  32. data/lib/cryptoconditions_ruby/utils/predictor.rb +58 -0
  33. data/lib/cryptoconditions_ruby/utils/reader.rb +167 -0
  34. data/lib/cryptoconditions_ruby/utils/writer.rb +81 -0
  35. data/lib/cryptoconditions_ruby/version.rb +3 -0
  36. data/lib/cryptoconditions_ruby.rb +28 -0
  37. metadata +191 -0
@@ -0,0 +1,168 @@
1
+ require 'base64'
2
+ require 'base32'
3
+ require 'rbnacl'
4
+
5
+ module CryptoconditionsRuby
6
+ module Crypto
7
+ def self.get_encoder(encoding)
8
+ case encoding
9
+ when 'base58' then Base58Encoder
10
+ when 'base64' then Base64Encoder
11
+ when 'base32' then Base32Encoder
12
+ when 'base16' then Base16Encoder
13
+ when 'hex' then HexEncoder
14
+ when 'bytes' then RawEncoder
15
+ else
16
+ raise Exceptions::UnknownEncodingError, 'Unknown or unsupported encoding'
17
+ end
18
+ end
19
+
20
+ module Helpers
21
+ def ed25519_generate_key_pair
22
+ sk = Ed25519SigningKey.generate
23
+ private_value_base58 = sk.encode('base58')
24
+ public_value_compressed_base58 = sk.get_verifying_key.encode('base58')
25
+ [private_value_base58, public_value_compressed_base58]
26
+ end
27
+
28
+ def base64_add_padding(data)
29
+ data = data.encode('utf-8') if data.is_a?(String)
30
+ missing_padding = (4 - data.length) % 4
31
+ data += '=' * missing_padding if missing_padding
32
+ data
33
+ end
34
+
35
+ def base64_remove_padding(data)
36
+ data = data.encode('utf-8') if data.is_a?(String)
37
+ data.sub(/=+\Z/, '')
38
+ end
39
+ end
40
+
41
+ class Base58Encoder
42
+ def encode(data)
43
+ CryptoconditionsRuby::Utils::Base58.encode(data)
44
+ end
45
+
46
+ def decode(data)
47
+ CryptoconditionsRuby::Utils::Base58.decode(data)
48
+ end
49
+ end
50
+
51
+ class Base64Encoder
52
+ def encode(data)
53
+ Base64.encode64(data).strip
54
+ end
55
+
56
+ def decode(data)
57
+ Base64.decode64(data)
58
+ end
59
+ end
60
+
61
+ class Base32Encoder
62
+ def encode(data)
63
+ Base32.encode(data)
64
+ end
65
+
66
+ def decode(data)
67
+ Base32.decode(data)
68
+ end
69
+ end
70
+
71
+ class Base16Encoder
72
+ def encode(data)
73
+ CryptoconditionsRuby::Utils::Base16.encode(data)
74
+ end
75
+
76
+ def decode(data)
77
+ CryptoconditionsRuby::Utils::Base16.decode(data)
78
+ end
79
+ end
80
+
81
+ class HexEncoder
82
+ def encode(data)
83
+ data.to_s.unpack('H*')[0]
84
+ end
85
+
86
+ def decode(data)
87
+ [data].pack('H*')
88
+ end
89
+ end
90
+
91
+ class RawEncoder
92
+ def encode(data)
93
+ data
94
+ end
95
+
96
+ def decode(data)
97
+ data
98
+ end
99
+ end
100
+
101
+ class Ed25519SigningKey < ::RbNaCl::SigningKey
102
+ CryptoKeypair = Struct.new(:private_key, :public_key)
103
+
104
+ attr_accessor :key, :encoder, :encoding
105
+ private :key, :encoder, :encoding
106
+
107
+ def initialize(key = nil, encoding = nil)
108
+ @key = key
109
+ @encoding = encoding || 'base58'
110
+ @encoder = Crypto.get_encoder(@encoding)
111
+ super(@encoder.new.decode(@key))
112
+ end
113
+
114
+ def self.generate
115
+ encoder = Base58Encoder.new
116
+ new(encoder.encode(::RbNaCl::Random.random_bytes(RbNaCl::Signatures::Ed25519::SEEDBYTES)))
117
+ end
118
+
119
+ def get_verifying_key
120
+ Ed25519VerifyingKey.new(encoder.new.encode(verify_key.to_s), encoding)
121
+ end
122
+
123
+ def sign(data, encoding = nil)
124
+ encoder = Crypto.get_encoder(encoding || 'base58')
125
+ encoder.new.encode(super(data))
126
+ end
127
+
128
+ def encode(encoding = 'base58')
129
+ encoder = Crypto.get_encoder(encoding).new
130
+ encoder.encode(self.to_s)
131
+ end
132
+
133
+ private
134
+
135
+ def generate_signing_key
136
+ if key
137
+ ::RbNaCl::SigningKey.new(key)
138
+ else
139
+ ::RbNaCl::SigningKey.generate
140
+ end
141
+ end
142
+ end
143
+
144
+ class Ed25519VerifyingKey < ::RbNaCl::VerifyKey
145
+ attr_accessor :key, :encoder
146
+ private :key, :encoder
147
+
148
+ def initialize(key = nil, encoding = nil)
149
+ @key = key
150
+ encoding = encoding || 'base58'
151
+ @encoder = Crypto.get_encoder(encoding)
152
+ super(@encoder.new.decode(@key))
153
+ end
154
+
155
+ def verify(signature, data, encoding = 'base58')
156
+ encoder = Crypto.get_encoder(encoding)
157
+ super(encoder.new.decode(signature), data)
158
+ rescue ::RbNaCl::BadSignatureError
159
+ false
160
+ end
161
+
162
+ def encode(encoding = 'base58')
163
+ encoder = Crypto.get_encoder(encoding).new
164
+ encoder.encode(self.to_s)
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,8 @@
1
+ module CryptoconditionsRuby
2
+ module Exceptions
3
+ class ParsingError < StandardError; end
4
+ class UnsupportedTypeError < StandardError; end
5
+ class ValidationError < StandardError; end
6
+ class UnknownEncodingError < StandardError; end
7
+ end
8
+ end
@@ -0,0 +1,138 @@
1
+ require 'base64'
2
+
3
+ module CryptoconditionsRuby
4
+ FULFILLMENT_REGEX = /^cf:([1-9a-f][0-9a-f]{0,3}|0):[a-zA-Z0-9_-]*$/
5
+
6
+ class Fulfillment
7
+ extend Crypto::Helpers
8
+ include Crypto::Helpers
9
+ TYPE_ID = nil
10
+ REGEX = FULFILLMENT_REGEX
11
+ FEATURE_BITMASK = nil
12
+
13
+ def self.from_uri(serialized_fulfillment)
14
+ return serialized_fulfillment if serialized_fulfillment.is_a?(Fulfillment)
15
+
16
+ unless serialized_fulfillment.is_a?(String)
17
+ raise TypeError, 'Serialized fulfillment must be a string'
18
+ end
19
+
20
+ pieces = serialized_fulfillment.split(':', -1)
21
+
22
+ unless pieces.first == 'cf'
23
+ raise TypeError, 'Serialized fulfillment must start with "cf:"'
24
+ end
25
+
26
+ unless serialized_fulfillment.match(Fulfillment::REGEX)
27
+ raise TypeError, 'Invalid fulfillment format'
28
+ end
29
+
30
+ type_id = pieces[1].to_i(16)
31
+ payload = Base64.urlsafe_decode64(base64_add_padding(pieces[2]))
32
+
33
+ cls = TypeRegistry.get_class_from_type_id(type_id)
34
+ fulfillment = cls.new
35
+
36
+ fulfillment.parse_payload(Utils::Reader.from_source(payload), payload.length)
37
+ fulfillment
38
+ end
39
+
40
+ def self.from_binary(reader)
41
+ reader = Utils::Reader.from_source(reader)
42
+
43
+ cls_type = reader.read_uint16
44
+ cls = TypeRegistry.get_class_from_type_id(cls_type)
45
+
46
+ fulfillment = cls.new
47
+ payload_length = reader.read_length_prefix
48
+ fulfillment.parse_payload(reader, payload_length)
49
+ fulfillment
50
+ end
51
+
52
+ def self.from_dict(data)
53
+ cls_type = data['type_id']
54
+ cls = TypeRegistry.get_class_from_type_id(cls_type)
55
+ fulfillment = cls.new
56
+ fulfillment.parse_dict(data)
57
+ fulfillment
58
+ end
59
+
60
+ def type_id
61
+ self.class::TYPE_ID
62
+ end
63
+
64
+ def bitmask
65
+ self.class::FEATURE_BITMASK
66
+ end
67
+
68
+ def condition
69
+ condition = Condition.new
70
+ condition.type_id = type_id
71
+ condition.bitmask = bitmask
72
+ condition.hash = generate_hash
73
+ condition.max_fulfillment_length = calculate_max_fulfillment_length
74
+ condition
75
+ end
76
+
77
+ def condition_uri
78
+ condition.serialize_uri
79
+ end
80
+
81
+ def condition_binary
82
+ condition.serialize_binary
83
+ end
84
+
85
+ def generate_hash
86
+ raise 'Implement me'
87
+ end
88
+
89
+ def calculate_max_fulfillment_length
90
+ predictor = Utils::Predictor.new
91
+ write_payload(predictor)
92
+ predictor.size
93
+ end
94
+
95
+ def serialize_uri
96
+ format(
97
+ 'cf:%x:%s',
98
+ type_id,
99
+ base64_remove_padding(
100
+ Base64.urlsafe_encode64(serialize_payload)
101
+ )
102
+ )
103
+ end
104
+
105
+ def serialize_binary
106
+ writer = Utils::Writer.new
107
+ writer.write_uint16(type_id)
108
+ writer.write_var_octet_string(serialize_payload)
109
+ writer.buffer
110
+ end
111
+
112
+ def serialize_payload
113
+ writer = Utils::Writer.new
114
+ write_payload(writer)
115
+ writer.buffer
116
+ end
117
+
118
+ def write_payload(_writer)
119
+ raise 'Implement me'
120
+ end
121
+
122
+ def parse_payload(_reader, *_args)
123
+ raise 'Implement me'
124
+ end
125
+
126
+ def to_dict
127
+ raise 'Implement me'
128
+ end
129
+
130
+ def parse_dict(_data)
131
+ raise 'Implement me'
132
+ end
133
+
134
+ def validate(**_kwargs)
135
+ raise 'Implement me'
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,27 @@
1
+ class CryptoconditionsRuby::TypeRegistry
2
+ MAX_SAFE_INTEGER_JS = 2 ** 53 - 1
3
+
4
+ def self.registered_types
5
+ @registered_types ||=[]
6
+ end
7
+
8
+ def self.get_class_from_type_id(type_id)
9
+ if type_id > MAX_SAFE_INTEGER_JS
10
+ raise TypeError.new("Type #{type_id} is not supported")
11
+ end
12
+
13
+ type = registered_types.find do |registered_type|
14
+ type_id == registered_type['type_id']
15
+ end
16
+
17
+ if type
18
+ type['class']
19
+ else
20
+ raise TypeError.new("Type #{type_id} is not supported")
21
+ end
22
+ end
23
+
24
+ def self.register_type(cls)
25
+ registered_types.push({'type_id' => cls::TYPE_ID, 'class' => cls})
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ module CryptoconditionsRuby
2
+ module Types
3
+ class BaseSha256Fulfillment < Fulfillment
4
+ def generate_hash
5
+ hasher = Utils::Hasher.new('sha256')
6
+ write_hash_payload(hasher)
7
+ hasher.digest
8
+ end
9
+
10
+ def write_hash_payload(_hasher)
11
+ raise 'Implement me'
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,79 @@
1
+ module CryptoconditionsRuby
2
+ module Types
3
+ class Ed25519Fulfillment < Fulfillment
4
+ TYPE_ID = 4
5
+ FEATURE_BITMASK = 0x20
6
+ PUBKEY_LENGTH = 32
7
+ SIGNATURE_LENGTH = 64
8
+ FULFILLMENT_LENGTH = PUBKEY_LENGTH + SIGNATURE_LENGTH
9
+
10
+ attr_accessor :public_key, :signature
11
+ private :signature
12
+ def initialize(public_key = nil)
13
+ if public_key
14
+ public_key = Crypto::Ed25519VerifyingKey.new(public_key) if public_key.is_a?(String)
15
+ raise TypeError unless public_key.is_a?(Crypto::Ed25519VerifyingKey)
16
+ end
17
+ @public_key = public_key
18
+ @signature = nil
19
+ end
20
+
21
+ def write_common_header(writer)
22
+ writer.write_var_octet_string(public_key)
23
+ end
24
+
25
+ def sign(message, private_key)
26
+ sk = private_key
27
+ vk = sk.get_verifying_key
28
+
29
+ self.public_key = vk
30
+
31
+ self.signature = sk.sign(message, 'bytes')
32
+ end
33
+
34
+ def generate_hash
35
+ raise StandardError, 'Requires a public publicKey' unless public_key
36
+ public_key.to_s
37
+ end
38
+
39
+ def parse_payload(reader, *_args)
40
+ self.public_key = Crypto::Ed25519VerifyingKey.new(
41
+ Utils::Base58.encode(reader.read_octet_string(Ed25519Fulfillment::PUBKEY_LENGTH))
42
+ )
43
+ self.signature = reader.read_octet_string(Ed25519Fulfillment::SIGNATURE_LENGTH)
44
+ end
45
+
46
+ def write_payload(writer)
47
+ writer.tap do |w|
48
+ w.write_octet_string(public_key.to_s, Ed25519Fulfillment::PUBKEY_LENGTH)
49
+ w.write_octet_string(signature, Ed25519Fulfillment::SIGNATURE_LENGTH)
50
+ end
51
+ end
52
+
53
+ def calculate_max_fulfillment_length
54
+ Ed25519Fulfillment::FULFILLMENT_LENGTH
55
+ end
56
+
57
+ def to_dict
58
+ {
59
+ 'type' => 'fulfillment',
60
+ 'type_id' => TYPE_ID,
61
+ 'bitmask' => bitmask,
62
+ 'public_key' => Utils::Base58.encode(public_key.to_s),
63
+ 'signature' => (Utils::Base58.encode(signature) if signature)
64
+ }
65
+ end
66
+
67
+ def parse_dict(data)
68
+ self.public_key = Crypto::Ed25519VerifyingKey.new(data['public_key'])
69
+ self.signature = (Utils::Base58.decode(data['signature']) if data['signature'])
70
+ end
71
+
72
+ def validate(message: nil, **_kwargs)
73
+ return false unless message && signature
74
+
75
+ public_key.verify(signature, message, 'bytes')
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,15 @@
1
+ module CryptoconditionsRuby
2
+ CONDITION = 'condition'.freeze
3
+ FULFILLMENT = 'fulfillment'.freeze
4
+
5
+ module Types
6
+ class InvertedThresholdSha256Fulfillment < ThresholdSha256Fulfillment
7
+ TYPE_ID = 98
8
+ FEATURE_BITMASK = 0x09
9
+
10
+ def validate(message: nil, **kwargs)
11
+ !super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,64 @@
1
+ module CryptoconditionsRuby
2
+ module Types
3
+ class PreimageSha256Fulfillment < BaseSha256Fulfillment
4
+ TYPE_ID = 0
5
+ FEATURE_BITMASK = 0x03
6
+
7
+ attr_accessor :preimage
8
+ private :preimage
9
+ def initialize(preimage = nil)
10
+ if preimage && !preimage.respond_to?(:bytes)
11
+ raise TypeError, "Preimage must be bytes, was #{preimage.class.name}"
12
+ end
13
+ @preimage = preimage
14
+ end
15
+
16
+ def bitmask
17
+ FEATURE_BITMASK
18
+ end
19
+
20
+ def write_hash_payload(hasher)
21
+ unless hasher.is_a?(Utils::Hasher)
22
+ raise TypeError, 'hasher must be a Hasher instance'
23
+ end
24
+ unless preimage
25
+ raise TypeError, 'Could not calculate hash, no preimage provided'
26
+ end
27
+ hasher.write(preimage)
28
+ end
29
+
30
+ def parse_payload(reader, payload_size)
31
+ unless reader.is_a?(Utils::Reader)
32
+ raise TypeError, 'reader must be a Reader instance'
33
+ end
34
+ self.preimage = reader.read(payload_size)
35
+ end
36
+
37
+ def write_payload(writer)
38
+ unless [Utils::Writer, Utils::Predictor].include?(writer.class)
39
+ raise TypeError, 'writer must be a Writer instance'
40
+ end
41
+ raise TypeError, 'Preimage must be specified' unless preimage
42
+ writer.write(preimage)
43
+ writer
44
+ end
45
+
46
+ def to_dict
47
+ {
48
+ 'type' => 'fulfillment',
49
+ 'type_id' => TYPE_ID,
50
+ 'bitmask' => bitmask,
51
+ 'preimage' => preimage
52
+ }
53
+ end
54
+
55
+ def parse_dict(data)
56
+ self.preimage = data['preimage'].encode
57
+ end
58
+
59
+ def validate(**_kwargs)
60
+ true
61
+ end
62
+ end
63
+ end
64
+ end