tapyrus 0.2.4 → 0.2.9
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/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 +14 -9
- data/lib/schnorr/sign_to_contract.rb +51 -0
- data/lib/schnorr/signature.rb +3 -6
- data/lib/tapyrus.rb +8 -30
- 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.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 +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 +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.rb +1 -0
- 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/script/color.rb +20 -2
- data/lib/tapyrus/script/multisig.rb +13 -12
- data/lib/tapyrus/script/script.rb +104 -67
- 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 +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/tapyrusrb.gemspec +13 -16
- metadata +22 -31
- data/.travis.yml +0 -12
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
|