tapyrus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tapyrusrb-cli +5 -0
- data/exe/tapyrusrbd +41 -0
- data/lib/openassets/marker_output.rb +20 -0
- data/lib/openassets/payload.rb +54 -0
- data/lib/openassets/util.rb +28 -0
- data/lib/openassets.rb +9 -0
- data/lib/tapyrus/base58.rb +38 -0
- data/lib/tapyrus/block.rb +77 -0
- data/lib/tapyrus/block_header.rb +88 -0
- data/lib/tapyrus/bloom_filter.rb +78 -0
- data/lib/tapyrus/chain_params.rb +90 -0
- data/lib/tapyrus/chainparams/mainnet.yml +41 -0
- data/lib/tapyrus/chainparams/regtest.yml +38 -0
- data/lib/tapyrus/chainparams/testnet.yml +41 -0
- data/lib/tapyrus/constants.rb +195 -0
- data/lib/tapyrus/descriptor.rb +147 -0
- data/lib/tapyrus/ext_key.rb +337 -0
- data/lib/tapyrus/key.rb +296 -0
- data/lib/tapyrus/key_path.rb +26 -0
- data/lib/tapyrus/logger.rb +42 -0
- data/lib/tapyrus/merkle_tree.rb +149 -0
- data/lib/tapyrus/message/addr.rb +35 -0
- data/lib/tapyrus/message/base.rb +28 -0
- data/lib/tapyrus/message/block.rb +46 -0
- data/lib/tapyrus/message/block_transaction_request.rb +45 -0
- data/lib/tapyrus/message/block_transactions.rb +31 -0
- data/lib/tapyrus/message/block_txn.rb +27 -0
- data/lib/tapyrus/message/cmpct_block.rb +42 -0
- data/lib/tapyrus/message/error.rb +10 -0
- data/lib/tapyrus/message/fee_filter.rb +27 -0
- data/lib/tapyrus/message/filter_add.rb +28 -0
- data/lib/tapyrus/message/filter_clear.rb +17 -0
- data/lib/tapyrus/message/filter_load.rb +39 -0
- data/lib/tapyrus/message/get_addr.rb +17 -0
- data/lib/tapyrus/message/get_block_txn.rb +27 -0
- data/lib/tapyrus/message/get_blocks.rb +29 -0
- data/lib/tapyrus/message/get_data.rb +21 -0
- data/lib/tapyrus/message/get_headers.rb +28 -0
- data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
- data/lib/tapyrus/message/headers.rb +35 -0
- data/lib/tapyrus/message/headers_parser.rb +24 -0
- data/lib/tapyrus/message/inv.rb +21 -0
- data/lib/tapyrus/message/inventories_parser.rb +23 -0
- data/lib/tapyrus/message/inventory.rb +51 -0
- data/lib/tapyrus/message/mem_pool.rb +17 -0
- data/lib/tapyrus/message/merkle_block.rb +42 -0
- data/lib/tapyrus/message/network_addr.rb +63 -0
- data/lib/tapyrus/message/not_found.rb +21 -0
- data/lib/tapyrus/message/ping.rb +30 -0
- data/lib/tapyrus/message/pong.rb +26 -0
- data/lib/tapyrus/message/prefilled_tx.rb +29 -0
- data/lib/tapyrus/message/reject.rb +46 -0
- data/lib/tapyrus/message/send_cmpct.rb +43 -0
- data/lib/tapyrus/message/send_headers.rb +16 -0
- data/lib/tapyrus/message/tx.rb +30 -0
- data/lib/tapyrus/message/ver_ack.rb +17 -0
- data/lib/tapyrus/message/version.rb +69 -0
- data/lib/tapyrus/message.rb +70 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/tapyrus/mnemonic.rb +77 -0
- data/lib/tapyrus/network/connection.rb +73 -0
- data/lib/tapyrus/network/message_handler.rb +241 -0
- data/lib/tapyrus/network/peer.rb +223 -0
- data/lib/tapyrus/network/peer_discovery.rb +42 -0
- data/lib/tapyrus/network/pool.rb +135 -0
- data/lib/tapyrus/network.rb +13 -0
- data/lib/tapyrus/node/cli.rb +112 -0
- data/lib/tapyrus/node/configuration.rb +38 -0
- data/lib/tapyrus/node/spv.rb +79 -0
- data/lib/tapyrus/node.rb +7 -0
- data/lib/tapyrus/opcodes.rb +178 -0
- data/lib/tapyrus/out_point.rb +44 -0
- data/lib/tapyrus/rpc/http_server.rb +65 -0
- data/lib/tapyrus/rpc/request_handler.rb +150 -0
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
- data/lib/tapyrus/rpc.rb +7 -0
- data/lib/tapyrus/script/multisig.rb +92 -0
- data/lib/tapyrus/script/script.rb +551 -0
- data/lib/tapyrus/script/script_error.rb +111 -0
- data/lib/tapyrus/script/script_interpreter.rb +668 -0
- data/lib/tapyrus/script/tx_checker.rb +81 -0
- data/lib/tapyrus/script_witness.rb +38 -0
- data/lib/tapyrus/secp256k1/native.rb +174 -0
- data/lib/tapyrus/secp256k1/ruby.rb +123 -0
- data/lib/tapyrus/secp256k1.rb +12 -0
- data/lib/tapyrus/slip39/share.rb +122 -0
- data/lib/tapyrus/slip39/sss.rb +245 -0
- data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
- data/lib/tapyrus/slip39.rb +93 -0
- data/lib/tapyrus/store/chain_entry.rb +67 -0
- data/lib/tapyrus/store/db/level_db.rb +98 -0
- data/lib/tapyrus/store/db.rb +9 -0
- data/lib/tapyrus/store/spv_chain.rb +101 -0
- data/lib/tapyrus/store.rb +9 -0
- data/lib/tapyrus/tx.rb +347 -0
- data/lib/tapyrus/tx_in.rb +89 -0
- data/lib/tapyrus/tx_out.rb +74 -0
- data/lib/tapyrus/util.rb +133 -0
- data/lib/tapyrus/validation.rb +115 -0
- data/lib/tapyrus/version.rb +3 -0
- data/lib/tapyrus/wallet/account.rb +151 -0
- data/lib/tapyrus/wallet/base.rb +162 -0
- data/lib/tapyrus/wallet/db.rb +81 -0
- data/lib/tapyrus/wallet/master_key.rb +110 -0
- data/lib/tapyrus/wallet.rb +8 -0
- data/lib/tapyrus.rb +219 -0
- data/tapyrusrb.conf.sample +0 -0
- data/tapyrusrb.gemspec +47 -0
- 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
|