tapyrus 0.1.0

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