tapyrus 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.prettierignore +3 -0
- data/.prettierrc.yaml +3 -0
- data/CODE_OF_CONDUCT.md +7 -7
- data/README.md +14 -17
- data/Rakefile +3 -3
- data/lib/openassets.rb +0 -2
- data/lib/openassets/marker_output.rb +0 -4
- data/lib/openassets/payload.rb +4 -10
- data/lib/schnorr.rb +2 -3
- data/lib/schnorr/signature.rb +3 -6
- data/lib/tapyrus.rb +6 -22
- data/lib/tapyrus/base58.rb +7 -6
- 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.rb +1 -1
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -4
- data/lib/tapyrus/ext_key.rb +38 -34
- 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 +19 -20
- data/lib/tapyrus/message.rb +14 -16
- 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/mnemonic.rb +17 -15
- data/lib/tapyrus/network.rb +0 -2
- 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 +3 -5
- data/lib/tapyrus/network/pool.rb +12 -12
- data/lib/tapyrus/node.rb +1 -1
- 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/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 +53 -25
- data/lib/tapyrus/script/color.rb +10 -0
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +72 -71
- 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.rb +0 -4
- 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/slip39.rb +20 -5
- data/lib/tapyrus/slip39/share.rb +41 -29
- data/lib/tapyrus/slip39/sss.rb +101 -57
- data/lib/tapyrus/store.rb +1 -3
- data/lib/tapyrus/store/chain_entry.rb +0 -4
- data/lib/tapyrus/store/db.rb +0 -2
- data/lib/tapyrus/store/db/level_db.rb +5 -9
- data/lib/tapyrus/store/spv_chain.rb +11 -17
- 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 +7 -9
- 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/tapyrusrb.gemspec +13 -14
- metadata +21 -4
- 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,18 @@ 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].htb.bytes.each.map.with_index do |v, i|
|
194
|
+
(result[i].bti ^ (v == 0 ? 0 : (EXP_TABLE[(LOG_TABLE[v] + log_basis_eval) % 255]))).itb
|
195
|
+
end.join
|
162
196
|
end
|
163
197
|
result.bth
|
164
198
|
end
|
@@ -172,10 +206,14 @@ module Tapyrus
|
|
172
206
|
l, r = ems[0...(ems.length / 2)].htb, ems[(ems.length / 2)..-1].htb
|
173
207
|
salt = get_salt(id)
|
174
208
|
e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
|
175
|
-
Tapyrus::SLIP39::ROUND_COUNT
|
176
|
-
|
177
|
-
|
178
|
-
|
209
|
+
Tapyrus::SLIP39::ROUND_COUNT
|
210
|
+
.times
|
211
|
+
.to_a
|
212
|
+
.reverse
|
213
|
+
.each do |i|
|
214
|
+
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
215
|
+
l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
|
216
|
+
end
|
179
217
|
(r + l).bth
|
180
218
|
end
|
181
219
|
|
@@ -190,10 +228,13 @@ module Tapyrus
|
|
190
228
|
l, r = s[0...(s.bytesize / 2)], s[(s.bytesize / 2)..-1]
|
191
229
|
salt = get_salt(id)
|
192
230
|
e = (Tapyrus::SLIP39::BASE_ITERATION_COUNT << exp) / Tapyrus::SLIP39::ROUND_COUNT
|
193
|
-
Tapyrus::SLIP39::ROUND_COUNT
|
194
|
-
|
195
|
-
|
196
|
-
|
231
|
+
Tapyrus::SLIP39::ROUND_COUNT
|
232
|
+
.times
|
233
|
+
.to_a
|
234
|
+
.each do |i|
|
235
|
+
f = OpenSSL::PKCS5.pbkdf2_hmac((i.itb + passphrase), salt + r, e, r.bytesize, 'sha256')
|
236
|
+
l, r = padding_zero(r, r.bytesize), padding_zero((l.bti ^ f.bti).itb, r.bytesize)
|
237
|
+
end
|
197
238
|
(r + l).bth
|
198
239
|
end
|
199
240
|
|
@@ -220,26 +261,29 @@ module Tapyrus
|
|
220
261
|
# @return [Array[Integer, String]] the array of split secret.
|
221
262
|
def self.split_secret(threshold, count, secret)
|
222
263
|
raise ArgumentError, "The requested threshold (#{threshold}) must be a positive integer." if threshold < 1
|
223
|
-
|
224
|
-
|
264
|
+
if threshold > count
|
265
|
+
raise ArgumentError, "The requested threshold (#{threshold}) must not exceed the number of shares (#{count})."
|
266
|
+
end
|
267
|
+
if count > MAX_SHARE_COUNT
|
268
|
+
raise ArgumentError, "The requested number of shares (#{count}) must not exceed #{MAX_SHARE_COUNT}."
|
269
|
+
end
|
225
270
|
|
226
|
-
return count.times.map{|i|[i, secret]} if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
271
|
+
return count.times.map { |i| [i, secret] } if threshold == 1 # if the threshold is 1, digest of the share is not used.
|
227
272
|
|
228
273
|
random_share_count = threshold - 2
|
229
274
|
|
230
|
-
shares = random_share_count.times.map{|i|[i, SecureRandom.hex(secret.htb.bytesize)]}
|
275
|
+
shares = random_share_count.times.map { |i| [i, SecureRandom.hex(secret.htb.bytesize)] }
|
231
276
|
random_part = SecureRandom.hex(secret.htb.bytesize - DIGEST_LENGTH_BYTES)
|
232
277
|
digest = create_digest(secret, random_part)
|
233
278
|
|
234
279
|
base_shares = shares + [[DIGEST_INDEX, digest + random_part], [SECRET_INDEX, secret]]
|
235
280
|
|
236
|
-
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)]}
|
281
|
+
(random_share_count...count).each { |i| shares << [i, interpolate(base_shares, i)] }
|
237
282
|
|
238
283
|
shares
|
239
284
|
end
|
240
285
|
|
241
286
|
private_class_method :split_secret, :get_salt, :interpolate, :encrypt, :decrypt, :create_digest
|
242
|
-
|
243
287
|
end
|
244
288
|
end
|
245
|
-
end
|
289
|
+
end
|
data/lib/tapyrus/store.rb
CHANGED
@@ -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
|
data/lib/tapyrus/store/db.rb
CHANGED
@@ -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
|
@@ -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/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
|