tapyrus 0.1.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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +100 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/tapyrusrb-cli +5 -0
  15. data/exe/tapyrusrbd +41 -0
  16. data/lib/openassets/marker_output.rb +20 -0
  17. data/lib/openassets/payload.rb +54 -0
  18. data/lib/openassets/util.rb +28 -0
  19. data/lib/openassets.rb +9 -0
  20. data/lib/tapyrus/base58.rb +38 -0
  21. data/lib/tapyrus/block.rb +77 -0
  22. data/lib/tapyrus/block_header.rb +88 -0
  23. data/lib/tapyrus/bloom_filter.rb +78 -0
  24. data/lib/tapyrus/chain_params.rb +90 -0
  25. data/lib/tapyrus/chainparams/mainnet.yml +41 -0
  26. data/lib/tapyrus/chainparams/regtest.yml +38 -0
  27. data/lib/tapyrus/chainparams/testnet.yml +41 -0
  28. data/lib/tapyrus/constants.rb +195 -0
  29. data/lib/tapyrus/descriptor.rb +147 -0
  30. data/lib/tapyrus/ext_key.rb +337 -0
  31. data/lib/tapyrus/key.rb +296 -0
  32. data/lib/tapyrus/key_path.rb +26 -0
  33. data/lib/tapyrus/logger.rb +42 -0
  34. data/lib/tapyrus/merkle_tree.rb +149 -0
  35. data/lib/tapyrus/message/addr.rb +35 -0
  36. data/lib/tapyrus/message/base.rb +28 -0
  37. data/lib/tapyrus/message/block.rb +46 -0
  38. data/lib/tapyrus/message/block_transaction_request.rb +45 -0
  39. data/lib/tapyrus/message/block_transactions.rb +31 -0
  40. data/lib/tapyrus/message/block_txn.rb +27 -0
  41. data/lib/tapyrus/message/cmpct_block.rb +42 -0
  42. data/lib/tapyrus/message/error.rb +10 -0
  43. data/lib/tapyrus/message/fee_filter.rb +27 -0
  44. data/lib/tapyrus/message/filter_add.rb +28 -0
  45. data/lib/tapyrus/message/filter_clear.rb +17 -0
  46. data/lib/tapyrus/message/filter_load.rb +39 -0
  47. data/lib/tapyrus/message/get_addr.rb +17 -0
  48. data/lib/tapyrus/message/get_block_txn.rb +27 -0
  49. data/lib/tapyrus/message/get_blocks.rb +29 -0
  50. data/lib/tapyrus/message/get_data.rb +21 -0
  51. data/lib/tapyrus/message/get_headers.rb +28 -0
  52. data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
  53. data/lib/tapyrus/message/headers.rb +35 -0
  54. data/lib/tapyrus/message/headers_parser.rb +24 -0
  55. data/lib/tapyrus/message/inv.rb +21 -0
  56. data/lib/tapyrus/message/inventories_parser.rb +23 -0
  57. data/lib/tapyrus/message/inventory.rb +51 -0
  58. data/lib/tapyrus/message/mem_pool.rb +17 -0
  59. data/lib/tapyrus/message/merkle_block.rb +42 -0
  60. data/lib/tapyrus/message/network_addr.rb +63 -0
  61. data/lib/tapyrus/message/not_found.rb +21 -0
  62. data/lib/tapyrus/message/ping.rb +30 -0
  63. data/lib/tapyrus/message/pong.rb +26 -0
  64. data/lib/tapyrus/message/prefilled_tx.rb +29 -0
  65. data/lib/tapyrus/message/reject.rb +46 -0
  66. data/lib/tapyrus/message/send_cmpct.rb +43 -0
  67. data/lib/tapyrus/message/send_headers.rb +16 -0
  68. data/lib/tapyrus/message/tx.rb +30 -0
  69. data/lib/tapyrus/message/ver_ack.rb +17 -0
  70. data/lib/tapyrus/message/version.rb +69 -0
  71. data/lib/tapyrus/message.rb +70 -0
  72. data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  73. data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  74. data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
  75. data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
  76. data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
  77. data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
  78. data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
  79. data/lib/tapyrus/mnemonic.rb +77 -0
  80. data/lib/tapyrus/network/connection.rb +73 -0
  81. data/lib/tapyrus/network/message_handler.rb +241 -0
  82. data/lib/tapyrus/network/peer.rb +223 -0
  83. data/lib/tapyrus/network/peer_discovery.rb +42 -0
  84. data/lib/tapyrus/network/pool.rb +135 -0
  85. data/lib/tapyrus/network.rb +13 -0
  86. data/lib/tapyrus/node/cli.rb +112 -0
  87. data/lib/tapyrus/node/configuration.rb +38 -0
  88. data/lib/tapyrus/node/spv.rb +79 -0
  89. data/lib/tapyrus/node.rb +7 -0
  90. data/lib/tapyrus/opcodes.rb +178 -0
  91. data/lib/tapyrus/out_point.rb +44 -0
  92. data/lib/tapyrus/rpc/http_server.rb +65 -0
  93. data/lib/tapyrus/rpc/request_handler.rb +150 -0
  94. data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
  95. data/lib/tapyrus/rpc.rb +7 -0
  96. data/lib/tapyrus/script/multisig.rb +92 -0
  97. data/lib/tapyrus/script/script.rb +551 -0
  98. data/lib/tapyrus/script/script_error.rb +111 -0
  99. data/lib/tapyrus/script/script_interpreter.rb +668 -0
  100. data/lib/tapyrus/script/tx_checker.rb +81 -0
  101. data/lib/tapyrus/script_witness.rb +38 -0
  102. data/lib/tapyrus/secp256k1/native.rb +174 -0
  103. data/lib/tapyrus/secp256k1/ruby.rb +123 -0
  104. data/lib/tapyrus/secp256k1.rb +12 -0
  105. data/lib/tapyrus/slip39/share.rb +122 -0
  106. data/lib/tapyrus/slip39/sss.rb +245 -0
  107. data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
  108. data/lib/tapyrus/slip39.rb +93 -0
  109. data/lib/tapyrus/store/chain_entry.rb +67 -0
  110. data/lib/tapyrus/store/db/level_db.rb +98 -0
  111. data/lib/tapyrus/store/db.rb +9 -0
  112. data/lib/tapyrus/store/spv_chain.rb +101 -0
  113. data/lib/tapyrus/store.rb +9 -0
  114. data/lib/tapyrus/tx.rb +347 -0
  115. data/lib/tapyrus/tx_in.rb +89 -0
  116. data/lib/tapyrus/tx_out.rb +74 -0
  117. data/lib/tapyrus/util.rb +133 -0
  118. data/lib/tapyrus/validation.rb +115 -0
  119. data/lib/tapyrus/version.rb +3 -0
  120. data/lib/tapyrus/wallet/account.rb +151 -0
  121. data/lib/tapyrus/wallet/base.rb +162 -0
  122. data/lib/tapyrus/wallet/db.rb +81 -0
  123. data/lib/tapyrus/wallet/master_key.rb +110 -0
  124. data/lib/tapyrus/wallet.rb +8 -0
  125. data/lib/tapyrus.rb +219 -0
  126. data/tapyrusrb.conf.sample +0 -0
  127. data/tapyrusrb.gemspec +47 -0
  128. metadata +451 -0
@@ -0,0 +1,245 @@
1
+ require 'securerandom'
2
+
3
+ module Tapyrus
4
+ module SLIP39
5
+
6
+ # Shamir's Secret Sharing
7
+ class SSS
8
+
9
+ include Tapyrus::Util
10
+ extend Tapyrus::Util
11
+
12
+ # Create SSS shares.
13
+ #
14
+ # [Usage]
15
+ # 4 groups shares.
16
+ # = two for Alice
17
+ # = one for friends(required 3 of her 5 friends) and
18
+ # = one for family members(required 2 of her 6 family)
19
+ #
20
+ # Two of these group shares are required to reconstruct the master secret.
21
+ # groups = [1, 1], [1, 1], [3, 5], [2, 6]
22
+ #
23
+ # group_shares = Tapyrus::SLIP39::SSS.setup_shares(group_threshold: 2, groups: groups, secret: 'secret with hex format', passphrase: 'xxx')
24
+ # return 4 group array of Tapyrus::SLIP39::Share
25
+ #
26
+ # Get each share word
27
+ # groups[0][1].to_words
28
+ # => ["shadow", "pistol", "academic", "always", "adequate", "wildlife", "fancy", "gross", "oasis", "cylinder", "mustang", "wrist", "rescue", "view", "short", "owner", "flip", "making", "coding", "armed"]
29
+ #
30
+ # @param [Array[Array[Integer, Integer]]] groups
31
+ # @param [Integer] group_threshold threshold number of group shares required to reconstruct the master secret.
32
+ # @param [Integer] exp Iteration exponent. default is 0.
33
+ # @param [String] secret master secret with hex format.
34
+ # @param [String] passphrase the passphrase used for encryption/decryption.
35
+ # @return [Array[Array[Tapyrus::SLIP39::Share]]] array of group shares.
36
+ def self.setup_shares(groups: [], group_threshold: nil, exp: 0, secret: nil, passphrase: '')
37
+ raise ArgumentError, 'Groups is empty.' if groups.empty?
38
+ raise ArgumentError, 'Group threshold must be greater than 0.' if group_threshold.nil? || group_threshold < 1
39
+ raise ArgumentError, 'Master secret does not specified.' unless secret
40
+ raise ArgumentError, "The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes." if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
41
+ raise ArgumentError, 'The length of the master secret in bytes must be an even number.' unless secret.bytesize.even?
42
+ raise ArgumentError, 'The passphrase must contain only printable ASCII characters (code points 32-126).' unless passphrase.ascii_only?
43
+ raise ArgumentError, "The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})." if group_threshold > groups.length
44
+ groups.each do |threshold, count|
45
+ raise ArgumentError, 'Group threshold must be greater than 0.' if threshold.nil? || threshold < 1
46
+ raise ArgumentError, "The requested member threshold (#{threshold}) must not exceed the number of share (#{count})." if threshold > count
47
+ raise ArgumentError, "Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead." if threshold == 1 && count > 1
48
+ end
49
+
50
+ id = SecureRandom.random_number(32767) # 32767 is max number for 15 bits.
51
+ ems = encrypt(secret, passphrase, exp, id)
52
+
53
+ group_shares = split_secret(group_threshold, groups.length, ems)
54
+
55
+ shares = group_shares.map.with_index do |s, i|
56
+ group_index, group_share = s[0], s[1]
57
+ member_threshold, member_count = groups[i][0], groups[i][1]
58
+ shares = split_secret(member_threshold, member_count, group_share)
59
+ shares.map do |member_index, member_share|
60
+ share = Tapyrus::SLIP39::Share.new
61
+ share.id = id
62
+ share.iteration_exp = exp
63
+ share.group_index = group_index
64
+ share.group_threshold = group_threshold
65
+ share.group_count = groups.length
66
+ share.member_index = member_index
67
+ share.member_threshold = member_threshold
68
+ share.value = member_share
69
+ share.checksum = share.calculate_checksum
70
+ share
71
+ end
72
+ end
73
+ shares
74
+ end
75
+
76
+ # recovery master secret form shares.
77
+ #
78
+ # [Usage]
79
+ # shares: An array of shares required for recovery.
80
+ # master_secret = Tapyrus::SLIP39::SSS.recover_secret(shares, passphrase: 'xxx')
81
+ #
82
+ # @param [Array[Tapyrus::SLIP30::Share]] shares an array of shares.
83
+ # @param [String] passphrase the passphrase using decrypt master secret.
84
+ # @return [String] a master secret.
85
+ def self.recover_secret(shares, passphrase: '')
86
+ raise ArgumentError, 'share is empty.' if shares.nil? || shares.empty?
87
+ groups = {}
88
+ id = shares[0].id
89
+ exp = shares[0].iteration_exp
90
+ group_threshold = shares.first.group_threshold
91
+ group_count = shares.first.group_count
92
+
93
+ shares.each do |share|
94
+ raise ArgumentError, 'Invalid set of shares. All shares must have the same id.' unless id == share.id
95
+ raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.' unless group_threshold == share.group_threshold
96
+ raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.' unless group_count == share.group_count
97
+ raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.' unless exp == share.iteration_exp
98
+ groups[share.group_index] ||= []
99
+ groups[share.group_index] << share
100
+ end
101
+
102
+ group_shares = {}
103
+ groups.each do |group_index, shares|
104
+ member_threshold = shares.first.member_threshold
105
+ raise ArgumentError, "Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}" if shares.length < member_threshold
106
+ if shares.length == 1 && member_threshold == 1
107
+ group_shares[group_index] = shares.first.value
108
+ else
109
+ value_length = shares.first.value.length
110
+ x_coordinates = []
111
+ shares.each do |share|
112
+ raise ArgumentError, 'Invalid set of shares. All shares in a group must have the same member threshold.' unless member_threshold == share.member_threshold
113
+ raise ArgumentError, 'Invalid set of shares. All share values must have the same length.' unless value_length == share.value.length
114
+ x_coordinates << share.member_index
115
+ end
116
+ x_coordinates.uniq!
117
+ raise ArgumentError, 'Invalid set of shares. Share indices must be unique.' unless x_coordinates.size == shares.size
118
+ interpolate_shares = shares.map{|s|[s.member_index, s.value]}
119
+
120
+ secret = interpolate(interpolate_shares, SECRET_INDEX)
121
+ digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
122
+ digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
123
+ recover_digest = create_digest(secret, random_value)
124
+ raise ArgumentError, 'Invalid digest of the shared secret.' unless digest == recover_digest
125
+
126
+ group_shares[group_index] = secret
127
+ end
128
+ end
129
+
130
+ return decrypt(group_shares.values.first, passphrase, exp, id) if group_threshold == 1
131
+
132
+ raise ArgumentError, "Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}" if group_shares.length < group_threshold
133
+
134
+ interpolate_shares = group_shares.map{|k, v|[k, v]}
135
+ secret = interpolate(interpolate_shares, SECRET_INDEX)
136
+ digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
137
+ digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
138
+ recover_digest = create_digest(secret, random_value)
139
+ raise ArgumentError, 'Invalid digest of the shared secret.' unless digest == recover_digest
140
+
141
+ decrypt(secret, passphrase, exp, id)
142
+ end
143
+
144
+ private
145
+
146
+ # Calculate f(x) from given shamir shares.
147
+ # @param [Array[index, value]] shares the array of shamir shares.
148
+ # @param [Integer] x the x coordinate of the result.
149
+ # @return [String] f(x) value with hex format.
150
+ def self.interpolate(shares, x)
151
+ s = shares.find{|s|s[0] == x}
152
+ return s[1] if s
153
+
154
+ log_prod = shares.sum{|s|LOG_TABLE[s[0] ^ x]}
155
+
156
+ result = ('00' * shares.first[1].length).htb
157
+ shares.each do |share|
158
+ log_basis_eval = (log_prod - LOG_TABLE[share[0] ^ x] - shares.sum{|s|LOG_TABLE[share[0] ^ s[0]]}) % 255
159
+ result = share[1].htb.bytes.each.map.with_index do |v, i|
160
+ (result[i].bti ^ (v == 0 ? 0 : (EXP_TABLE[(LOG_TABLE[v] + log_basis_eval) % 255]))).itb
161
+ end.join
162
+ end
163
+ result.bth
164
+ end
165
+
166
+ # Decrypt encrypted master secret using passphrase.
167
+ # @param [String] ems an encrypted master secret with hex format.
168
+ # @param [String] passphrase the passphrase when using encrypt master secret with binary format.
169
+ # @param [Integer] exp iteration exponent
170
+ # @param [Integer] id identifier
171
+ def self.decrypt(ems, passphrase, exp, id)
172
+ l, r = ems[0...(ems.length / 2)].htb, ems[(ems.length / 2)..-1].htb
173
+ salt = get_salt(id)
174
+ e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
175
+ Tapyrus::SLIP39::ROUND_COUNT.times.to_a.reverse.each do |i|
176
+ f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
177
+ l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
178
+ end
179
+ (r + l).bth
180
+ end
181
+
182
+ # Encrypt master secret using passphrase
183
+ # @param [String] secret master secret with hex format.
184
+ # @param [String] passphrase the passphrase when using encrypt master secret with binary format.
185
+ # @param [Integer] exp iteration exponent
186
+ # @param [Integer] id identifier
187
+ # @return [String] encrypted master secret with hex format.
188
+ def self.encrypt(secret, passphrase, exp, id)
189
+ s = secret.htb
190
+ l, r = s[0...(s.bytesize / 2)], s[(s.bytesize / 2)..-1]
191
+ salt = get_salt(id)
192
+ e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
193
+ Tapyrus::SLIP39::ROUND_COUNT.times.to_a.each do |i|
194
+ f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
195
+ l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
196
+ end
197
+ (r + l).bth
198
+ end
199
+
200
+ # Create digest of the shared secret.
201
+ # @param [String] secret the shared secret with hex format.
202
+ # @param [String] random value (n-4 bytes) with hex format.
203
+ # @return [String] digest value(4 bytes) with hex format.
204
+ def self.create_digest(secret, random)
205
+ h = Tapyrus.hmac_sha256(random.htb, secret.htb)
206
+ h[0...4].bth
207
+ end
208
+
209
+ # get salt using encryption/decryption form id.
210
+ # @param [Integer] id id
211
+ # @return [String] salt with binary format.
212
+ def self.get_salt(id)
213
+ (Tapyrus::SLIP39::CUSTOMIZATION_STRING.pack('c*') + id.itb)
214
+ end
215
+
216
+ # Split the share into +count+ with threshold +threshold+.
217
+ # @param [Integer] threshold the threshold.
218
+ # @param [Integer] count split count.
219
+ # @param [Integer] secret the secret to be split.
220
+ # @return [Array[Integer, String]] the array of split secret.
221
+ def self.split_secret(threshold, count, secret)
222
+ raise ArgumentError, "The requested threshold (#{threshold}) must be a positive integer." if threshold < 1
223
+ raise ArgumentError, "The requested threshold (#{threshold}) must not exceed the number of shares (#{count})." if threshold > count
224
+ raise ArgumentError, "The requested number of shares (#{count}) must not exceed #{MAX_SHARE_COUNT}." if count > MAX_SHARE_COUNT
225
+
226
+ return count.times.map{|i|[i, secret]} if threshold == 1 # if the threshold is 1, digest of the share is not used.
227
+
228
+ random_share_count = threshold - 2
229
+
230
+ shares = random_share_count.times.map{|i|[i, SecureRandom.hex(secret.htb.bytesize)]}
231
+ random_part = SecureRandom.hex(secret.htb.bytesize - DIGEST_LENGTH_BYTES)
232
+ digest = create_digest(secret, random_part)
233
+
234
+ base_shares = shares + [[DIGEST_INDEX, digest + random_part], [SECRET_INDEX, secret]]
235
+
236
+ (random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)]}
237
+
238
+ shares
239
+ end
240
+
241
+ private_class_method :split_secret, :get_salt, :interpolate, :encrypt, :decrypt, :create_digest
242
+
243
+ end
244
+ end
245
+ end