tapyrus 0.2.6 → 0.2.10
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.prettierignore +3 -0
- data/.prettierrc.yaml +3 -0
- data/.ruby-version +1 -1
- data/CODE_OF_CONDUCT.md +7 -7
- data/README.md +14 -17
- data/Rakefile +3 -3
- data/lib/openassets/marker_output.rb +0 -4
- data/lib/openassets/payload.rb +4 -10
- data/lib/openassets.rb +0 -2
- data/lib/schnorr/sign_to_contract.rb +51 -0
- data/lib/schnorr/signature.rb +3 -6
- data/lib/schnorr.rb +14 -9
- data/lib/tapyrus/base58.rb +7 -6
- data/lib/tapyrus/bip175.rb +67 -0
- data/lib/tapyrus/block.rb +1 -2
- data/lib/tapyrus/block_header.rb +15 -9
- data/lib/tapyrus/bloom_filter.rb +5 -3
- data/lib/tapyrus/chain_params.rb +1 -4
- data/lib/tapyrus/chainparams/dev.yml +3 -2
- data/lib/tapyrus/chainparams/prod.yml +3 -2
- data/lib/tapyrus/constants.rb +29 -23
- data/lib/tapyrus/errors.rb +1 -3
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -4
- data/lib/tapyrus/ext.rb +1 -1
- data/lib/tapyrus/ext_key.rb +44 -32
- data/lib/tapyrus/key.rb +31 -35
- data/lib/tapyrus/key_path.rb +15 -12
- data/lib/tapyrus/logger.rb +20 -16
- data/lib/tapyrus/merkle_tree.rb +22 -20
- data/lib/tapyrus/message/addr.rb +1 -7
- data/lib/tapyrus/message/base.rb +0 -3
- data/lib/tapyrus/message/block.rb +2 -9
- data/lib/tapyrus/message/block_transaction_request.rb +3 -6
- data/lib/tapyrus/message/block_transactions.rb +2 -6
- data/lib/tapyrus/message/block_txn.rb +0 -4
- data/lib/tapyrus/message/cmpct_block.rb +1 -7
- data/lib/tapyrus/message/error.rb +1 -4
- data/lib/tapyrus/message/fee_filter.rb +1 -4
- data/lib/tapyrus/message/filter_add.rb +0 -4
- data/lib/tapyrus/message/filter_clear.rb +0 -4
- data/lib/tapyrus/message/filter_load.rb +2 -5
- data/lib/tapyrus/message/get_addr.rb +0 -4
- data/lib/tapyrus/message/get_block_txn.rb +0 -4
- data/lib/tapyrus/message/get_blocks.rb +0 -3
- data/lib/tapyrus/message/get_data.rb +1 -4
- data/lib/tapyrus/message/get_headers.rb +1 -3
- data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
- data/lib/tapyrus/message/headers.rb +0 -4
- data/lib/tapyrus/message/headers_parser.rb +3 -8
- data/lib/tapyrus/message/inv.rb +1 -4
- data/lib/tapyrus/message/inventories_parser.rb +2 -7
- data/lib/tapyrus/message/inventory.rb +12 -5
- data/lib/tapyrus/message/mem_pool.rb +0 -4
- data/lib/tapyrus/message/merkle_block.rb +4 -9
- data/lib/tapyrus/message/network_addr.rb +7 -6
- data/lib/tapyrus/message/not_found.rb +0 -3
- data/lib/tapyrus/message/ping.rb +0 -3
- data/lib/tapyrus/message/pong.rb +0 -3
- data/lib/tapyrus/message/prefilled_tx.rb +0 -4
- data/lib/tapyrus/message/reject.rb +0 -3
- data/lib/tapyrus/message/send_cmpct.rb +1 -3
- data/lib/tapyrus/message/send_headers.rb +0 -3
- data/lib/tapyrus/message/tx.rb +0 -4
- data/lib/tapyrus/message/ver_ack.rb +1 -5
- data/lib/tapyrus/message/version.rb +2 -5
- data/lib/tapyrus/message.rb +14 -16
- data/lib/tapyrus/mnemonic.rb +17 -15
- data/lib/tapyrus/network/connection.rb +0 -3
- data/lib/tapyrus/network/message_handler.rb +61 -60
- data/lib/tapyrus/network/peer.rb +13 -12
- data/lib/tapyrus/network/peer_discovery.rb +10 -9
- data/lib/tapyrus/network/pool.rb +12 -12
- data/lib/tapyrus/network.rb +0 -2
- data/lib/tapyrus/node/cli.rb +12 -14
- data/lib/tapyrus/node/configuration.rb +1 -3
- data/lib/tapyrus/node/spv.rb +2 -3
- data/lib/tapyrus/node.rb +1 -1
- data/lib/tapyrus/opcodes.rb +9 -7
- data/lib/tapyrus/out_point.rb +5 -5
- data/lib/tapyrus/rpc/http_server.rb +21 -22
- data/lib/tapyrus/rpc/request_handler.rb +42 -44
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
- data/lib/tapyrus/rpc.rb +1 -0
- data/lib/tapyrus/script/color.rb +10 -0
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +99 -88
- data/lib/tapyrus/script/script_error.rb +1 -4
- data/lib/tapyrus/script/script_interpreter.rb +439 -399
- data/lib/tapyrus/script/tx_checker.rb +20 -10
- data/lib/tapyrus/secp256k1/native.rb +14 -15
- data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
- data/lib/tapyrus/secp256k1/ruby.rb +10 -12
- data/lib/tapyrus/secp256k1.rb +0 -4
- data/lib/tapyrus/slip39/share.rb +41 -29
- data/lib/tapyrus/slip39/sss.rb +107 -57
- data/lib/tapyrus/slip39.rb +20 -5
- data/lib/tapyrus/store/chain_entry.rb +0 -4
- data/lib/tapyrus/store/db/level_db.rb +5 -9
- data/lib/tapyrus/store/db.rb +0 -2
- data/lib/tapyrus/store/spv_chain.rb +11 -17
- data/lib/tapyrus/store.rb +1 -3
- data/lib/tapyrus/tx.rb +45 -37
- data/lib/tapyrus/tx_builder.rb +158 -0
- data/lib/tapyrus/tx_in.rb +1 -6
- data/lib/tapyrus/tx_out.rb +2 -7
- data/lib/tapyrus/util.rb +20 -7
- data/lib/tapyrus/validation.rb +12 -11
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +22 -18
- data/lib/tapyrus/wallet/base.rb +12 -9
- data/lib/tapyrus/wallet/db.rb +6 -9
- data/lib/tapyrus/wallet/master_key.rb +2 -4
- data/lib/tapyrus.rb +8 -30
- data/tapyrusrb.gemspec +13 -14
- metadata +26 -7
- data/.travis.yml +0 -14
data/lib/tapyrus/slip39/sss.rb
CHANGED
|
@@ -2,10 +2,8 @@ require 'securerandom'
|
|
|
2
2
|
|
|
3
3
|
module Tapyrus
|
|
4
4
|
module SLIP39
|
|
5
|
-
|
|
6
5
|
# Shamir's Secret Sharing
|
|
7
6
|
class SSS
|
|
8
|
-
|
|
9
7
|
include Tapyrus::Util
|
|
10
8
|
extend Tapyrus::Util
|
|
11
9
|
|
|
@@ -37,39 +35,56 @@ module Tapyrus
|
|
|
37
35
|
raise ArgumentError, 'Groups is empty.' if groups.empty?
|
|
38
36
|
raise ArgumentError, 'Group threshold must be greater than 0.' if group_threshold.nil? || group_threshold < 1
|
|
39
37
|
raise ArgumentError, 'Master secret does not specified.' unless secret
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
if (secret.htb.bytesize * 8) < MIN_STRENGTH_BITS
|
|
39
|
+
raise ArgumentError,
|
|
40
|
+
"The length of the master secret (#{secret.htb.bytesize} bytes) must be at least #{MIN_STRENGTH_BITS / 8} bytes."
|
|
41
|
+
end
|
|
42
|
+
unless secret.bytesize.even?
|
|
43
|
+
raise ArgumentError, 'The length of the master secret in bytes must be an even number.'
|
|
44
|
+
end
|
|
45
|
+
unless passphrase.ascii_only?
|
|
46
|
+
raise ArgumentError, 'The passphrase must contain only printable ASCII characters (code points 32-126).'
|
|
47
|
+
end
|
|
48
|
+
if group_threshold > groups.length
|
|
49
|
+
raise ArgumentError,
|
|
50
|
+
"The requested group threshold (#{group_threshold}) must not exceed the number of groups (#{groups.length})."
|
|
51
|
+
end
|
|
44
52
|
groups.each do |threshold, count|
|
|
45
53
|
raise ArgumentError, 'Group threshold must be greater than 0.' if threshold.nil? || threshold < 1
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if threshold > count
|
|
55
|
+
raise ArgumentError,
|
|
56
|
+
"The requested member threshold (#{threshold}) must not exceed the number of share (#{count})."
|
|
57
|
+
end
|
|
58
|
+
if threshold == 1 && count > 1
|
|
59
|
+
raise ArgumentError,
|
|
60
|
+
'Creating multiple member shares with member threshold 1 is not allowed. Use 1-of-1 member sharing instead.'
|
|
61
|
+
end
|
|
48
62
|
end
|
|
49
63
|
|
|
50
|
-
id = SecureRandom.random_number(
|
|
64
|
+
id = SecureRandom.random_number(32_767) # 32767 is max number for 15 bits.
|
|
51
65
|
ems = encrypt(secret, passphrase, exp, id)
|
|
52
66
|
|
|
53
67
|
group_shares = split_secret(group_threshold, groups.length, ems)
|
|
54
68
|
|
|
55
|
-
shares =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
shares =
|
|
70
|
+
group_shares.map.with_index do |s, i|
|
|
71
|
+
group_index, group_share = s[0], s[1]
|
|
72
|
+
member_threshold, member_count = groups[i][0], groups[i][1]
|
|
73
|
+
shares = split_secret(member_threshold, member_count, group_share)
|
|
74
|
+
shares.map do |member_index, member_share|
|
|
75
|
+
share = Tapyrus::SLIP39::Share.new
|
|
76
|
+
share.id = id
|
|
77
|
+
share.iteration_exp = exp
|
|
78
|
+
share.group_index = group_index
|
|
79
|
+
share.group_threshold = group_threshold
|
|
80
|
+
share.group_count = groups.length
|
|
81
|
+
share.member_index = member_index
|
|
82
|
+
share.member_threshold = member_threshold
|
|
83
|
+
share.value = member_share
|
|
84
|
+
share.checksum = share.calculate_checksum
|
|
85
|
+
share
|
|
86
|
+
end
|
|
71
87
|
end
|
|
72
|
-
end
|
|
73
88
|
shares
|
|
74
89
|
end
|
|
75
90
|
|
|
@@ -92,9 +107,15 @@ module Tapyrus
|
|
|
92
107
|
|
|
93
108
|
shares.each do |share|
|
|
94
109
|
raise ArgumentError, 'Invalid set of shares. All shares must have the same id.' unless id == share.id
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
110
|
+
unless group_threshold == share.group_threshold
|
|
111
|
+
raise ArgumentError, 'Invalid set of shares. All shares must have the same group threshold.'
|
|
112
|
+
end
|
|
113
|
+
unless group_count == share.group_count
|
|
114
|
+
raise ArgumentError, 'Invalid set of shares. All shares must have the same group count.'
|
|
115
|
+
end
|
|
116
|
+
unless exp == share.iteration_exp
|
|
117
|
+
raise ArgumentError, 'Invalid set of shares. All Shares must have the same iteration exponent.'
|
|
118
|
+
end
|
|
98
119
|
groups[share.group_index] ||= []
|
|
99
120
|
groups[share.group_index] << share
|
|
100
121
|
end
|
|
@@ -102,20 +123,29 @@ module Tapyrus
|
|
|
102
123
|
group_shares = {}
|
|
103
124
|
groups.each do |group_index, shares|
|
|
104
125
|
member_threshold = shares.first.member_threshold
|
|
105
|
-
|
|
126
|
+
if shares.length < member_threshold
|
|
127
|
+
raise ArgumentError,
|
|
128
|
+
"Wrong number of mnemonics. Threshold is #{member_threshold}, but share count is #{shares.length}"
|
|
129
|
+
end
|
|
106
130
|
if shares.length == 1 && member_threshold == 1
|
|
107
131
|
group_shares[group_index] = shares.first.value
|
|
108
132
|
else
|
|
109
133
|
value_length = shares.first.value.length
|
|
110
134
|
x_coordinates = []
|
|
111
135
|
shares.each do |share|
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
unless member_threshold == share.member_threshold
|
|
137
|
+
raise ArgumentError, 'Invalid set of shares. All shares in a group must have the same member threshold.'
|
|
138
|
+
end
|
|
139
|
+
unless value_length == share.value.length
|
|
140
|
+
raise ArgumentError, 'Invalid set of shares. All share values must have the same length.'
|
|
141
|
+
end
|
|
114
142
|
x_coordinates << share.member_index
|
|
115
143
|
end
|
|
116
144
|
x_coordinates.uniq!
|
|
117
|
-
|
|
118
|
-
|
|
145
|
+
unless x_coordinates.size == shares.size
|
|
146
|
+
raise ArgumentError, 'Invalid set of shares. Share indices must be unique.'
|
|
147
|
+
end
|
|
148
|
+
interpolate_shares = shares.map { |s| [s.member_index, s.value] }
|
|
119
149
|
|
|
120
150
|
secret = interpolate(interpolate_shares, SECRET_INDEX)
|
|
121
151
|
digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
|
|
@@ -129,9 +159,12 @@ module Tapyrus
|
|
|
129
159
|
|
|
130
160
|
return decrypt(group_shares.values.first, passphrase, exp, id) if group_threshold == 1
|
|
131
161
|
|
|
132
|
-
|
|
162
|
+
if group_shares.length < group_threshold
|
|
163
|
+
raise ArgumentError,
|
|
164
|
+
"Wrong number of mnemonics. Group threshold is #{group_threshold}, but share count is #{group_shares.length}"
|
|
165
|
+
end
|
|
133
166
|
|
|
134
|
-
interpolate_shares = group_shares.map{|k, v|[k, v]}
|
|
167
|
+
interpolate_shares = group_shares.map { |k, v| [k, v] }
|
|
135
168
|
secret = interpolate(interpolate_shares, SECRET_INDEX)
|
|
136
169
|
digest_value = interpolate(interpolate_shares, DIGEST_INDEX).htb
|
|
137
170
|
digest, random_value = digest_value[0...DIGEST_LENGTH_BYTES].bth, digest_value[DIGEST_LENGTH_BYTES..-1].bth
|
|
@@ -148,17 +181,24 @@ module Tapyrus
|
|
|
148
181
|
# @param [Integer] x the x coordinate of the result.
|
|
149
182
|
# @return [String] f(x) value with hex format.
|
|
150
183
|
def self.interpolate(shares, x)
|
|
151
|
-
s = shares.find{|s|s[0] == x}
|
|
184
|
+
s = shares.find { |s| s[0] == x }
|
|
152
185
|
return s[1] if s
|
|
153
186
|
|
|
154
|
-
log_prod = shares.sum{|s|LOG_TABLE[s[0] ^ x]}
|
|
187
|
+
log_prod = shares.sum { |s| LOG_TABLE[s[0] ^ x] }
|
|
155
188
|
|
|
156
189
|
result = ('00' * shares.first[1].length).htb
|
|
157
190
|
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 =
|
|
160
|
-
|
|
161
|
-
|
|
191
|
+
log_basis_eval = (log_prod - LOG_TABLE[share[0] ^ x] - shares.sum { |s| LOG_TABLE[share[0] ^ s[0]] }) % 255
|
|
192
|
+
result =
|
|
193
|
+
share[1]
|
|
194
|
+
.htb
|
|
195
|
+
.bytes
|
|
196
|
+
.each
|
|
197
|
+
.map
|
|
198
|
+
.with_index do |v, i|
|
|
199
|
+
(result[i].bti ^ (v == 0 ? 0 : (EXP_TABLE[(LOG_TABLE[v] + log_basis_eval) % 255]))).itb
|
|
200
|
+
end
|
|
201
|
+
.join
|
|
162
202
|
end
|
|
163
203
|
result.bth
|
|
164
204
|
end
|
|
@@ -172,10 +212,14 @@ module Tapyrus
|
|
|
172
212
|
l, r = ems[0...(ems.length / 2)].htb, ems[(ems.length / 2)..-1].htb
|
|
173
213
|
salt = get_salt(id)
|
|
174
214
|
e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
|
|
175
|
-
Tapyrus::SLIP39::ROUND_COUNT
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
215
|
+
Tapyrus::SLIP39::ROUND_COUNT
|
|
216
|
+
.times
|
|
217
|
+
.to_a
|
|
218
|
+
.reverse
|
|
219
|
+
.each do |i|
|
|
220
|
+
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
|
221
|
+
l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
|
|
222
|
+
end
|
|
179
223
|
(r + l).bth
|
|
180
224
|
end
|
|
181
225
|
|
|
@@ -190,10 +234,13 @@ module Tapyrus
|
|
|
190
234
|
l, r = s[0...(s.bytesize / 2)], s[(s.bytesize / 2)..-1]
|
|
191
235
|
salt = get_salt(id)
|
|
192
236
|
e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
|
|
193
|
-
Tapyrus::SLIP39::ROUND_COUNT
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
237
|
+
Tapyrus::SLIP39::ROUND_COUNT
|
|
238
|
+
.times
|
|
239
|
+
.to_a
|
|
240
|
+
.each do |i|
|
|
241
|
+
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
|
242
|
+
l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
|
|
243
|
+
end
|
|
197
244
|
(r + l).bth
|
|
198
245
|
end
|
|
199
246
|
|
|
@@ -220,26 +267,29 @@ module Tapyrus
|
|
|
220
267
|
# @return [Array[Integer, String]] the array of split secret.
|
|
221
268
|
def self.split_secret(threshold, count, secret)
|
|
222
269
|
raise ArgumentError, "The requested threshold (#{threshold}) must be a positive integer." if threshold < 1
|
|
223
|
-
|
|
224
|
-
|
|
270
|
+
if threshold > count
|
|
271
|
+
raise ArgumentError, "The requested threshold (#{threshold}) must not exceed the number of shares (#{count})."
|
|
272
|
+
end
|
|
273
|
+
if count > MAX_SHARE_COUNT
|
|
274
|
+
raise ArgumentError, "The requested number of shares (#{count}) must not exceed #{MAX_SHARE_COUNT}."
|
|
275
|
+
end
|
|
225
276
|
|
|
226
|
-
return count.times.map{|i|[i, secret]} if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
|
277
|
+
return count.times.map { |i| [i, secret] } if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
|
227
278
|
|
|
228
279
|
random_share_count = threshold - 2
|
|
229
280
|
|
|
230
|
-
shares = random_share_count.times.map{|i|[i, SecureRandom.hex(secret.htb.bytesize)]}
|
|
281
|
+
shares = random_share_count.times.map { |i| [i, SecureRandom.hex(secret.htb.bytesize)] }
|
|
231
282
|
random_part = SecureRandom.hex(secret.htb.bytesize - DIGEST_LENGTH_BYTES)
|
|
232
283
|
digest = create_digest(secret, random_part)
|
|
233
284
|
|
|
234
285
|
base_shares = shares + [[DIGEST_INDEX, digest + random_part], [SECRET_INDEX, secret]]
|
|
235
286
|
|
|
236
|
-
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)]}
|
|
287
|
+
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)] }
|
|
237
288
|
|
|
238
289
|
shares
|
|
239
290
|
end
|
|
240
291
|
|
|
241
292
|
private_class_method :split_secret, :get_salt, :interpolate, :encrypt, :decrypt, :create_digest
|
|
242
|
-
|
|
243
293
|
end
|
|
244
294
|
end
|
|
245
|
-
end
|
|
295
|
+
end
|
data/lib/tapyrus/slip39.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module SLIP39
|
|
3
|
-
|
|
4
3
|
WORDS = File.readlines("#{__dir__}/slip39/wordlist/english.txt").map(&:strip)
|
|
5
4
|
|
|
6
5
|
module_function
|
|
@@ -15,37 +14,53 @@ module Tapyrus
|
|
|
15
14
|
|
|
16
15
|
# The length of the radix in bits.
|
|
17
16
|
RADIX_BITS = 10
|
|
17
|
+
|
|
18
18
|
# The number of words in the wordlist.
|
|
19
|
-
RADIX = 2
|
|
19
|
+
RADIX = 2**RADIX_BITS
|
|
20
|
+
|
|
20
21
|
# The length of the random identifier in bits.
|
|
21
22
|
ID_LENGTH_BITS = 15
|
|
23
|
+
|
|
22
24
|
# The length of the iteration exponent in bits.
|
|
23
25
|
ITERATION_EXP_LENGTH_BITS = 5
|
|
26
|
+
|
|
24
27
|
# The length of the random identifier and iteration exponent in words.
|
|
25
28
|
ID_EXP_LENGTH_WORDS = bits_to_words(ID_LENGTH_BITS + ITERATION_EXP_LENGTH_BITS)
|
|
29
|
+
|
|
26
30
|
# The maximum number of shares that can be created.
|
|
27
31
|
MAX_SHARE_COUNT = 16
|
|
32
|
+
|
|
28
33
|
# The length of the RS1024 checksum in words.
|
|
29
34
|
CHECKSUM_LENGTH_WORDS = 3
|
|
35
|
+
|
|
30
36
|
# The length of the digest of the shared secret in bytes.
|
|
31
37
|
DIGEST_LENGTH_BYTES = 4
|
|
38
|
+
|
|
32
39
|
# The customization string used in the RS1024 checksum and in the PBKDF2 salt.
|
|
33
40
|
CUSTOMIZATION_STRING = 'shamir'.bytes
|
|
41
|
+
|
|
34
42
|
# The length of the mnemonic in words without the share value.
|
|
35
43
|
METADATA_LENGTH_WORDS = ID_EXP_LENGTH_WORDS + 2 + CHECKSUM_LENGTH_WORDS
|
|
44
|
+
|
|
36
45
|
# The minimum allowed entropy of the master secret.
|
|
37
46
|
MIN_STRENGTH_BITS = 128
|
|
47
|
+
|
|
38
48
|
# The minimum allowed length of the mnemonic in words.
|
|
39
49
|
MIN_MNEMONIC_LENGTH_WORDS = METADATA_LENGTH_WORDS + bits_to_words(MIN_STRENGTH_BITS)
|
|
50
|
+
|
|
40
51
|
# The minimum number of iterations to use in PBKDF2.
|
|
41
|
-
BASE_ITERATION_COUNT =
|
|
52
|
+
BASE_ITERATION_COUNT = 10_000
|
|
53
|
+
|
|
42
54
|
# The number of rounds to use in the Feistel cipher.
|
|
43
55
|
ROUND_COUNT = 4
|
|
56
|
+
|
|
44
57
|
# The index of the share containing the shared secret.
|
|
45
58
|
SECRET_INDEX = 255
|
|
59
|
+
|
|
46
60
|
# The index of the share containing the digest of the shared secret.
|
|
47
61
|
DIGEST_INDEX = 254
|
|
48
62
|
|
|
63
|
+
# prettier-ignore
|
|
49
64
|
EXP_TABLE = [
|
|
50
65
|
1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19,
|
|
51
66
|
53, 95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34,
|
|
@@ -66,6 +81,7 @@ module Tapyrus
|
|
|
66
81
|
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246
|
|
67
82
|
]
|
|
68
83
|
|
|
84
|
+
# prettier-ignore
|
|
69
85
|
LOG_TABLE = [
|
|
70
86
|
0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
|
|
71
87
|
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28,
|
|
@@ -88,6 +104,5 @@ module Tapyrus
|
|
|
88
104
|
|
|
89
105
|
autoload :SSS, 'tapyrus/slip39/sss'
|
|
90
106
|
autoload :Share, 'tapyrus/slip39/share'
|
|
91
|
-
|
|
92
107
|
end
|
|
93
|
-
end
|
|
108
|
+
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
2
|
module Store
|
|
3
|
-
|
|
4
3
|
# wrap a block header object with extra data.
|
|
5
4
|
class ChainEntry
|
|
6
5
|
include Tapyrus::HexConverter
|
|
@@ -60,9 +59,6 @@ module Tapyrus
|
|
|
60
59
|
height_value = height_value.htb.reverse
|
|
61
60
|
Tapyrus.pack_var_int(height_value.bytesize) + height_value + header.to_payload
|
|
62
61
|
end
|
|
63
|
-
|
|
64
62
|
end
|
|
65
|
-
|
|
66
63
|
end
|
|
67
|
-
|
|
68
64
|
end
|
|
@@ -3,9 +3,7 @@ require 'leveldb-native'
|
|
|
3
3
|
module Tapyrus
|
|
4
4
|
module Store
|
|
5
5
|
module DB
|
|
6
|
-
|
|
7
6
|
class LevelDB
|
|
8
|
-
|
|
9
7
|
attr_reader :db
|
|
10
8
|
attr_reader :logger
|
|
11
9
|
|
|
@@ -62,9 +60,11 @@ module Tapyrus
|
|
|
62
60
|
# @param [Tapyrus::Store::ChainEntry]
|
|
63
61
|
def save_entry(entry)
|
|
64
62
|
db.batch do
|
|
65
|
-
db.put(entry.key
|
|
63
|
+
db.put(entry.key, entry.to_payload)
|
|
66
64
|
db.put(height_key(entry.height), entry.block_hash)
|
|
67
|
-
|
|
65
|
+
if entry.header.upgrade_agg_pubkey?
|
|
66
|
+
add_agg_pubkey(entry.height == 0 ? 0 : entry.height + 1, entry.header.x_field)
|
|
67
|
+
end
|
|
68
68
|
connect_entry(entry)
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -134,9 +134,7 @@ module Tapyrus
|
|
|
134
134
|
unless tip_block.block_hash == entry.prev_hash
|
|
135
135
|
raise "entry(#{entry.block_hash}) does not reference current best block hash(#{tip_block.block_hash})"
|
|
136
136
|
end
|
|
137
|
-
unless tip_block.height + 1 == entry.height
|
|
138
|
-
raise "block height is small than current best block."
|
|
139
|
-
end
|
|
137
|
+
raise 'block height is small than current best block.' unless tip_block.height + 1 == entry.height
|
|
140
138
|
end
|
|
141
139
|
db.put(KEY_PREFIX[:best], entry.block_hash)
|
|
142
140
|
db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
|
|
@@ -148,9 +146,7 @@ module Tapyrus
|
|
|
148
146
|
index = db.get(KEY_PREFIX[:latest_agg_pubkey])
|
|
149
147
|
index&.to_i(16)
|
|
150
148
|
end
|
|
151
|
-
|
|
152
149
|
end
|
|
153
|
-
|
|
154
150
|
end
|
|
155
151
|
end
|
|
156
152
|
end
|
data/lib/tapyrus/store/db.rb
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
module Tapyrus
|
|
2
|
-
|
|
3
2
|
module Store
|
|
4
|
-
|
|
5
3
|
KEY_PREFIX = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
entry: 'e', # key: block hash, value: Tapyrus::Store::ChainEntry payload
|
|
5
|
+
height: 'h', # key: block height, value: block hash.
|
|
6
|
+
best: 'B', # value: best block hash.
|
|
7
|
+
next: 'n', # key: block hash, value: A hash of the next block of the specified hash
|
|
8
|
+
agg_pubkey: 'a', # key: index, value: Activated block height | aggregated public key.
|
|
9
|
+
latest_agg_pubkey: 'g' # value: latest agg pubkey index.
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
class SPVChain
|
|
15
|
-
|
|
16
13
|
attr_reader :db
|
|
17
14
|
attr_reader :logger
|
|
18
15
|
|
|
@@ -53,7 +50,9 @@ module Tapyrus
|
|
|
53
50
|
logger.info("append header #{header.block_id}")
|
|
54
51
|
best_block = latest_block
|
|
55
52
|
current_height = best_block.height
|
|
56
|
-
|
|
53
|
+
unless header.valid?(db.agg_pubkey_with_height(current_height + 1))
|
|
54
|
+
raise "this header is invalid. #{header.block_hash}"
|
|
55
|
+
end
|
|
57
56
|
if best_block.block_hash == header.prev_hash
|
|
58
57
|
entry = Tapyrus::Store::ChainEntry.new(header, current_height + 1)
|
|
59
58
|
db.save_entry(entry)
|
|
@@ -107,13 +106,8 @@ module Tapyrus
|
|
|
107
106
|
# if database is empty, put genesis block.
|
|
108
107
|
# @param [Tapyrus::Block] genesis genesis block
|
|
109
108
|
def initialize_block(genesis)
|
|
110
|
-
unless latest_block
|
|
111
|
-
db.save_entry(ChainEntry.new(genesis.header, 0))
|
|
112
|
-
end
|
|
109
|
+
db.save_entry(ChainEntry.new(genesis.header, 0)) unless latest_block
|
|
113
110
|
end
|
|
114
|
-
|
|
115
111
|
end
|
|
116
|
-
|
|
117
112
|
end
|
|
118
|
-
|
|
119
|
-
end
|
|
113
|
+
end
|
data/lib/tapyrus/store.rb
CHANGED
data/lib/tapyrus/tx.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
module Tapyrus
|
|
5
|
-
|
|
6
5
|
# Transaction class
|
|
7
6
|
class Tx
|
|
8
7
|
include Tapyrus::HexConverter
|
|
@@ -10,7 +9,7 @@ module Tapyrus
|
|
|
10
9
|
MAX_STANDARD_VERSION = 2
|
|
11
10
|
|
|
12
11
|
# The maximum weight for transactions we're willing to relay/mine
|
|
13
|
-
MAX_STANDARD_TX_WEIGHT =
|
|
12
|
+
MAX_STANDARD_TX_WEIGHT = 400_000
|
|
14
13
|
|
|
15
14
|
attr_accessor :features
|
|
16
15
|
attr_reader :inputs
|
|
@@ -34,14 +33,10 @@ module Tapyrus
|
|
|
34
33
|
|
|
35
34
|
in_count = Tapyrus.unpack_var_int_from_io(buf)
|
|
36
35
|
|
|
37
|
-
in_count.times
|
|
38
|
-
tx.inputs << TxIn.parse_from_payload(buf)
|
|
39
|
-
end
|
|
36
|
+
in_count.times { tx.inputs << TxIn.parse_from_payload(buf) }
|
|
40
37
|
|
|
41
38
|
out_count = Tapyrus.unpack_var_int_from_io(buf)
|
|
42
|
-
out_count.times
|
|
43
|
-
tx.outputs << TxOut.parse_from_payload(buf)
|
|
44
|
-
end
|
|
39
|
+
out_count.times { tx.outputs << TxOut.parse_from_payload(buf) }
|
|
45
40
|
|
|
46
41
|
tx.lock_time = buf.read(4).unpack('V').first
|
|
47
42
|
|
|
@@ -58,7 +53,7 @@ module Tapyrus
|
|
|
58
53
|
|
|
59
54
|
def txid
|
|
60
55
|
buf = [features].pack('V')
|
|
61
|
-
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
|
|
56
|
+
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map { |i| i.to_payload(use_malfix: true) }.join
|
|
62
57
|
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
|
63
58
|
buf << [lock_time].pack('V')
|
|
64
59
|
Tapyrus.double_sha256(buf).reverse.bth
|
|
@@ -95,6 +90,7 @@ module Tapyrus
|
|
|
95
90
|
outputs.each do |o|
|
|
96
91
|
return false unless o.script_pubkey.standard?
|
|
97
92
|
data_count += 1 if o.script_pubkey.op_return?
|
|
93
|
+
|
|
98
94
|
# TODO add non P2SH multisig relay(permitbaremultisig)
|
|
99
95
|
return false if o.dust?
|
|
100
96
|
end
|
|
@@ -114,8 +110,14 @@ module Tapyrus
|
|
|
114
110
|
# @param [Integer] amount tapyrus amount locked in input. required for witness input only.
|
|
115
111
|
# @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
|
|
116
112
|
# the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.
|
|
117
|
-
def sighash_for_input(
|
|
118
|
-
|
|
113
|
+
def sighash_for_input(
|
|
114
|
+
input_index,
|
|
115
|
+
output_script,
|
|
116
|
+
hash_type: SIGHASH_TYPE[:all],
|
|
117
|
+
sig_version: :base,
|
|
118
|
+
amount: nil,
|
|
119
|
+
skip_separator_index: 0
|
|
120
|
+
)
|
|
119
121
|
raise ArgumentError, 'input_index must be specified.' unless input_index
|
|
120
122
|
raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
|
|
121
123
|
raise ArgumentError, 'script_pubkey must be specified.' unless output_script
|
|
@@ -129,16 +131,19 @@ module Tapyrus
|
|
|
129
131
|
# @param [Integer] amount the amount of tapyrus, require for witness program only.
|
|
130
132
|
# @param [Array] flags the flags used when execute script interpreter.
|
|
131
133
|
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
|
|
132
|
-
if script_pubkey.p2sh?
|
|
133
|
-
flags << SCRIPT_VERIFY_P2SH
|
|
134
|
-
end
|
|
134
|
+
flags << SCRIPT_VERIFY_P2SH if script_pubkey.p2sh?
|
|
135
135
|
verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def to_h
|
|
139
139
|
{
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
txid: txid,
|
|
141
|
+
hash: tx_hash,
|
|
142
|
+
features: features,
|
|
143
|
+
size: size,
|
|
144
|
+
locktime: lock_time,
|
|
145
|
+
vin: inputs.map(&:to_h),
|
|
146
|
+
vout: outputs.map.with_index { |tx_out, index| tx_out.to_h.merge({ n: index }) }
|
|
142
147
|
}
|
|
143
148
|
end
|
|
144
149
|
|
|
@@ -154,43 +159,48 @@ module Tapyrus
|
|
|
154
159
|
|
|
155
160
|
# generate sighash with legacy format
|
|
156
161
|
def sighash_for_legacy(index, script_code, hash_type)
|
|
157
|
-
ins =
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
ins =
|
|
163
|
+
inputs.map.with_index do |i, idx|
|
|
164
|
+
if idx == index
|
|
165
|
+
i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
|
|
166
|
+
else
|
|
167
|
+
case hash_type & 0x1f
|
|
162
168
|
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
|
163
169
|
i.to_payload(Tapyrus::Script.new, 0)
|
|
164
170
|
else
|
|
165
171
|
i.to_payload(Tapyrus::Script.new)
|
|
172
|
+
end
|
|
166
173
|
end
|
|
167
174
|
end
|
|
168
|
-
end
|
|
169
175
|
|
|
170
176
|
outs = outputs.map(&:to_payload)
|
|
171
177
|
out_size = Tapyrus.pack_var_int(outputs.size)
|
|
172
178
|
|
|
173
179
|
case hash_type & 0x1f
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
when SIGHASH_TYPE[:none]
|
|
181
|
+
outs = ''
|
|
182
|
+
out_size = Tapyrus.pack_var_int(0)
|
|
183
|
+
when SIGHASH_TYPE[:single]
|
|
184
|
+
return "\x01".ljust(32, "\x00") if index >= outputs.size
|
|
185
|
+
outs =
|
|
186
|
+
outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
|
|
187
|
+
out_size = Tapyrus.pack_var_int(index + 1)
|
|
181
188
|
end
|
|
182
189
|
|
|
183
|
-
if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
|
184
|
-
ins = [ins[index]]
|
|
185
|
-
end
|
|
190
|
+
ins = [ins[index]] if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
|
186
191
|
|
|
187
|
-
buf = [
|
|
188
|
-
|
|
192
|
+
buf = [
|
|
193
|
+
[features].pack('V'),
|
|
194
|
+
Tapyrus.pack_var_int(ins.size),
|
|
195
|
+
ins,
|
|
196
|
+
out_size,
|
|
197
|
+
outs,
|
|
198
|
+
[lock_time, hash_type].pack('VV')
|
|
199
|
+
].join
|
|
189
200
|
|
|
190
201
|
Tapyrus.double_sha256(buf)
|
|
191
202
|
end
|
|
192
203
|
|
|
193
|
-
|
|
194
204
|
# verify input signature for legacy tx.
|
|
195
205
|
def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
|
196
206
|
script_sig = inputs[input_index].script_sig
|
|
@@ -199,7 +209,5 @@ module Tapyrus
|
|
|
199
209
|
|
|
200
210
|
interpreter.verify_script(script_sig, script_pubkey)
|
|
201
211
|
end
|
|
202
|
-
|
|
203
212
|
end
|
|
204
|
-
|
|
205
213
|
end
|