tapyrus 0.2.3 → 0.2.8
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 +2 -3
- data/lib/schnorr/signature.rb +3 -6
- data/lib/tapyrus.rb +7 -30
- 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 +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 +17 -18
- 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 +20 -31
- data/.travis.yml +0 -12
@@ -1,9 +1,7 @@
|
|
1
1
|
module Tapyrus
|
2
2
|
module RPC
|
3
|
-
|
4
3
|
# RPC server's request handler.
|
5
4
|
module RequestHandler
|
6
|
-
|
7
5
|
# Returns an object containing various state info regarding blockchain processing.
|
8
6
|
def getblockchaininfo
|
9
7
|
h = {}
|
@@ -28,19 +26,19 @@ module Tapyrus
|
|
28
26
|
raise ArgumentError.new('Block not found') unless entry
|
29
27
|
if verbose
|
30
28
|
{
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
29
|
+
hash: block_id,
|
30
|
+
height: entry.height,
|
31
|
+
features: entry.header.features,
|
32
|
+
featuresHex: entry.header.features.to_even_length_hex.ljust(8, '0'),
|
33
|
+
merkleroot: entry.header.merkle_root.rhex,
|
34
|
+
immutablemerkleroot: entry.header.im_merkle_root.rhex,
|
35
|
+
time: entry.header.time,
|
36
|
+
mediantime: node.chain.mtp(block_hash),
|
37
|
+
xfield_type: entry.header.x_field_type,
|
38
|
+
xfield: entry.header.x_field,
|
39
|
+
proof: entry.header.proof,
|
40
|
+
previousblockhash: entry.prev_hash.rhex,
|
41
|
+
nextblockhash: node.chain.next_hash(block_hash).rhex
|
44
42
|
}
|
45
43
|
else
|
46
44
|
entry.header.to_hex
|
@@ -49,34 +47,38 @@ module Tapyrus
|
|
49
47
|
|
50
48
|
# Returns connected peer information.
|
51
49
|
def getpeerinfo
|
52
|
-
node
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
50
|
+
node
|
51
|
+
.pool
|
52
|
+
.peers
|
53
|
+
.map do |peer|
|
54
|
+
local_addr = "#{peer.remote_version.remote_addr.ip}:18333"
|
55
|
+
{
|
56
|
+
id: peer.id,
|
57
|
+
addr: "#{peer.host}:#{peer.port}",
|
58
|
+
addrlocal: local_addr,
|
59
|
+
services: peer.remote_version.services.to_even_length_hex.rjust(16, '0'),
|
60
|
+
relaytxes: peer.remote_version.relay,
|
61
|
+
lastsend: peer.last_send,
|
62
|
+
lastrecv: peer.last_recv,
|
63
|
+
bytessent: peer.bytes_sent,
|
64
|
+
bytesrecv: peer.bytes_recv,
|
65
|
+
conntime: peer.conn_time,
|
66
|
+
pingtime: peer.ping_time,
|
67
|
+
minping: peer.min_ping,
|
68
|
+
version: peer.remote_version.version,
|
69
|
+
subver: peer.remote_version.user_agent,
|
70
|
+
inbound: !peer.outbound?,
|
71
|
+
startingheight: peer.remote_version.start_height,
|
72
|
+
best_hash: peer.best_hash,
|
73
|
+
best_height: peer.best_height
|
74
|
+
}
|
75
|
+
end
|
75
76
|
end
|
76
77
|
|
77
78
|
# broadcast transaction
|
78
79
|
def sendrawtransaction(hex_tx)
|
79
80
|
tx = Tapyrus::Tx.parse_from_payload(hex_tx.htb)
|
81
|
+
|
80
82
|
# TODO check wether tx is valid
|
81
83
|
node.broadcast(tx)
|
82
84
|
tx.txid
|
@@ -110,7 +112,7 @@ module Tapyrus
|
|
110
112
|
def createwallet(wallet_id = 1, wallet_path_prefix = Tapyrus::Wallet::Base.default_path_prefix)
|
111
113
|
wallet = Tapyrus::Wallet::Base.create(wallet_id, wallet_path_prefix)
|
112
114
|
node.wallet = wallet unless node.wallet
|
113
|
-
{wallet_id: wallet.wallet_id, mnemonic: wallet.master_key.mnemonic}
|
115
|
+
{ wallet_id: wallet.wallet_id, mnemonic: wallet.master_key.mnemonic }
|
114
116
|
end
|
115
117
|
|
116
118
|
# get wallet list.
|
@@ -127,9 +129,7 @@ module Tapyrus
|
|
127
129
|
def listaccounts
|
128
130
|
return {} unless node.wallet
|
129
131
|
accounts = {}
|
130
|
-
node.wallet.accounts.each
|
131
|
-
accounts[a.name] = node.wallet.get_balance(a)
|
132
|
-
end
|
132
|
+
node.wallet.accounts.each { |a| accounts[a.name] = node.wallet.get_balance(a) }
|
133
133
|
accounts
|
134
134
|
end
|
135
135
|
|
@@ -144,8 +144,6 @@ module Tapyrus
|
|
144
144
|
def getnewaddress(account_name)
|
145
145
|
node.wallet.generate_new_address(account_name)
|
146
146
|
end
|
147
|
-
|
148
147
|
end
|
149
|
-
|
150
148
|
end
|
151
149
|
end
|
@@ -3,6 +3,37 @@ require 'json/pure'
|
|
3
3
|
|
4
4
|
module Tapyrus
|
5
5
|
module RPC
|
6
|
+
# Throw when happened anything http's error with connect to server.
|
7
|
+
#
|
8
|
+
# Almost case this exception happened from 401 Unauthorized or 500 Internal Server Error.
|
9
|
+
# And also, throw by cause of other http's errors.
|
10
|
+
#
|
11
|
+
# You can pull RPC error message when happened 500 Internal Server Error, like below:
|
12
|
+
#
|
13
|
+
# rescue Tapyrus::RPC::Error => ex
|
14
|
+
# if ex.message.response_code == 500
|
15
|
+
# puts ex.message[:rpc_error]
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
class Error < StandardError
|
19
|
+
attr_reader :message
|
20
|
+
|
21
|
+
def initialize(response)
|
22
|
+
raise ArgumentError, 'Must set response as cause.' unless response
|
23
|
+
|
24
|
+
# set message from response body or status code.
|
25
|
+
@message = { response_code: response&.code, response_msg: response&.msg }
|
26
|
+
begin
|
27
|
+
@message.merge!({ rpc_error: Tapyrus::RPC.response_body2json(response.body)['error'] })
|
28
|
+
rescue JSON::ParserError => _
|
29
|
+
# if RPC server don't send error message.
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
@message.to_s
|
35
|
+
end
|
36
|
+
end
|
6
37
|
|
7
38
|
# Client implementation for RPC to Tapyrus Core.
|
8
39
|
#
|
@@ -16,25 +47,21 @@ module Tapyrus
|
|
16
47
|
# client.getblockchaininfo
|
17
48
|
#
|
18
49
|
class TapyrusCoreClient
|
19
|
-
|
20
50
|
attr_reader :config
|
21
51
|
|
22
52
|
# @param [Hash] config a configuration required to connect to Bitcoin Core.
|
23
53
|
def initialize(config)
|
24
54
|
@config = config
|
25
55
|
|
26
|
-
commands =
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
TapyrusCoreClient.class_eval do
|
33
|
-
commands.each do |command|
|
34
|
-
define_method(command) do |*params|
|
35
|
-
request(command, *params)
|
56
|
+
commands =
|
57
|
+
request(:help)
|
58
|
+
.split("\n")
|
59
|
+
.inject([]) do |memo_ary, line|
|
60
|
+
memo_ary << line.split(' ').first.to_sym if !line.empty? && !line.start_with?('==')
|
61
|
+
memo_ary
|
36
62
|
end
|
37
|
-
|
63
|
+
TapyrusCoreClient.class_eval do
|
64
|
+
commands.each { |command| define_method(command) { |*params| request(command, *params) } }
|
38
65
|
end
|
39
66
|
end
|
40
67
|
|
@@ -42,33 +69,34 @@ module Tapyrus
|
|
42
69
|
|
43
70
|
def server_url
|
44
71
|
url = "#{config[:schema]}://#{config[:user]}:#{config[:password]}@#{config[:host]}:#{config[:port]}"
|
45
|
-
if !config[:wallet].nil? && !config[:wallet].empty?
|
46
|
-
url += "/wallet/#{config[:wallet]}"
|
47
|
-
end
|
72
|
+
url += "/wallet/#{config[:wallet]}" if !config[:wallet].nil? && !config[:wallet].empty?
|
48
73
|
url
|
49
74
|
end
|
50
75
|
|
51
76
|
def request(command, *params)
|
52
|
-
data = {
|
53
|
-
:method => command,
|
54
|
-
:params => params,
|
55
|
-
:id => 'jsonrpc'
|
56
|
-
}
|
77
|
+
data = { method: command, params: params, id: 'jsonrpc' }
|
57
78
|
uri = URI.parse(server_url)
|
58
79
|
http = Net::HTTP.new(uri.hostname, uri.port)
|
59
|
-
http.use_ssl = uri.scheme ===
|
80
|
+
http.use_ssl = uri.scheme === 'https'
|
60
81
|
request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path)
|
61
82
|
request.basic_auth(uri.user, uri.password)
|
62
83
|
request.content_type = 'application/json'
|
63
84
|
request.body = data.to_json
|
64
85
|
response = http.request(request)
|
65
|
-
|
66
|
-
response = Tapyrus::
|
67
|
-
raise response['error'].to_json if response['error']
|
86
|
+
raise Error.new(response) unless response.is_a? Net::HTTPOK
|
87
|
+
response = Tapyrus::RPC.response_body2json(response.body)
|
68
88
|
response['result']
|
69
89
|
end
|
90
|
+
end
|
70
91
|
|
92
|
+
def response_body2json(body)
|
93
|
+
Tapyrus::Ext::JsonParser.new(
|
94
|
+
body.gsub(/\\u([\da-fA-F]{4})/) do
|
95
|
+
[$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8')
|
96
|
+
end
|
97
|
+
).parse
|
71
98
|
end
|
72
99
|
|
100
|
+
module_function :response_body2json
|
73
101
|
end
|
74
|
-
end
|
102
|
+
end
|
data/lib/tapyrus/script/color.rb
CHANGED
@@ -24,6 +24,11 @@ module Tapyrus
|
|
24
24
|
new(TokenTypes::NFT, Tapyrus.sha256(out_point.to_payload))
|
25
25
|
end
|
26
26
|
|
27
|
+
# Return ColorIdentifier for native token(i.e TPC)
|
28
|
+
def self.default
|
29
|
+
new(TokenTypes::NONE, '0000000000000000000000000000000000000000000000000000000000000000'.htb)
|
30
|
+
end
|
31
|
+
|
27
32
|
def to_payload
|
28
33
|
[type, payload].pack('Ca*')
|
29
34
|
end
|
@@ -43,6 +48,19 @@ module Tapyrus
|
|
43
48
|
true
|
44
49
|
end
|
45
50
|
|
51
|
+
def hash
|
52
|
+
to_payload.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def eql?(other)
|
56
|
+
to_payload.eql?(other.to_payload)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return true if the coin is native token(i.e TPC), otherwise false
|
60
|
+
def default?
|
61
|
+
self == ColorIdentifier.default
|
62
|
+
end
|
63
|
+
|
46
64
|
private
|
47
65
|
|
48
66
|
def initialize(type, payload)
|
@@ -53,11 +71,11 @@ module Tapyrus
|
|
53
71
|
|
54
72
|
module ColoredOutput
|
55
73
|
def colored?
|
56
|
-
script_pubkey.
|
74
|
+
script_pubkey.colored?
|
57
75
|
end
|
58
76
|
|
59
77
|
def color_id
|
60
|
-
@color_id ||=
|
78
|
+
@color_id ||= script_pubkey.color_id
|
61
79
|
end
|
62
80
|
|
63
81
|
def reissuable?
|
@@ -1,11 +1,10 @@
|
|
1
1
|
module Tapyrus
|
2
|
-
|
3
2
|
# utility for multisig
|
4
3
|
module Multisig
|
5
4
|
include Tapyrus::Opcodes
|
6
5
|
|
7
6
|
def self.prefix
|
8
|
-
[OP_0].pack(
|
7
|
+
[OP_0].pack('C*')
|
9
8
|
end
|
10
9
|
|
11
10
|
# generate input script sig spending a multisig output script.
|
@@ -23,7 +22,7 @@ module Tapyrus
|
|
23
22
|
# multiple parties. Signatures must be in the same order as the
|
24
23
|
# pubkeys in the output script being redeemed.
|
25
24
|
def self.add_sig_to_multisig_script_sig(sig_to_add, script_sig, hash_type = SIGHASH_TYPE[:all])
|
26
|
-
signature = sig_to_add + [hash_type].pack(
|
25
|
+
signature = sig_to_add + [hash_type].pack('C*')
|
27
26
|
offset = script_sig.empty? ? 0 : 1
|
28
27
|
script_sig.insert(offset, Tapyrus::Script.pack_pushdata(signature))
|
29
28
|
end
|
@@ -49,17 +48,19 @@ module Tapyrus
|
|
49
48
|
pubkeys = redeem_script.get_multisig_pubkeys
|
50
49
|
|
51
50
|
# find the pubkey for each signature by trying to verify it
|
52
|
-
sigs =
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
sigs =
|
52
|
+
Hash[
|
53
|
+
script.chunks[1...-1].map.with_index do |sig, idx|
|
54
|
+
sig = sig.pushed_data
|
55
|
+
pubkey =
|
56
|
+
pubkeys.map { |key| Tapyrus::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil }.compact.first
|
57
|
+
raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
|
58
|
+
[pubkey, sig]
|
59
|
+
end
|
60
|
+
]
|
60
61
|
|
61
62
|
prefix + pubkeys.map { |k| sigs[k] ? Tapyrus::Script.pack_pushdata(sigs[k]) : nil }.join +
|
62
63
|
Tapyrus::Script.pack_pushdata(script.chunks[-1].pushed_data)
|
63
64
|
end
|
64
65
|
end
|
65
|
-
end
|
66
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
3
3
|
|
4
4
|
module Tapyrus
|
5
|
-
|
6
5
|
# tapyrus script
|
7
6
|
class Script
|
8
7
|
include Tapyrus::HexConverter
|
@@ -75,9 +74,19 @@ module Tapyrus
|
|
75
74
|
end
|
76
75
|
end
|
77
76
|
|
77
|
+
# Remove color identifier from cp2pkh or cp2sh
|
78
|
+
# @param [ColorIdentifier] color identifier
|
79
|
+
# @return [Script] P2PKH or P2SH script
|
80
|
+
# @raise [RuntimeError] if script is neither cp2pkh nor cp2sh
|
81
|
+
def remove_color
|
82
|
+
raise RuntimeError, 'Only cp2pkh and cp2sh can remove color' unless cp2pkh? or cp2sh?
|
83
|
+
|
84
|
+
Tapyrus::Script.new.tap { |s| s.chunks = self.chunks[2..-1] }
|
85
|
+
end
|
86
|
+
|
78
87
|
def get_multisig_pubkeys
|
79
88
|
num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
|
80
|
-
(1..num).map{ |i| chunks[i].pushed_data }
|
89
|
+
(1..num).map { |i| chunks[i].pushed_data }
|
81
90
|
end
|
82
91
|
|
83
92
|
# generate m of n multisig script
|
@@ -92,14 +101,16 @@ module Tapyrus
|
|
92
101
|
# generate script from string.
|
93
102
|
def self.from_string(string)
|
94
103
|
script = new
|
95
|
-
string
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
104
|
+
string
|
105
|
+
.split(' ')
|
106
|
+
.each do |v|
|
107
|
+
opcode = Opcodes.name_to_opcode(v)
|
108
|
+
if opcode
|
109
|
+
script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
|
110
|
+
else
|
111
|
+
script << (v =~ /^[0-9]+$/ ? v.to_i : v)
|
112
|
+
end
|
101
113
|
end
|
102
|
-
end
|
103
114
|
script
|
104
115
|
end
|
105
116
|
|
@@ -118,6 +129,12 @@ module Tapyrus
|
|
118
129
|
Tapyrus::Script.to_p2pkh(hex)
|
119
130
|
when Tapyrus.chain_params.p2sh_version
|
120
131
|
Tapyrus::Script.to_p2sh(hex)
|
132
|
+
when Tapyrus.chain_params.cp2pkh_version
|
133
|
+
color = Tapyrus::Color::ColorIdentifier.parse_from_payload(hex[0..65].htb)
|
134
|
+
Tapyrus::Script.to_cp2pkh(color, hex[66..-1])
|
135
|
+
when Tapyrus.chain_params.cp2sh_version
|
136
|
+
color = Tapyrus::Color::ColorIdentifier.parse_from_payload(hex[0..65].htb)
|
137
|
+
Tapyrus::Script.to_cp2sh(color, hex[66..-1])
|
121
138
|
else
|
122
139
|
throw e
|
123
140
|
end
|
@@ -132,19 +149,20 @@ module Tapyrus
|
|
132
149
|
if opcode.pushdata?
|
133
150
|
pushcode = opcode.ord
|
134
151
|
packed_size = nil
|
135
|
-
len =
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
152
|
+
len =
|
153
|
+
case pushcode
|
154
|
+
when OP_PUSHDATA1
|
155
|
+
packed_size = buf.read(1)
|
156
|
+
packed_size.unpack('C').first
|
157
|
+
when OP_PUSHDATA2
|
158
|
+
packed_size = buf.read(2)
|
159
|
+
packed_size.unpack('v').first
|
160
|
+
when OP_PUSHDATA4
|
161
|
+
packed_size = buf.read(4)
|
162
|
+
packed_size.unpack('V').first
|
163
|
+
else
|
164
|
+
pushcode if pushcode < OP_PUSHDATA1
|
165
|
+
end
|
148
166
|
if len
|
149
167
|
s.chunks << [len].pack('C') if buf.eof?
|
150
168
|
unless buf.eof?
|
@@ -176,7 +194,7 @@ module Tapyrus
|
|
176
194
|
return [p2sh_addr] if p2sh?
|
177
195
|
return [cp2pkh_addr] if cp2pkh?
|
178
196
|
return [cp2sh_addr] if cp2sh?
|
179
|
-
return get_multisig_pubkeys.map{|pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
|
197
|
+
return get_multisig_pubkeys.map { |pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh } if multisig?
|
180
198
|
[]
|
181
199
|
end
|
182
200
|
|
@@ -188,8 +206,8 @@ module Tapyrus
|
|
188
206
|
# whether this script is a P2PKH format script.
|
189
207
|
def p2pkh?
|
190
208
|
return false unless chunks.size == 5
|
191
|
-
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
|
192
|
-
|
209
|
+
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] == (chunks[0..1] + chunks[3..4]).map(&:ord) &&
|
210
|
+
chunks[2].bytesize == 21
|
193
211
|
end
|
194
212
|
|
195
213
|
def p2sh?
|
@@ -210,19 +228,22 @@ module Tapyrus
|
|
210
228
|
end
|
211
229
|
|
212
230
|
def standard_op_return?
|
213
|
-
op_return? && size <= MAX_OP_RETURN_RELAY &&
|
214
|
-
(chunks.size == 1 || chunks[1].opcode <= OP_16)
|
231
|
+
op_return? && size <= MAX_OP_RETURN_RELAY && (chunks.size == 1 || chunks[1].opcode <= OP_16)
|
215
232
|
end
|
216
233
|
|
234
|
+
# Return whether this script is a CP2PKH format script or not.
|
235
|
+
# @return [Boolean] true if this script is cp2pkh, otherwise false.
|
217
236
|
def cp2pkh?
|
218
237
|
return false unless chunks.size == 7
|
219
238
|
return false unless chunks[0].bytesize == 34
|
220
239
|
return false unless Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)&.valid?
|
221
240
|
return false unless chunks[1].ord == OP_COLOR
|
222
|
-
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
|
223
|
-
|
241
|
+
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] == (chunks[2..3] + chunks[5..6]).map(&:ord) &&
|
242
|
+
chunks[4].bytesize == 21
|
224
243
|
end
|
225
244
|
|
245
|
+
# Return whether this script is a CP2SH format script or not.
|
246
|
+
# @return [Boolean] true if this script is cp2pkh, otherwise false.
|
226
247
|
def cp2sh?
|
227
248
|
return false unless chunks.size == 5
|
228
249
|
return false unless chunks[0].bytesize == 34
|
@@ -231,6 +252,20 @@ module Tapyrus
|
|
231
252
|
OP_HASH160 == chunks[2].ord && OP_EQUAL == chunks[4].ord && chunks[3].bytesize == 21
|
232
253
|
end
|
233
254
|
|
255
|
+
# Return whether this script represents colored coin.
|
256
|
+
# @return [Boolean] true if this script is colored, otherwise false.
|
257
|
+
def colored?
|
258
|
+
cp2pkh? || cp2sh?
|
259
|
+
end
|
260
|
+
|
261
|
+
# Return color identifier for this script.
|
262
|
+
# @return [ColorIdentifer] color identifier for this script if this script is colored. return nil if this script is not colored.
|
263
|
+
def color_id
|
264
|
+
return nil unless colored?
|
265
|
+
|
266
|
+
Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)
|
267
|
+
end
|
268
|
+
|
234
269
|
def op_return_data
|
235
270
|
return nil unless op_return?
|
236
271
|
return nil if chunks.size == 1
|
@@ -239,24 +274,23 @@ module Tapyrus
|
|
239
274
|
|
240
275
|
# whether data push only script which dose not include other opcode
|
241
276
|
def push_only?
|
242
|
-
chunks.each
|
243
|
-
return false if !c.opcode.nil? && c.opcode > OP_16
|
244
|
-
end
|
277
|
+
chunks.each { |c| return false if !c.opcode.nil? && c.opcode > OP_16 }
|
245
278
|
true
|
246
279
|
end
|
247
280
|
|
248
281
|
# get public keys in the stack.
|
249
282
|
# @return[Array[String]] an array of the pubkeys with hex format.
|
250
283
|
def get_pubkeys
|
251
|
-
chunks.select
|
284
|
+
chunks.select do |c|
|
285
|
+
c.pushdata? && [33, 65].include?(c.pushed_data.bytesize) &&
|
286
|
+
[2, 3, 4, 6, 7].include?(c.pushed_data[0].bth.to_i(16))
|
287
|
+
end.map { |c| c.pushed_data.bth }
|
252
288
|
end
|
253
289
|
|
254
290
|
# returns the self payload. ScriptInterpreter does not use this.
|
255
291
|
def to_script_code(skip_separator_index = 0)
|
256
292
|
payload = to_payload
|
257
|
-
if skip_separator_index > 0
|
258
|
-
payload = subscript_codeseparator(skip_separator_index)
|
259
|
-
end
|
293
|
+
payload = subscript_codeseparator(skip_separator_index) if skip_separator_index > 0
|
260
294
|
Tapyrus.pack_var_string(payload)
|
261
295
|
end
|
262
296
|
|
@@ -267,7 +301,7 @@ module Tapyrus
|
|
267
301
|
elsif obj.is_a?(String)
|
268
302
|
append_data(obj)
|
269
303
|
elsif obj.is_a?(Array)
|
270
|
-
obj.each { |o| self.<< o}
|
304
|
+
obj.each { |o| self.<< o }
|
271
305
|
self
|
272
306
|
end
|
273
307
|
end
|
@@ -303,18 +337,19 @@ module Tapyrus
|
|
303
337
|
|
304
338
|
# Check the item is in the chunk of the script.
|
305
339
|
def include?(item)
|
306
|
-
chunk_item =
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
340
|
+
chunk_item =
|
341
|
+
if item.is_a?(Integer)
|
342
|
+
item.chr
|
343
|
+
elsif item.is_a?(String)
|
344
|
+
data = Encoding::ASCII_8BIT == item.encoding ? item : item.htb
|
345
|
+
Tapyrus::Script.pack_pushdata(data)
|
346
|
+
end
|
312
347
|
return false unless chunk_item
|
313
348
|
chunks.include?(chunk_item)
|
314
349
|
end
|
315
350
|
|
316
351
|
def to_s
|
317
|
-
chunks.map
|
352
|
+
chunks.map do |c|
|
318
353
|
case c
|
319
354
|
when Integer
|
320
355
|
opcode_to_name(c)
|
@@ -337,7 +372,7 @@ module Tapyrus
|
|
337
372
|
opcode ? opcode : 'OP_UNKNOWN [error]'
|
338
373
|
end
|
339
374
|
end
|
340
|
-
|
375
|
+
end.join(' ')
|
341
376
|
end
|
342
377
|
|
343
378
|
# generate sha-256 hash for payload
|
@@ -395,17 +430,18 @@ module Tapyrus
|
|
395
430
|
# binary +data+ convert pushdata which contains data length and append PUSHDATA opcode if necessary.
|
396
431
|
def self.pack_pushdata(data)
|
397
432
|
size = data.bytesize
|
398
|
-
header =
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
433
|
+
header =
|
434
|
+
if size < OP_PUSHDATA1
|
435
|
+
[size].pack('C')
|
436
|
+
elsif size < 0xff
|
437
|
+
[OP_PUSHDATA1, size].pack('CC')
|
438
|
+
elsif size < 0xffff
|
439
|
+
[OP_PUSHDATA2, size].pack('Cv')
|
440
|
+
elsif size < 0xffffffff
|
441
|
+
[OP_PUSHDATA4, size].pack('CV')
|
442
|
+
else
|
443
|
+
raise ArgumentError, 'data size is too big.'
|
444
|
+
end
|
409
445
|
header + data
|
410
446
|
end
|
411
447
|
|
@@ -429,8 +465,12 @@ module Tapyrus
|
|
429
465
|
if chunk == sub_chunk
|
430
466
|
buf << chunk
|
431
467
|
i += 1
|
432
|
-
(
|
433
|
-
|
468
|
+
(
|
469
|
+
i = 0
|
470
|
+
buf.clear
|
471
|
+
) if i == subscript.chunks.size # matched the whole subscript
|
472
|
+
else
|
473
|
+
# matched the part of head
|
434
474
|
i = 0
|
435
475
|
tmp = chunk.dup
|
436
476
|
tmp.slice!(sub_chunk)
|
@@ -452,7 +492,7 @@ module Tapyrus
|
|
452
492
|
|
453
493
|
# remove all occurences of opcode. Typically it's OP_CODESEPARATOR.
|
454
494
|
def delete_opcode(opcode)
|
455
|
-
@chunks = chunks.select{|chunk| chunk.ord != opcode}
|
495
|
+
@chunks = chunks.select { |chunk| chunk.ord != opcode }
|
456
496
|
self
|
457
497
|
end
|
458
498
|
|
@@ -460,12 +500,10 @@ module Tapyrus
|
|
460
500
|
def subscript_codeseparator(separator_index)
|
461
501
|
buf = []
|
462
502
|
process_separator_index = 0
|
463
|
-
chunks.each
|
503
|
+
chunks.each do |chunk|
|
464
504
|
buf << chunk if process_separator_index == separator_index
|
465
|
-
if chunk.ord == OP_CODESEPARATOR && process_separator_index < separator_index
|
466
|
-
|
467
|
-
end
|
468
|
-
}
|
505
|
+
process_separator_index += 1 if chunk.ord == OP_CODESEPARATOR && process_separator_index < separator_index
|
506
|
+
end
|
469
507
|
buf.join
|
470
508
|
end
|
471
509
|
|
@@ -482,10 +520,10 @@ module Tapyrus
|
|
482
520
|
end
|
483
521
|
|
484
522
|
def to_h
|
485
|
-
h = {asm: to_s, hex: to_hex, type: type}
|
523
|
+
h = { asm: to_s, hex: to_hex, type: type }
|
486
524
|
addrs = addresses
|
487
525
|
unless addrs.empty?
|
488
|
-
h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
|
526
|
+
h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) : addrs.size
|
489
527
|
h[:addresses] = addrs
|
490
528
|
end
|
491
529
|
h
|
@@ -532,5 +570,4 @@ module Tapyrus
|
|
532
570
|
Tapyrus.encode_base58_address(color_id + hash160, Tapyrus.chain_params.cp2sh_version)
|
533
571
|
end
|
534
572
|
end
|
535
|
-
|
536
573
|
end
|