tapyrus 0.3.8 → 0.4.0
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 +2 -2
- data/.ruby-version +1 -1
- data/README.md +43 -2
- data/lib/tapyrus/ext_key.rb +8 -4
- data/lib/tapyrus/key.rb +1 -1
- data/lib/tapyrus/key_path.rb +17 -13
- data/lib/tapyrus/message/fee_filter.rb +2 -2
- data/lib/tapyrus/message/header_and_short_ids.rb +4 -4
- data/lib/tapyrus/message/inventory.rb +1 -1
- data/lib/tapyrus/message/network_addr.rb +2 -2
- data/lib/tapyrus/message/ping.rb +2 -2
- data/lib/tapyrus/message/pong.rb +2 -2
- data/lib/tapyrus/message/send_cmpct.rb +2 -2
- data/lib/tapyrus/message/version.rb +3 -3
- data/lib/tapyrus/mnemonic.rb +9 -4
- data/lib/tapyrus/pstt/input.rb +422 -0
- data/lib/tapyrus/pstt/key_origin_info.rb +63 -0
- data/lib/tapyrus/pstt/output.rb +129 -0
- data/lib/tapyrus/pstt/proprietary.rb +54 -0
- data/lib/tapyrus/pstt/tx.rb +658 -0
- data/lib/tapyrus/pstt.rb +335 -0
- data/lib/tapyrus/rpc.rb +0 -2
- data/lib/tapyrus/script/script.rb +14 -4
- data/lib/tapyrus/secp256k1/rfc6979.rb +6 -3
- data/lib/tapyrus/tip0020.rb +341 -0
- data/lib/tapyrus/tx.rb +6 -2
- data/lib/tapyrus/tx_out.rb +2 -2
- data/lib/tapyrus/util.rb +3 -3
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +2 -2
- data/lib/tapyrus/wallet/db.rb +3 -3
- data/lib/tapyrus/wallet/master_key.rb +2 -2
- data/lib/tapyrus.rb +3 -3
- data/tapyrusrb.gemspec +0 -4
- metadata +10 -81
- data/exe/tapyrusrb-cli +0 -5
- data/exe/tapyrusrbd +0 -42
- data/lib/tapyrus/network/connection.rb +0 -70
- data/lib/tapyrus/network/message_handler.rb +0 -244
- data/lib/tapyrus/network/peer.rb +0 -210
- data/lib/tapyrus/network/peer_discovery.rb +0 -43
- data/lib/tapyrus/network/pool.rb +0 -135
- data/lib/tapyrus/network.rb +0 -11
- data/lib/tapyrus/node/cli.rb +0 -114
- data/lib/tapyrus/node/configuration.rb +0 -36
- data/lib/tapyrus/node/spv.rb +0 -78
- data/lib/tapyrus/node.rb +0 -7
- data/lib/tapyrus/rpc/http_server.rb +0 -64
- data/lib/tapyrus/rpc/request_handler.rb +0 -146
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
|
|
3
|
+
module Tapyrus
|
|
4
|
+
module TIP0020
|
|
5
|
+
# Token metadata class based on TIP-0020 specification
|
|
6
|
+
# @see https://github.com/chaintope/tips/blob/main/tip-0020.md
|
|
7
|
+
class Metadata
|
|
8
|
+
CURRENT_VERSION = "1.0"
|
|
9
|
+
MAX_NAME_LENGTH = 64
|
|
10
|
+
MAX_SYMBOL_LENGTH = 12
|
|
11
|
+
MAX_DESCRIPTION_LENGTH = 256
|
|
12
|
+
MIN_DECIMALS = 0
|
|
13
|
+
MAX_DECIMALS = 18
|
|
14
|
+
MAX_DATA_URI_SIZE = 32 * 1024 # 32KB
|
|
15
|
+
|
|
16
|
+
VALID_TOKEN_TYPES = %i[reissuable non_reissuable nft].freeze
|
|
17
|
+
NFT_FIELDS = %i[image animation_url external_url attributes].freeze
|
|
18
|
+
|
|
19
|
+
attr_accessor :token_type,
|
|
20
|
+
:version,
|
|
21
|
+
:name,
|
|
22
|
+
:symbol,
|
|
23
|
+
:decimals,
|
|
24
|
+
:description,
|
|
25
|
+
:icon,
|
|
26
|
+
:issuer,
|
|
27
|
+
:website,
|
|
28
|
+
:terms,
|
|
29
|
+
:properties,
|
|
30
|
+
:image,
|
|
31
|
+
:animation_url,
|
|
32
|
+
:external_url,
|
|
33
|
+
:attributes
|
|
34
|
+
|
|
35
|
+
# @param token_type [Symbol] Token type (:reissuable, :non_reissuable, :nft)
|
|
36
|
+
# @param version [String] Schema version (default: "1.0")
|
|
37
|
+
# @param name [String] Human-readable token name (max 64 characters, required)
|
|
38
|
+
# @param symbol [String] Token symbol (max 12 characters, required)
|
|
39
|
+
# @param decimals [Integer] Number of decimal places for display (0-18, default: 0)
|
|
40
|
+
# @param description [String] Token description (max 256 characters)
|
|
41
|
+
# @param icon [String] HTTPS URL or Data URI for icon
|
|
42
|
+
# @param issuer [Hash] Issuer information object
|
|
43
|
+
# @param website [String] Official website URL (HTTPS required)
|
|
44
|
+
# @param terms [String] URL to terms of service document (HTTPS required)
|
|
45
|
+
# @param properties [Hash] Additional custom properties
|
|
46
|
+
# @param image [String] NFT image URL (HTTPS or Data URI) - only for NFT
|
|
47
|
+
# @param animation_url [String] NFT animation/video/audio URL (HTTPS or Data URI) - only for NFT
|
|
48
|
+
# @param external_url [String] External URL to view NFT (HTTPS required) - only for NFT
|
|
49
|
+
# @param attributes [Array<Hash>] NFT attributes array with trait_type, value, display_type - only for NFT
|
|
50
|
+
def initialize(
|
|
51
|
+
token_type:,
|
|
52
|
+
version: CURRENT_VERSION,
|
|
53
|
+
name:,
|
|
54
|
+
symbol:,
|
|
55
|
+
decimals: 0,
|
|
56
|
+
description: nil,
|
|
57
|
+
icon: nil,
|
|
58
|
+
issuer: nil,
|
|
59
|
+
website: nil,
|
|
60
|
+
terms: nil,
|
|
61
|
+
properties: nil,
|
|
62
|
+
image: nil,
|
|
63
|
+
animation_url: nil,
|
|
64
|
+
external_url: nil,
|
|
65
|
+
attributes: nil
|
|
66
|
+
)
|
|
67
|
+
@token_type = token_type
|
|
68
|
+
@version = version
|
|
69
|
+
@name = name
|
|
70
|
+
@symbol = symbol
|
|
71
|
+
@decimals = decimals
|
|
72
|
+
@description = description
|
|
73
|
+
@icon = icon
|
|
74
|
+
@issuer = issuer
|
|
75
|
+
@website = website
|
|
76
|
+
@terms = terms
|
|
77
|
+
@properties = properties
|
|
78
|
+
@image = image
|
|
79
|
+
@animation_url = animation_url
|
|
80
|
+
@external_url = external_url
|
|
81
|
+
@attributes = attributes
|
|
82
|
+
validate!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validate metadata fields
|
|
86
|
+
# @raise [ArgumentError] if validation fails
|
|
87
|
+
def validate!
|
|
88
|
+
raise ArgumentError, "token_type is required" if token_type.nil?
|
|
89
|
+
unless VALID_TOKEN_TYPES.include?(token_type)
|
|
90
|
+
raise ArgumentError, "token_type must be one of #{VALID_TOKEN_TYPES.join(", ")}"
|
|
91
|
+
end
|
|
92
|
+
validate_nft_fields!
|
|
93
|
+
raise ArgumentError, "version is required" if version.nil? || version.empty?
|
|
94
|
+
raise ArgumentError, "version must be #{CURRENT_VERSION}" unless version == CURRENT_VERSION
|
|
95
|
+
raise ArgumentError, "name is required" if name.nil? || name.empty?
|
|
96
|
+
raise ArgumentError, "name must be #{MAX_NAME_LENGTH} characters or less" if name.length > MAX_NAME_LENGTH
|
|
97
|
+
raise ArgumentError, "symbol is required" if symbol.nil? || symbol.empty?
|
|
98
|
+
if symbol.length > MAX_SYMBOL_LENGTH
|
|
99
|
+
raise ArgumentError, "symbol must be #{MAX_SYMBOL_LENGTH} characters or less"
|
|
100
|
+
end
|
|
101
|
+
if decimals < MIN_DECIMALS || decimals > MAX_DECIMALS
|
|
102
|
+
raise ArgumentError, "decimals must be between #{MIN_DECIMALS} and #{MAX_DECIMALS}"
|
|
103
|
+
end
|
|
104
|
+
if description && description.length > MAX_DESCRIPTION_LENGTH
|
|
105
|
+
raise ArgumentError, "description must be #{MAX_DESCRIPTION_LENGTH} characters or less"
|
|
106
|
+
end
|
|
107
|
+
raise ArgumentError, "icon must be an HTTPS URL or Data URI" if icon && !valid_icon_format?(icon)
|
|
108
|
+
raise ArgumentError, "website must be an HTTPS URL" if website && !valid_https_url?(website)
|
|
109
|
+
raise ArgumentError, "terms must be an HTTPS URL" if terms && !valid_https_url?(terms)
|
|
110
|
+
raise ArgumentError, "image must be an HTTPS URL or Data URI" if image && !valid_media_url?(image)
|
|
111
|
+
if animation_url && !valid_media_url?(animation_url)
|
|
112
|
+
raise ArgumentError, "animation_url must be an HTTPS URL or Data URI"
|
|
113
|
+
end
|
|
114
|
+
raise ArgumentError, "external_url must be an HTTPS URL" if external_url && !valid_https_url?(external_url)
|
|
115
|
+
validate_issuer! if issuer
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Validate issuer object fields
|
|
119
|
+
# @raise [ArgumentError] if validation fails
|
|
120
|
+
def validate_issuer!
|
|
121
|
+
return unless issuer.is_a?(Hash)
|
|
122
|
+
issuer_url = issuer[:url] || issuer["url"]
|
|
123
|
+
raise ArgumentError, "issuer.url must be an HTTPS URL" if issuer_url && !valid_https_url?(issuer_url)
|
|
124
|
+
issuer_email = issuer[:email] || issuer["email"]
|
|
125
|
+
raise ArgumentError, "issuer.email must be a valid email address" if issuer_email && !valid_email?(issuer_email)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Validate NFT-specific fields are only used with NFT token type
|
|
129
|
+
# @raise [ArgumentError] if NFT fields are used with non-NFT token type
|
|
130
|
+
def validate_nft_fields!
|
|
131
|
+
return if token_type == :nft
|
|
132
|
+
nft_fields_present = NFT_FIELDS.select { |field| send(field) }
|
|
133
|
+
unless nft_fields_present.empty?
|
|
134
|
+
raise ArgumentError, "#{nft_fields_present.join(", ")} can only be used with NFT token type"
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Convert to Hash
|
|
139
|
+
# @return [Hash] metadata as hash
|
|
140
|
+
def to_h
|
|
141
|
+
result = { version: version, name: name, symbol: symbol }
|
|
142
|
+
result[:decimals] = decimals if decimals != 0
|
|
143
|
+
result[:description] = description if description
|
|
144
|
+
result[:icon] = icon if icon
|
|
145
|
+
result[:issuer] = issuer if issuer
|
|
146
|
+
result[:website] = website if website
|
|
147
|
+
result[:terms] = terms if terms
|
|
148
|
+
result[:properties] = properties if properties
|
|
149
|
+
# NFT fields
|
|
150
|
+
result[:image] = image if image
|
|
151
|
+
result[:animation_url] = animation_url if animation_url
|
|
152
|
+
result[:external_url] = external_url if external_url
|
|
153
|
+
result[:attributes] = attributes if attributes
|
|
154
|
+
result
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Canonicalize metadata according to RFC 8785 (JCS)
|
|
158
|
+
# @return [String] canonicalized JSON string
|
|
159
|
+
def canonicalize
|
|
160
|
+
jcs_serialize(to_h)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Calculate SHA256 hash of canonicalized metadata
|
|
164
|
+
# @return [String] 32-byte binary hash
|
|
165
|
+
def digest
|
|
166
|
+
Tapyrus.sha256(canonicalize)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Calculate SHA256 hash and return as hex string
|
|
170
|
+
# @return [String] 64-character hex string
|
|
171
|
+
def digest_hex
|
|
172
|
+
digest.bth
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Calculate P2C commitment: c = SHA256(P || h)
|
|
176
|
+
# @param pubkey [String] payment base public key (33 bytes compressed, hex string)
|
|
177
|
+
# @return [String] 32-byte binary commitment
|
|
178
|
+
def commitment(pubkey)
|
|
179
|
+
pubkey_bin = pubkey.htb
|
|
180
|
+
raise ArgumentError, "pubkey must be 33 bytes compressed public key" unless pubkey_bin.bytesize == 33
|
|
181
|
+
Tapyrus.sha256(pubkey_bin + digest)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Calculate P2C commitment and return as hex string
|
|
185
|
+
# @param pubkey [String] payment base public key (33 bytes compressed, hex string)
|
|
186
|
+
# @return [String] 64-character hex string
|
|
187
|
+
def commitment_hex(pubkey)
|
|
188
|
+
commitment(pubkey).bth
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Derive P2C public key: P' = P + c * G
|
|
192
|
+
# @param pubkey [String] payment base public key (33 bytes compressed, hex string)
|
|
193
|
+
# @return [String] P2C public key (33 bytes compressed, hex string)
|
|
194
|
+
# @raise [ArgumentError] if derivation results in point at infinity
|
|
195
|
+
def derive_p2c_pubkey(pubkey)
|
|
196
|
+
c = commitment(pubkey)
|
|
197
|
+
c_int = c.bth.to_i(16)
|
|
198
|
+
|
|
199
|
+
# P + c * G
|
|
200
|
+
group = ECDSA::Group::Secp256k1
|
|
201
|
+
point_p = Tapyrus::Key.new(pubkey: pubkey).to_point
|
|
202
|
+
point_cg = group.generator * c_int
|
|
203
|
+
point_p_prime = point_p + point_cg
|
|
204
|
+
|
|
205
|
+
raise ArgumentError, "P2C derivation resulted in point at infinity" if point_p_prime.infinity?
|
|
206
|
+
|
|
207
|
+
# Compress the result
|
|
208
|
+
ECDSA::Format::PointOctetString.encode(point_p_prime, compression: true).bth
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Derive P2C address
|
|
212
|
+
# @param pubkey [String] payment base public key (33 bytes compressed, hex string)
|
|
213
|
+
# @return [String] P2C address
|
|
214
|
+
def derive_p2c_address(pubkey)
|
|
215
|
+
p2c_pubkey = derive_p2c_pubkey(pubkey)
|
|
216
|
+
Tapyrus::Key.new(pubkey: p2c_pubkey).to_p2pkh
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Create ColorIdentifier based on token type
|
|
220
|
+
# @param pubkey [String] payment base public key (33 bytes compressed, hex string) - required for :reissuable
|
|
221
|
+
# @param out_point [Tapyrus::OutPoint] out point - required for :non_reissuable and :nft
|
|
222
|
+
# @return [Tapyrus::Color::ColorIdentifier] color identifier
|
|
223
|
+
def derive_color_id(pubkey: nil, out_point: nil)
|
|
224
|
+
case token_type
|
|
225
|
+
when :reissuable
|
|
226
|
+
raise ArgumentError, "pubkey is required for reissuable token" unless pubkey
|
|
227
|
+
p2c_pubkey = derive_p2c_pubkey(pubkey)
|
|
228
|
+
script = Tapyrus::Script.to_p2pkh(Tapyrus::Key.new(pubkey: p2c_pubkey).hash160)
|
|
229
|
+
Tapyrus::Color::ColorIdentifier.reissuable(script)
|
|
230
|
+
when :non_reissuable
|
|
231
|
+
raise ArgumentError, "out_point is required for non_reissuable token" unless out_point
|
|
232
|
+
Tapyrus::Color::ColorIdentifier.non_reissuable(out_point)
|
|
233
|
+
when :nft
|
|
234
|
+
raise ArgumentError, "out_point is required for nft token" unless out_point
|
|
235
|
+
Tapyrus::Color::ColorIdentifier.nft(out_point)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Parse from JSON string
|
|
240
|
+
# @param json_str [String] JSON string
|
|
241
|
+
# @param token_type [Symbol] Token type (:reissuable, :non_reissuable, :nft)
|
|
242
|
+
# @return [Metadata] metadata instance
|
|
243
|
+
def self.parse(json_str, token_type:)
|
|
244
|
+
data = JSON.parse(json_str, symbolize_names: true)
|
|
245
|
+
new(
|
|
246
|
+
token_type: token_type,
|
|
247
|
+
version: data[:version] || CURRENT_VERSION,
|
|
248
|
+
name: data[:name],
|
|
249
|
+
symbol: data[:symbol],
|
|
250
|
+
decimals: data[:decimals] || 0,
|
|
251
|
+
description: data[:description],
|
|
252
|
+
icon: data[:icon],
|
|
253
|
+
issuer: data[:issuer],
|
|
254
|
+
website: data[:website],
|
|
255
|
+
terms: data[:terms],
|
|
256
|
+
properties: data[:properties],
|
|
257
|
+
image: data[:image],
|
|
258
|
+
animation_url: data[:animation_url],
|
|
259
|
+
external_url: data[:external_url],
|
|
260
|
+
attributes: data[:attributes]
|
|
261
|
+
)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
|
|
266
|
+
# Check if the URL is a valid HTTPS URL
|
|
267
|
+
def valid_https_url?(url)
|
|
268
|
+
uri = URI.parse(url)
|
|
269
|
+
uri.is_a?(URI::HTTPS)
|
|
270
|
+
rescue URI::InvalidURIError
|
|
271
|
+
false
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Check if the icon format is valid (HTTPS URL or Data URI)
|
|
275
|
+
def valid_icon_format?(icon)
|
|
276
|
+
valid_media_url?(icon)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Check if media URL is valid (HTTPS URL or Data URI with size limit)
|
|
280
|
+
def valid_media_url?(url)
|
|
281
|
+
return url.bytesize <= MAX_DATA_URI_SIZE if url.start_with?("data:")
|
|
282
|
+
valid_https_url?(url)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Check if email format is valid
|
|
286
|
+
def valid_email?(email)
|
|
287
|
+
# Basic email format validation
|
|
288
|
+
email.match?(/\A[^@\s]+@[^@\s]+\.[^@\s]+\z/)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# RFC 8785 JSON Canonicalization Scheme serialization
|
|
292
|
+
# @param obj [Object] object to serialize
|
|
293
|
+
# @return [String] canonicalized JSON string
|
|
294
|
+
def jcs_serialize(obj)
|
|
295
|
+
case obj
|
|
296
|
+
when Hash
|
|
297
|
+
pairs =
|
|
298
|
+
obj
|
|
299
|
+
.keys
|
|
300
|
+
.map(&:to_s)
|
|
301
|
+
.sort
|
|
302
|
+
.map { |key| "#{jcs_serialize(key)}:#{jcs_serialize(obj[key.to_sym] || obj[key])}" }
|
|
303
|
+
"{#{pairs.join(",")}}"
|
|
304
|
+
when Array
|
|
305
|
+
"[#{obj.map { |v| jcs_serialize(v) }.join(",")}]"
|
|
306
|
+
when String
|
|
307
|
+
JSON.generate(obj)
|
|
308
|
+
when Integer
|
|
309
|
+
obj.to_s
|
|
310
|
+
when Float
|
|
311
|
+
# RFC 8785 requires specific float formatting
|
|
312
|
+
jcs_format_float(obj)
|
|
313
|
+
when TrueClass, FalseClass
|
|
314
|
+
obj.to_s
|
|
315
|
+
when NilClass
|
|
316
|
+
"null"
|
|
317
|
+
else
|
|
318
|
+
JSON.generate(obj)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Format float according to RFC 8785
|
|
323
|
+
def jcs_format_float(num)
|
|
324
|
+
return "0" if num.zero?
|
|
325
|
+
return "null" if num.nan? || num.infinite?
|
|
326
|
+
|
|
327
|
+
# Use exponential notation for very large or very small numbers
|
|
328
|
+
if num.abs >= 1e21 || (num != 0 && num.abs < 1e-6)
|
|
329
|
+
# Exponential format
|
|
330
|
+
exp = Math.log10(num.abs).floor
|
|
331
|
+
mantissa = num / (10**exp)
|
|
332
|
+
"#{mantissa}e#{exp >= 0 ? "+" : ""}#{exp}"
|
|
333
|
+
else
|
|
334
|
+
# Remove trailing zeros
|
|
335
|
+
str = num.to_s
|
|
336
|
+
str.sub(/\.?0+$/, "")
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
data/lib/tapyrus/tx.rb
CHANGED
|
@@ -47,6 +47,10 @@ module Tapyrus
|
|
|
47
47
|
to_hex.to_i(16)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def eql?(other)
|
|
51
|
+
other.is_a?(Tx) && self == other
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
def tx_hash
|
|
51
55
|
Tapyrus.double_sha256(to_payload).bth
|
|
52
56
|
end
|
|
@@ -118,9 +122,9 @@ module Tapyrus
|
|
|
118
122
|
# verify input signature.
|
|
119
123
|
# @param [Integer] input_index
|
|
120
124
|
# @param [Tapyrus::Script] script_pubkey the script pubkey for target input.
|
|
121
|
-
# @param [
|
|
125
|
+
# @param [Integer] flags the flags used when execute script interpreter.
|
|
122
126
|
def verify_input_sig(input_index, script_pubkey, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
|
|
123
|
-
flags
|
|
127
|
+
flags |= SCRIPT_VERIFY_P2SH if script_pubkey.p2sh?
|
|
124
128
|
verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
|
125
129
|
end
|
|
126
130
|
|
data/lib/tapyrus/tx_out.rb
CHANGED
|
@@ -17,14 +17,14 @@ module Tapyrus
|
|
|
17
17
|
|
|
18
18
|
def self.parse_from_payload(payload)
|
|
19
19
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
|
20
|
-
value = buf.read(8).unpack("
|
|
20
|
+
value = buf.read(8).unpack("Q<").first
|
|
21
21
|
script_size = Tapyrus.unpack_var_int_from_io(buf)
|
|
22
22
|
new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def to_payload
|
|
26
26
|
s = script_pubkey.to_payload
|
|
27
|
-
[value].pack("Q") << Tapyrus.pack_var_int(s.length) << s
|
|
27
|
+
[value].pack("Q<") << Tapyrus.pack_var_int(s.length) << s
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def to_empty_payload
|
data/lib/tapyrus/util.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Tapyrus
|
|
|
23
23
|
elsif i <= 0xffffffff
|
|
24
24
|
[0xfe, i].pack("CV")
|
|
25
25
|
elsif i <= 0xffffffffffffffff
|
|
26
|
-
[0xff, i].pack("CQ")
|
|
26
|
+
[0xff, i].pack("CQ<")
|
|
27
27
|
else
|
|
28
28
|
raise "int(#{i}) too large!"
|
|
29
29
|
end
|
|
@@ -37,7 +37,7 @@ module Tapyrus
|
|
|
37
37
|
when 0xfe
|
|
38
38
|
payload.unpack("xVa*")
|
|
39
39
|
when 0xff
|
|
40
|
-
payload.unpack("
|
|
40
|
+
payload.unpack("xQ<a*")
|
|
41
41
|
else
|
|
42
42
|
payload.unpack("Ca*")
|
|
43
43
|
end
|
|
@@ -52,7 +52,7 @@ module Tapyrus
|
|
|
52
52
|
when 0xfe
|
|
53
53
|
buf.read(4)&.unpack("V")&.first
|
|
54
54
|
when 0xff
|
|
55
|
-
buf.read(8)&.unpack("Q")&.first
|
|
55
|
+
buf.read(8)&.unpack("Q<")&.first
|
|
56
56
|
else
|
|
57
57
|
uchar
|
|
58
58
|
end
|
data/lib/tapyrus/version.rb
CHANGED
|
@@ -32,7 +32,7 @@ module Tapyrus
|
|
|
32
32
|
payload = buf.read
|
|
33
33
|
name, payload = Tapyrus.unpack_var_string(payload)
|
|
34
34
|
name = name.force_encoding("utf-8")
|
|
35
|
-
purpose, index, receive_depth, change_depth, lookahead = payload.unpack("
|
|
35
|
+
purpose, index, receive_depth, change_depth, lookahead = payload.unpack("V*")
|
|
36
36
|
a = Account.new(account_key, purpose, index, name)
|
|
37
37
|
a.receive_depth = receive_depth
|
|
38
38
|
a.change_depth = change_depth
|
|
@@ -43,7 +43,7 @@ module Tapyrus
|
|
|
43
43
|
def to_payload
|
|
44
44
|
payload = account_key.to_payload
|
|
45
45
|
payload << Tapyrus.pack_var_string(name.unpack("H*").first.htb)
|
|
46
|
-
payload << [purpose, index, receive_depth, change_depth, lookahead].pack("
|
|
46
|
+
payload << [purpose, index, receive_depth, change_depth, lookahead].pack("V*")
|
|
47
47
|
payload
|
|
48
48
|
end
|
|
49
49
|
|
data/lib/tapyrus/wallet/db.rb
CHANGED
|
@@ -30,7 +30,7 @@ module Tapyrus
|
|
|
30
30
|
|
|
31
31
|
def save_account(account)
|
|
32
32
|
level_db.batch do
|
|
33
|
-
id = [account.purpose, account.index].pack("
|
|
33
|
+
id = [account.purpose, account.index].pack("V*").bth
|
|
34
34
|
key = KEY_PREFIX[:account] + id
|
|
35
35
|
level_db.put(key, account.to_payload)
|
|
36
36
|
end
|
|
@@ -38,14 +38,14 @@ module Tapyrus
|
|
|
38
38
|
|
|
39
39
|
def save_key(account, purpose, index, key)
|
|
40
40
|
pubkey = key.pub
|
|
41
|
-
id = [account.purpose, account.index, purpose, index].pack("
|
|
41
|
+
id = [account.purpose, account.index, purpose, index].pack("V*").bth
|
|
42
42
|
k = KEY_PREFIX[:key] + id
|
|
43
43
|
level_db.put(k, pubkey)
|
|
44
44
|
key
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def get_keys(account)
|
|
48
|
-
id = [account.purpose, account.index].pack("
|
|
48
|
+
id = [account.purpose, account.index].pack("V*").bth
|
|
49
49
|
from = KEY_PREFIX[:key] + id + "00000000"
|
|
50
50
|
to = KEY_PREFIX[:key] + id + "ffffffff"
|
|
51
51
|
level_db.each(from: from, to: to).map { |k, v| v }
|
|
@@ -80,7 +80,7 @@ module Tapyrus
|
|
|
80
80
|
encrypted_data = ""
|
|
81
81
|
encrypted_data << enc.update(seed)
|
|
82
82
|
encrypted_data << enc.final
|
|
83
|
-
@seed = encrypted_data
|
|
83
|
+
@seed = encrypted_data.bth
|
|
84
84
|
@encrypted = true
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -91,7 +91,7 @@ module Tapyrus
|
|
|
91
91
|
dec.decrypt
|
|
92
92
|
dec.key, dec.iv = key_iv(dec, passphrase)
|
|
93
93
|
decrypted_data = ""
|
|
94
|
-
decrypted_data << dec.update(seed)
|
|
94
|
+
decrypted_data << dec.update(seed.htb)
|
|
95
95
|
decrypted_data << dec.final
|
|
96
96
|
@seed = decrypted_data
|
|
97
97
|
@encrypted = false
|
data/lib/tapyrus.rb
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
|
3
3
|
|
|
4
4
|
require "tapyrus/version"
|
|
5
|
-
require "eventmachine"
|
|
6
5
|
require "ecdsa"
|
|
7
6
|
require "securerandom"
|
|
7
|
+
require "stringio"
|
|
8
8
|
require "json"
|
|
9
9
|
require "jwt"
|
|
10
10
|
require "ffi"
|
|
@@ -36,12 +36,10 @@ module Tapyrus
|
|
|
36
36
|
autoload :ExtKey, "tapyrus/ext_key"
|
|
37
37
|
autoload :ExtPubkey, "tapyrus/ext_key"
|
|
38
38
|
autoload :Opcodes, "tapyrus/opcodes"
|
|
39
|
-
autoload :Node, "tapyrus/node"
|
|
40
39
|
autoload :Base58, "tapyrus/base58"
|
|
41
40
|
autoload :Secp256k1, "tapyrus/secp256k1"
|
|
42
41
|
autoload :Mnemonic, "tapyrus/mnemonic"
|
|
43
42
|
autoload :ValidationState, "tapyrus/validation"
|
|
44
|
-
autoload :Network, "tapyrus/network"
|
|
45
43
|
autoload :Store, "tapyrus/store"
|
|
46
44
|
autoload :RPC, "tapyrus/rpc"
|
|
47
45
|
autoload :Wallet, "tapyrus/wallet"
|
|
@@ -54,6 +52,8 @@ module Tapyrus
|
|
|
54
52
|
autoload :BIP175, "tapyrus/bip175"
|
|
55
53
|
autoload :Contract, "tapyrus/contract"
|
|
56
54
|
autoload :TIP0137, "tapyrus/tip0137"
|
|
55
|
+
autoload :TIP0020, "tapyrus/tip0020"
|
|
56
|
+
autoload :PSTT, "tapyrus/pstt"
|
|
57
57
|
autoload :JWS, "tapyrus/jws"
|
|
58
58
|
|
|
59
59
|
require_relative "tapyrus/constants"
|
data/tapyrusrb.gemspec
CHANGED
|
@@ -21,17 +21,13 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.require_paths = ["lib"]
|
|
22
22
|
|
|
23
23
|
spec.add_runtime_dependency "ecdsa"
|
|
24
|
-
spec.add_runtime_dependency "eventmachine"
|
|
25
24
|
spec.add_runtime_dependency "murmurhash3"
|
|
26
25
|
spec.add_runtime_dependency "daemon-spawn"
|
|
27
|
-
spec.add_runtime_dependency "thor"
|
|
28
26
|
spec.add_runtime_dependency "ffi"
|
|
29
27
|
spec.add_runtime_dependency "leb128", "~> 1.0.0"
|
|
30
|
-
spec.add_runtime_dependency "eventmachine_httpserver"
|
|
31
28
|
spec.add_runtime_dependency "iniparse"
|
|
32
29
|
spec.add_runtime_dependency "siphash"
|
|
33
30
|
spec.add_runtime_dependency "activesupport", ">= 5.2.3"
|
|
34
|
-
spec.add_runtime_dependency "json_pure", ">= 2.6.3", "< 2.8.0"
|
|
35
31
|
spec.add_runtime_dependency "terminal-table", "~> 3.0.2"
|
|
36
32
|
spec.add_runtime_dependency "jwt"
|
|
37
33
|
spec.add_runtime_dependency "observer"
|