tapyrus 0.3.4 → 0.3.5
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 +1 -1
- data/{.prettierrc.yaml → .prettierrc.yml} +0 -1
- data/.ruby-version +1 -1
- data/Gemfile +2 -2
- data/README.md +1 -1
- data/Rakefile +2 -2
- data/exe/tapyrus-script-debugger +5 -5
- data/exe/tapyrusrb-cli +1 -1
- data/exe/tapyrusrbd +18 -17
- data/lib/openassets/payload.rb +2 -2
- data/lib/openassets/util.rb +2 -2
- data/lib/openassets.rb +4 -4
- data/lib/schnorr/sign_to_contract.rb +4 -4
- data/lib/schnorr/signature.rb +5 -5
- data/lib/schnorr.rb +14 -14
- data/lib/tapyrus/base58.rb +8 -8
- data/lib/tapyrus/bip175.rb +5 -5
- data/lib/tapyrus/block_header.rb +2 -2
- data/lib/tapyrus/bloom_filter.rb +1 -1
- data/lib/tapyrus/chain_params.rb +5 -5
- data/lib/tapyrus/constants.rb +1 -1
- data/lib/tapyrus/errors.rb +9 -9
- data/lib/tapyrus/ext/ecdsa.rb +4 -4
- data/lib/tapyrus/ext/json_parser.rb +1 -1
- data/lib/tapyrus/ext.rb +3 -3
- data/lib/tapyrus/ext_key.rb +21 -21
- data/lib/tapyrus/jws.rb +76 -0
- data/lib/tapyrus/key.rb +48 -20
- data/lib/tapyrus/key_path.rb +3 -3
- data/lib/tapyrus/logger.rb +4 -11
- data/lib/tapyrus/merkle_tree.rb +1 -1
- data/lib/tapyrus/message/addr.rb +2 -2
- data/lib/tapyrus/message/base.rb +2 -2
- data/lib/tapyrus/message/block.rb +1 -1
- data/lib/tapyrus/message/block_txn.rb +1 -1
- data/lib/tapyrus/message/cmpct_block.rb +1 -1
- data/lib/tapyrus/message/fee_filter.rb +3 -3
- data/lib/tapyrus/message/filter_add.rb +1 -1
- data/lib/tapyrus/message/filter_clear.rb +2 -2
- data/lib/tapyrus/message/filter_load.rb +7 -7
- data/lib/tapyrus/message/get_addr.rb +2 -2
- data/lib/tapyrus/message/get_block_txn.rb +1 -1
- data/lib/tapyrus/message/get_blocks.rb +1 -1
- data/lib/tapyrus/message/get_data.rb +1 -1
- data/lib/tapyrus/message/get_headers.rb +1 -1
- data/lib/tapyrus/message/header_and_short_ids.rb +6 -6
- data/lib/tapyrus/message/headers.rb +1 -1
- data/lib/tapyrus/message/headers_parser.rb +2 -2
- data/lib/tapyrus/message/inv.rb +1 -1
- data/lib/tapyrus/message/inventory.rb +3 -3
- data/lib/tapyrus/message/mem_pool.rb +2 -2
- data/lib/tapyrus/message/merkle_block.rb +3 -3
- data/lib/tapyrus/message/network_addr.rb +9 -9
- data/lib/tapyrus/message/not_found.rb +1 -1
- data/lib/tapyrus/message/ping.rb +3 -3
- data/lib/tapyrus/message/pong.rb +3 -3
- data/lib/tapyrus/message/reject.rb +6 -6
- data/lib/tapyrus/message/send_cmpct.rb +4 -4
- data/lib/tapyrus/message/send_headers.rb +2 -2
- data/lib/tapyrus/message/tx.rb +1 -1
- data/lib/tapyrus/message/ver_ack.rb +2 -2
- data/lib/tapyrus/message/version.rb +7 -7
- data/lib/tapyrus/message.rb +37 -37
- data/lib/tapyrus/mnemonic.rb +18 -16
- data/lib/tapyrus/network/connection.rb +2 -2
- data/lib/tapyrus/network/message_handler.rb +11 -11
- data/lib/tapyrus/network/peer.rb +1 -1
- data/lib/tapyrus/network/peer_discovery.rb +1 -1
- data/lib/tapyrus/network/pool.rb +4 -4
- data/lib/tapyrus/network.rb +6 -6
- data/lib/tapyrus/node/cli.rb +34 -34
- data/lib/tapyrus/node/configuration.rb +3 -3
- data/lib/tapyrus/node/spv.rb +2 -2
- data/lib/tapyrus/node.rb +3 -3
- data/lib/tapyrus/opcodes.rb +7 -7
- data/lib/tapyrus/out_point.rb +2 -2
- data/lib/tapyrus/rpc/http_server.rb +6 -6
- data/lib/tapyrus/rpc/request_handler.rb +5 -5
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +10 -10
- data/lib/tapyrus/rpc.rb +4 -4
- data/lib/tapyrus/script/color.rb +3 -3
- data/lib/tapyrus/script/debugger.rb +9 -9
- data/lib/tapyrus/script/multisig.rb +2 -2
- data/lib/tapyrus/script/script.rb +28 -28
- data/lib/tapyrus/script/script_error.rb +42 -42
- data/lib/tapyrus/script/script_interpreter.rb +9 -9
- data/lib/tapyrus/script/tx_checker.rb +2 -2
- data/lib/tapyrus/secp256k1/native.rb +23 -23
- data/lib/tapyrus/secp256k1/rfc6979.rb +7 -7
- data/lib/tapyrus/secp256k1/ruby.rb +2 -2
- data/lib/tapyrus/secp256k1.rb +3 -3
- data/lib/tapyrus/slip39/share.rb +7 -7
- data/lib/tapyrus/slip39/sss.rb +24 -24
- data/lib/tapyrus/slip39.rb +514 -37
- data/lib/tapyrus/store/db/level_db.rb +2 -2
- data/lib/tapyrus/store/db.rb +1 -1
- data/lib/tapyrus/store/spv_chain.rb +7 -7
- data/lib/tapyrus/store.rb +3 -3
- data/lib/tapyrus/tip0137.rb +201 -0
- data/lib/tapyrus/tx.rb +12 -12
- data/lib/tapyrus/tx_builder.rb +3 -3
- data/lib/tapyrus/tx_in.rb +3 -3
- data/lib/tapyrus/tx_out.rb +3 -3
- data/lib/tapyrus/util.rb +21 -21
- data/lib/tapyrus/validation.rb +9 -9
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +12 -12
- data/lib/tapyrus/wallet/base.rb +9 -8
- data/lib/tapyrus/wallet/db.rb +11 -11
- data/lib/tapyrus/wallet/master_key.rb +13 -13
- data/lib/tapyrus/wallet.rb +4 -4
- data/lib/tapyrus.rb +62 -59
- data/tapyrusrb.gemspec +33 -31
- metadata +30 -14
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "leveldb-native"
|
2
2
|
|
3
3
|
module Tapyrus
|
4
4
|
module Store
|
@@ -134,7 +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
|
-
raise
|
137
|
+
raise "block height is small than current best block." unless tip_block.height + 1 == entry.height
|
138
138
|
end
|
139
139
|
db.put(KEY_PREFIX[:best], entry.block_hash)
|
140
140
|
db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.block_hash)
|
data/lib/tapyrus/store/db.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module Tapyrus
|
2
2
|
module Store
|
3
3
|
KEY_PREFIX = {
|
4
|
-
entry:
|
5
|
-
height:
|
6
|
-
best:
|
7
|
-
next:
|
8
|
-
agg_pubkey:
|
9
|
-
latest_agg_pubkey:
|
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.
|
10
10
|
}
|
11
11
|
|
12
12
|
class SPVChain
|
@@ -17,7 +17,7 @@ module Tapyrus
|
|
17
17
|
# @param[Tapyrus::Store::DB::LevelDB] db
|
18
18
|
# @param[Tapyrus::Block] genesis genesis block
|
19
19
|
def initialize(db = Tapyrus::Store::DB::LevelDB.new, genesis: nil)
|
20
|
-
raise ArgumentError,
|
20
|
+
raise ArgumentError, "genesis block should be specified." unless genesis
|
21
21
|
@db = db # TODO multiple db switch
|
22
22
|
@logger = Tapyrus::Logger.create(:debug)
|
23
23
|
initialize_block(genesis)
|
data/lib/tapyrus/store.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Tapyrus
|
2
2
|
module Store
|
3
|
-
autoload :DB,
|
4
|
-
autoload :SPVChain,
|
5
|
-
autoload :ChainEntry,
|
3
|
+
autoload :DB, "tapyrus/store/db"
|
4
|
+
autoload :SPVChain, "tapyrus/store/spv_chain"
|
5
|
+
autoload :ChainEntry, "tapyrus/store/chain_entry"
|
6
6
|
end
|
7
7
|
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
module TIP0137
|
3
|
+
# @param key [Tapyrus::Key] A private key
|
4
|
+
# @param txid [String] A transaction id string in hexadecimal format (64 characters long)
|
5
|
+
# @param index [Integer] Index of the transaction output, a non-negative integer less than 2^32
|
6
|
+
# @param color_id [Tapyrus::Color::ColorIdentifier] A valid instance of Tapyrus::Color::ColorIdentifier
|
7
|
+
# @param value [Integer] A non-negative integer less than 2^64 representing the value of the transaction output
|
8
|
+
# @param script_pubkey [Tapyrus::Script] A script pubkey in the transaction output
|
9
|
+
# @param address [String] A valid Tapyrus address string for the transaction output
|
10
|
+
# @param message [String] A hexadecimal formatted string
|
11
|
+
# @param client [Tapyrus::RPC::TapyrusCoreClient] The RPC client instance. If the client is specified, verify that the transaction associated with the txid and index exists on the blockchain and that a valid script_pubkey exists in the transaction.
|
12
|
+
# @return [String] Returns the message signed in JWS Format
|
13
|
+
# @raise [ArgumentError] If the txid is not a 64-character hexadecimal string
|
14
|
+
# @raise [ArgumentError] If the index is not a non-negative integer less than 2^32
|
15
|
+
# @raise [ArgumentError] If the color_id is not a valid Tapyrus::Color::ColorIdentifier object
|
16
|
+
# @raise [ArgumentError] If the value is not a non-negative integer less than 2^64
|
17
|
+
# @raise [ArgumentError] If the script_pubkey is not a valid Tapyrus::Script object
|
18
|
+
# @raise [ArgumentError] If the provided Tapyrus address is invalid
|
19
|
+
# @raise [ArgumentError] If the message is not a hexadecimal string
|
20
|
+
# @raise [ArgumentError] If the transaction with the given txid and index is not found in the blockchain
|
21
|
+
# @raise [ArgumentError] If the script and value do not correspond with those in the blockchain
|
22
|
+
# @raise [Tapyrus::RPC::Error] If RPC access fails
|
23
|
+
def sign_message!(
|
24
|
+
key,
|
25
|
+
txid:,
|
26
|
+
index:,
|
27
|
+
value:,
|
28
|
+
script_pubkey:,
|
29
|
+
color_id: nil,
|
30
|
+
address: nil,
|
31
|
+
message: nil,
|
32
|
+
client: nil
|
33
|
+
)
|
34
|
+
validate_payload!(
|
35
|
+
key: key,
|
36
|
+
txid: txid,
|
37
|
+
index: index,
|
38
|
+
color_id: color_id,
|
39
|
+
value: value,
|
40
|
+
script_pubkey: script_pubkey,
|
41
|
+
address: address,
|
42
|
+
message: message
|
43
|
+
)
|
44
|
+
if client
|
45
|
+
validate_on_blockchain!(
|
46
|
+
client: client,
|
47
|
+
txid: txid,
|
48
|
+
index: index,
|
49
|
+
color_id: color_id,
|
50
|
+
value: value,
|
51
|
+
script_pubkey: script_pubkey
|
52
|
+
)
|
53
|
+
end
|
54
|
+
data = {
|
55
|
+
txid: txid,
|
56
|
+
index: index,
|
57
|
+
color_id: color_id&.to_hex,
|
58
|
+
value: value,
|
59
|
+
script_pubkey: script_pubkey&.to_hex,
|
60
|
+
address: address,
|
61
|
+
message: message
|
62
|
+
}
|
63
|
+
Tapyrus::JWS.encode(data, key.priv_key)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @param jws [String] JWS (JSON Web Signature)
|
67
|
+
# @param client [Tapyrus::RPC::TapyrusCoreClient] The RPC client instance. If the client is specified, verify that the transaction associated with the txid and index exists on the blockchain and that a valid script_pubkey exists in the transaction.
|
68
|
+
# @return A decoded JSON Object
|
69
|
+
# @raise [ArgumentError] If the decoded txid is not a 64-character hexadecimal string
|
70
|
+
# @raise [ArgumentError] If the decoded index is not a non-negative integer less than 2^32
|
71
|
+
# @raise [ArgumentError] If the decoded color_id is an invalid Tapyrus::Color::ColorIdentifier object
|
72
|
+
# @raise [ArgumentError] If the decoded value is not a non-negative integer less than 2^64
|
73
|
+
# @raise [ArgumentError] If the decoded script_pubkey is an invalid Tapyrus::Script object
|
74
|
+
# @raise [ArgumentError] If the decoded address is an invalid Tapyrus address
|
75
|
+
# @raise [ArgumentError] If the decoded message is not a hexadecimal string
|
76
|
+
# @raise [ArgumentError] If a transaction is not found with the decoded txid and index in the blockchain
|
77
|
+
# @raise [ArgumentError] If the decoded script and value do not match the ones in the blockchain
|
78
|
+
# @raise [JWT::DecodeError] If JWS decoding fails
|
79
|
+
# @raise [Tapyus::JWS::DecodeError] If the JWK key is invalid
|
80
|
+
# @raise [JWT::VerificationError] If the verification of the signature fails
|
81
|
+
# @raise [Tapyrus::RPC::Error] If RPC access fails
|
82
|
+
def verify_message!(jws, client: nil)
|
83
|
+
Tapyrus::JWS
|
84
|
+
.decode(jws)
|
85
|
+
.tap do |decoded|
|
86
|
+
header = decoded[1]
|
87
|
+
validate_header!(header)
|
88
|
+
jwk = header.dig("jwk", "keys", 0)
|
89
|
+
key = to_tapyrus_key(jwk)
|
90
|
+
payload = decoded[0]
|
91
|
+
color_id =
|
92
|
+
begin
|
93
|
+
Tapyrus::Color::ColorIdentifier.parse_from_payload(payload["color_id"]&.htb) if payload["color_id"]
|
94
|
+
rescue => _e
|
95
|
+
raise ArgumentError, "color_id is invalid"
|
96
|
+
end
|
97
|
+
script_pubkey =
|
98
|
+
begin
|
99
|
+
Tapyrus::Script.parse_from_payload(payload["script_pubkey"]&.htb)
|
100
|
+
rescue => _e
|
101
|
+
raise ArgumentError, "script_pubkey is invalid"
|
102
|
+
end
|
103
|
+
validate_payload!(
|
104
|
+
key: key,
|
105
|
+
txid: payload["txid"],
|
106
|
+
index: payload["index"],
|
107
|
+
color_id: color_id,
|
108
|
+
value: payload["value"],
|
109
|
+
script_pubkey: script_pubkey,
|
110
|
+
address: payload["address"],
|
111
|
+
message: payload["message"]
|
112
|
+
)
|
113
|
+
if client
|
114
|
+
validate_on_blockchain!(
|
115
|
+
client: client,
|
116
|
+
txid: payload["txid"],
|
117
|
+
index: payload["index"],
|
118
|
+
color_id: color_id,
|
119
|
+
value: payload["value"],
|
120
|
+
script_pubkey: script_pubkey
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# @param jws [String] JWS (JSON Web Signature)
|
127
|
+
# @param client [Tapyrus::RPC::TapyrusCoreClient] The RPC client instance
|
128
|
+
# @return [Boolean] true if JWT and decoded object is valid.
|
129
|
+
def verify_message(jws, client: nil)
|
130
|
+
verify_message!(jws, client: client)
|
131
|
+
true
|
132
|
+
rescue ArgumentError, JWT::DecodeError, JWT::VerificationError
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
def validate_header!(header)
|
137
|
+
raise ArgumentError, 'type must be "JWT"' if header["typ"] && header["typ"] != "JWT"
|
138
|
+
raise ArgumentError, 'alg must be "ES256K"' if header["alg"] && header["alg"] != Tapyrus::JWS::ALGO
|
139
|
+
end
|
140
|
+
|
141
|
+
def validate_payload!(key:, txid:, index:, value:, script_pubkey:, color_id: nil, address: nil, message: nil)
|
142
|
+
raise ArgumentError, "txid is invalid" if !txid || !/^[0-9a-fA-F]{64}$/.match(txid)
|
143
|
+
raise ArgumentError, "index is invalid" if !index || !/^\d+$/.match(index.to_s) || index < 0 || index >= 2**32
|
144
|
+
|
145
|
+
raise ArgumentError, "value is invalid" if !value || !/^\d+$/.match(value.to_s) || value < 0 || value >= 2**64
|
146
|
+
if !script_pubkey || !script_pubkey.is_a?(Tapyrus::Script) || !(script_pubkey.p2pkh? || script_pubkey.cp2pkh?)
|
147
|
+
raise ArgumentError,
|
148
|
+
"script_pubkey is invalid. scirpt_pubkey must be a hex string and its type must be p2pkh or cp2pkh"
|
149
|
+
end
|
150
|
+
|
151
|
+
if color_id
|
152
|
+
if !color_id.is_a?(Tapyrus::Color::ColorIdentifier) || !color_id.valid?
|
153
|
+
raise ArgumentError, "color_id is invalid"
|
154
|
+
end
|
155
|
+
|
156
|
+
raise ArgumentError, "color_id should be equal to colorId in scriptPubkey" if color_id != script_pubkey.color_id
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
address && Base58.decode(address)
|
161
|
+
rescue ArgumentError => e
|
162
|
+
raise ArgumentError, "address is invalid"
|
163
|
+
end
|
164
|
+
|
165
|
+
if address && script_pubkey.to_addr != address
|
166
|
+
raise ArgumentError, "address is invalid. An address should be derived from scriptPubkey"
|
167
|
+
end
|
168
|
+
|
169
|
+
if (script_pubkey.p2pkh? && key.to_p2pkh != script_pubkey.to_addr) ||
|
170
|
+
(script_pubkey.cp2pkh? && key.to_p2pkh != script_pubkey.remove_color.to_addr)
|
171
|
+
raise ArgumentError, "key is invalid"
|
172
|
+
end
|
173
|
+
|
174
|
+
if message && !/^([0-9a-fA-F]{2})+$/.match(message)
|
175
|
+
raise ArgumentError, "message is invalid. message must be a hex string"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def validate_on_blockchain!(client: nil, txid: nil, index: nil, color_id: nil, value: nil, script_pubkey: nil)
|
180
|
+
raw_tx = client.getrawtransaction(txid)
|
181
|
+
tx = Tapyrus::Tx.parse_from_payload(raw_tx.htb)
|
182
|
+
output = tx.outputs[index]
|
183
|
+
raise ArgumentError, "output not found in blockchain" unless output
|
184
|
+
if color_id != output.color_id
|
185
|
+
raise ArgumentError, "color_id of transaction in blockchain is not match to one in the signed message"
|
186
|
+
end
|
187
|
+
if value != output.value
|
188
|
+
raise ArgumentError, "value of transaction in blockchain is not match to one in the signed message"
|
189
|
+
end
|
190
|
+
if script_pubkey != output.script_pubkey
|
191
|
+
raise ArgumentError, "script_pubkey of transaction in blockchain is not match to one in the signed message"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_tapyrus_key(jwk)
|
196
|
+
vefiry_key = JWT::JWK.new(jwk).verify_key
|
197
|
+
pubkey = vefiry_key.public_key.to_octet_string(:compressed)
|
198
|
+
Tapyrus::Key.new(pubkey: pubkey.bth)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
data/lib/tapyrus/tx.rb
CHANGED
@@ -29,7 +29,7 @@ module Tapyrus
|
|
29
29
|
def self.parse_from_payload(payload)
|
30
30
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
31
31
|
tx = new
|
32
|
-
tx.features = buf.read(4).unpack(
|
32
|
+
tx.features = buf.read(4).unpack("V").first
|
33
33
|
|
34
34
|
in_count = Tapyrus.unpack_var_int_from_io(buf)
|
35
35
|
|
@@ -38,7 +38,7 @@ module Tapyrus
|
|
38
38
|
out_count = Tapyrus.unpack_var_int_from_io(buf)
|
39
39
|
out_count.times { tx.outputs << TxOut.parse_from_payload(buf) }
|
40
40
|
|
41
|
-
tx.lock_time = buf.read(4).unpack(
|
41
|
+
tx.lock_time = buf.read(4).unpack("V").first
|
42
42
|
|
43
43
|
tx
|
44
44
|
end
|
@@ -52,18 +52,18 @@ module Tapyrus
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def txid
|
55
|
-
buf = [features].pack(
|
55
|
+
buf = [features].pack("V")
|
56
56
|
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map { |i| i.to_payload(use_malfix: true) }.join
|
57
57
|
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
58
|
-
buf << [lock_time].pack(
|
58
|
+
buf << [lock_time].pack("V")
|
59
59
|
Tapyrus.double_sha256(buf).reverse.bth
|
60
60
|
end
|
61
61
|
|
62
62
|
def to_payload
|
63
|
-
buf = [features].pack(
|
63
|
+
buf = [features].pack("V")
|
64
64
|
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
|
65
65
|
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
66
|
-
buf << [lock_time].pack(
|
66
|
+
buf << [lock_time].pack("V")
|
67
67
|
buf
|
68
68
|
end
|
69
69
|
|
@@ -109,9 +109,9 @@ module Tapyrus
|
|
109
109
|
# @param [Tapyrus::Script] output_script script pubkey or script code. if script pubkey is P2SH, set redeem script to this.
|
110
110
|
# @return [String] sighash
|
111
111
|
def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all])
|
112
|
-
raise ArgumentError,
|
113
|
-
raise ArgumentError,
|
114
|
-
raise ArgumentError,
|
112
|
+
raise ArgumentError, "input_index must be specified." unless input_index
|
113
|
+
raise ArgumentError, "does not exist input corresponding to input_index." if input_index >= inputs.size
|
114
|
+
raise ArgumentError, "script_pubkey must be specified." unless output_script
|
115
115
|
sighash_for_legacy(input_index, output_script, hash_type)
|
116
116
|
end
|
117
117
|
|
@@ -167,7 +167,7 @@ module Tapyrus
|
|
167
167
|
|
168
168
|
case hash_type & 0x1f
|
169
169
|
when SIGHASH_TYPE[:none]
|
170
|
-
outs =
|
170
|
+
outs = ""
|
171
171
|
out_size = Tapyrus.pack_var_int(0)
|
172
172
|
when SIGHASH_TYPE[:single]
|
173
173
|
return "\x01".ljust(32, "\x00") if index >= outputs.size
|
@@ -179,12 +179,12 @@ module Tapyrus
|
|
179
179
|
ins = [ins[index]] if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
180
180
|
|
181
181
|
buf = [
|
182
|
-
[features].pack(
|
182
|
+
[features].pack("V"),
|
183
183
|
Tapyrus.pack_var_int(ins.size),
|
184
184
|
ins,
|
185
185
|
out_size,
|
186
186
|
outs,
|
187
|
-
[lock_time, hash_type].pack(
|
187
|
+
[lock_time, hash_type].pack("VV")
|
188
188
|
].join
|
189
189
|
|
190
190
|
Tapyrus.double_sha256(buf)
|
data/lib/tapyrus/tx_builder.rb
CHANGED
@@ -85,12 +85,12 @@ module Tapyrus
|
|
85
85
|
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
86
86
|
|
87
87
|
unless color_id.default?
|
88
|
-
raise ArgumentError,
|
88
|
+
raise ArgumentError, "invalid address" if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
|
89
89
|
script_pubkey = script_pubkey.add_color(color_id)
|
90
90
|
end
|
91
91
|
|
92
92
|
output = Tapyrus::TxOut.new(script_pubkey: script_pubkey, value: value)
|
93
|
-
raise ArgumentError,
|
93
|
+
raise ArgumentError, "The transaction amount is too small" if color_id.default? && output.dust?
|
94
94
|
@outgoings[color_id] ||= 0
|
95
95
|
@outgoings[color_id] += value
|
96
96
|
@outputs << output
|
@@ -119,7 +119,7 @@ module Tapyrus
|
|
119
119
|
# @param address [String] p2pkh or p2sh address.
|
120
120
|
def change_address(address)
|
121
121
|
script_pubkey = Tapyrus::Script.parse_from_addr(address)
|
122
|
-
raise ArgumentError,
|
122
|
+
raise ArgumentError, "invalid address" if !script_pubkey.p2pkh? && !script_pubkey.p2sh?
|
123
123
|
@change_script_pubkey = script_pubkey
|
124
124
|
self
|
125
125
|
end
|
data/lib/tapyrus/tx_in.rb
CHANGED
@@ -30,7 +30,7 @@ module Tapyrus
|
|
30
30
|
def self.parse_from_payload(payload)
|
31
31
|
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
32
32
|
i = new
|
33
|
-
hash, index = buf.read(36).unpack(
|
33
|
+
hash, index = buf.read(36).unpack("a32V")
|
34
34
|
i.out_point = OutPoint.new(hash.bth, index)
|
35
35
|
sig_length = Tapyrus.unpack_var_int_from_io(buf)
|
36
36
|
if sig_length == 0
|
@@ -38,7 +38,7 @@ module Tapyrus
|
|
38
38
|
else
|
39
39
|
i.script_sig = Script.parse_from_payload(buf.read(sig_length))
|
40
40
|
end
|
41
|
-
i.sequence = buf.read(4).unpack(
|
41
|
+
i.sequence = buf.read(4).unpack("V").first
|
42
42
|
i
|
43
43
|
end
|
44
44
|
|
@@ -52,7 +52,7 @@ module Tapyrus
|
|
52
52
|
p << Tapyrus.pack_var_int(script_sig.to_payload.bytesize)
|
53
53
|
p << script_sig.to_payload
|
54
54
|
end
|
55
|
-
p << [sequence].pack(
|
55
|
+
p << [sequence].pack("V")
|
56
56
|
p
|
57
57
|
end
|
58
58
|
|
data/lib/tapyrus/tx_out.rb
CHANGED
@@ -17,18 +17,18 @@ 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(
|
27
|
+
[value].pack("Q") << Tapyrus.pack_var_int(s.length) << s
|
28
28
|
end
|
29
29
|
|
30
30
|
def to_empty_payload
|
31
|
-
|
31
|
+
"ffffffffffffffff00".htb
|
32
32
|
end
|
33
33
|
|
34
34
|
# convert satoshi to btc
|
data/lib/tapyrus/util.rb
CHANGED
@@ -17,13 +17,13 @@ module Tapyrus
|
|
17
17
|
|
18
18
|
def pack_var_int(i)
|
19
19
|
if i < 0xfd
|
20
|
-
[i].pack(
|
20
|
+
[i].pack("C")
|
21
21
|
elsif i <= 0xffff
|
22
|
-
[0xfd, i].pack(
|
22
|
+
[0xfd, i].pack("Cv")
|
23
23
|
elsif i <= 0xffffffff
|
24
|
-
[0xfe, i].pack(
|
24
|
+
[0xfe, i].pack("CV")
|
25
25
|
elsif i <= 0xffffffffffffffff
|
26
|
-
[0xff, i].pack(
|
26
|
+
[0xff, i].pack("CQ")
|
27
27
|
else
|
28
28
|
raise "int(#{i}) too large!"
|
29
29
|
end
|
@@ -31,39 +31,39 @@ module Tapyrus
|
|
31
31
|
|
32
32
|
# @return an integer for a valid payload, otherwise nil
|
33
33
|
def unpack_var_int(payload)
|
34
|
-
case payload.unpack(
|
34
|
+
case payload.unpack("C").first
|
35
35
|
when 0xfd
|
36
|
-
payload.unpack(
|
36
|
+
payload.unpack("xva*")
|
37
37
|
when 0xfe
|
38
|
-
payload.unpack(
|
38
|
+
payload.unpack("xVa*")
|
39
39
|
when 0xff
|
40
|
-
payload.unpack(
|
40
|
+
payload.unpack("xQa*")
|
41
41
|
else
|
42
|
-
payload.unpack(
|
42
|
+
payload.unpack("Ca*")
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
# @return an integer for a valid payload, otherwise nil
|
47
47
|
def unpack_var_int_from_io(buf)
|
48
|
-
uchar = buf.read(1)&.unpack(
|
48
|
+
uchar = buf.read(1)&.unpack("C")&.first
|
49
49
|
case uchar
|
50
50
|
when 0xfd
|
51
|
-
buf.read(2)&.unpack(
|
51
|
+
buf.read(2)&.unpack("v")&.first
|
52
52
|
when 0xfe
|
53
|
-
buf.read(4)&.unpack(
|
53
|
+
buf.read(4)&.unpack("V")&.first
|
54
54
|
when 0xff
|
55
|
-
buf.read(8)&.unpack(
|
55
|
+
buf.read(8)&.unpack("Q")&.first
|
56
56
|
else
|
57
57
|
uchar
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
61
|
def pack_boolean(b)
|
62
|
-
b ? [0x01].pack(
|
62
|
+
b ? [0x01].pack("C") : [0x00].pack("C")
|
63
63
|
end
|
64
64
|
|
65
65
|
def unpack_boolean(payload)
|
66
|
-
data, payload = payload.unpack(
|
66
|
+
data, payload = payload.unpack("Ca*")
|
67
67
|
[(data.zero? ? false : true), payload]
|
68
68
|
end
|
69
69
|
|
@@ -77,7 +77,7 @@ module Tapyrus
|
|
77
77
|
|
78
78
|
# byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
|
79
79
|
def byte_to_bit(byte)
|
80
|
-
byte.unpack(
|
80
|
+
byte.unpack("b*").first
|
81
81
|
end
|
82
82
|
|
83
83
|
# padding zero to the left of binary string until bytesize.
|
@@ -86,7 +86,7 @@ module Tapyrus
|
|
86
86
|
# @return [String] padded binary string.
|
87
87
|
def padding_zero(binary, bytesize)
|
88
88
|
return binary unless binary.bytesize < bytesize
|
89
|
-
(
|
89
|
+
("00" * (bytesize - binary.bytesize)).htb + binary
|
90
90
|
end
|
91
91
|
|
92
92
|
# generate sha256-ripemd160 hash for value
|
@@ -110,16 +110,16 @@ module Tapyrus
|
|
110
110
|
hex = Base58.decode(addr)
|
111
111
|
if hex.size == 50 && calc_checksum(hex[0...-8]) == hex[-8..-1]
|
112
112
|
unless [Tapyrus.chain_params.address_version, Tapyrus.chain_params.p2sh_version].include?(hex[0..1])
|
113
|
-
raise
|
113
|
+
raise "Invalid version bytes."
|
114
114
|
end
|
115
115
|
[hex[2...-8], hex[0..1]]
|
116
116
|
elsif hex.size == 116 && calc_checksum(hex[0...-8]) == hex[-8..-1]
|
117
117
|
unless [Tapyrus.chain_params.cp2pkh_version, Tapyrus.chain_params.cp2sh_version].include?(hex[0..1])
|
118
|
-
raise
|
118
|
+
raise "Invalid version bytes."
|
119
119
|
end
|
120
120
|
[hex[2...-8], hex[0..1]]
|
121
121
|
else
|
122
|
-
raise
|
122
|
+
raise "Invalid address."
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
@@ -127,7 +127,7 @@ module Tapyrus
|
|
127
127
|
double_sha256(hex.htb).bth[0..7]
|
128
128
|
end
|
129
129
|
|
130
|
-
DIGEST_NAME_SHA256 =
|
130
|
+
DIGEST_NAME_SHA256 = "sha256"
|
131
131
|
|
132
132
|
def hmac_sha256(key, data)
|
133
133
|
OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
|
data/lib/tapyrus/validation.rb
CHANGED
@@ -4,42 +4,42 @@ module Tapyrus
|
|
4
4
|
def check_tx(tx, state)
|
5
5
|
# Basic checks that don't depend on any context
|
6
6
|
if tx.inputs.empty?
|
7
|
-
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
7
|
+
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-vin-empty")
|
8
8
|
end
|
9
9
|
|
10
10
|
if tx.outputs.empty?
|
11
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
11
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-vout-empty")
|
12
12
|
end
|
13
13
|
|
14
14
|
# Check for negative or overflow output values
|
15
15
|
amount = 0
|
16
16
|
tx.outputs.each do |o|
|
17
17
|
if o.value < 0
|
18
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
18
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-vout-negative")
|
19
19
|
end
|
20
20
|
if MAX_MONEY < o.value
|
21
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
21
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-vout-toolarge")
|
22
22
|
end
|
23
23
|
amount += o.value
|
24
24
|
if MAX_MONEY < amount
|
25
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
25
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-vout-toolarge")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
# Check for duplicate inputs - note that this check is slow so we skip it in CheckBlock
|
30
30
|
out_points = tx.inputs.map { |i| i.out_point.to_payload }
|
31
31
|
unless out_points.size == out_points.uniq.size
|
32
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
32
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-inputs-duplicate")
|
33
33
|
end
|
34
34
|
|
35
35
|
if tx.coinbase_tx?
|
36
36
|
if tx.inputs[0].out_point.index == 0xffffffff || tx.inputs[0].script_sig.size > 100
|
37
|
-
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
37
|
+
return state.DoS(100, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-cb-length")
|
38
38
|
end
|
39
39
|
else
|
40
40
|
tx.inputs.each do |i|
|
41
41
|
if i.out_point.nil? || !i.out_point.valid?
|
42
|
-
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason:
|
42
|
+
return state.DoS(10, reject_code: Message::Reject::CODE_INVALID, reject_reason: "bad-txns-prevout-null")
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -85,7 +85,7 @@ module Tapyrus
|
|
85
85
|
@corruption_possible = false
|
86
86
|
end
|
87
87
|
|
88
|
-
def DoS(level, ret: false, reject_code: 0, reject_reason:
|
88
|
+
def DoS(level, ret: false, reject_code: 0, reject_reason: "", corruption_in: false, debug_message: "")
|
89
89
|
@reject_code = reject_code
|
90
90
|
@reject_reason = reject_reason
|
91
91
|
@corruption_possible = corruption_in
|
data/lib/tapyrus/version.rb
CHANGED
@@ -15,7 +15,7 @@ module Tapyrus
|
|
15
15
|
attr_accessor :lookahead
|
16
16
|
attr_accessor :wallet
|
17
17
|
|
18
|
-
def initialize(account_key, purpose = PURPOSE_TYPE[:native_segwit], index = 0, name =
|
18
|
+
def initialize(account_key, purpose = PURPOSE_TYPE[:native_segwit], index = 0, name = "")
|
19
19
|
validate_params!(account_key, purpose, index)
|
20
20
|
@purpose = purpose
|
21
21
|
@index = index
|
@@ -31,8 +31,8 @@ module Tapyrus
|
|
31
31
|
account_key = Tapyrus::ExtPubkey.parse_from_payload(buf.read(78))
|
32
32
|
payload = buf.read
|
33
33
|
name, payload = Tapyrus.unpack_var_string(payload)
|
34
|
-
name = name.force_encoding(
|
35
|
-
purpose, index, receive_depth, change_depth, lookahead = payload.unpack(
|
34
|
+
name = name.force_encoding("utf-8")
|
35
|
+
purpose, index, receive_depth, change_depth, lookahead = payload.unpack("I*")
|
36
36
|
a = Account.new(account_key, purpose, index, name)
|
37
37
|
a.receive_depth = receive_depth
|
38
38
|
a.change_depth = change_depth
|
@@ -42,8 +42,8 @@ module Tapyrus
|
|
42
42
|
|
43
43
|
def to_payload
|
44
44
|
payload = account_key.to_payload
|
45
|
-
payload << Tapyrus.pack_var_string(name.unpack(
|
46
|
-
payload << [purpose, index, receive_depth, change_depth, lookahead].pack(
|
45
|
+
payload << Tapyrus.pack_var_string(name.unpack("H*").first.htb)
|
46
|
+
payload << [purpose, index, receive_depth, change_depth, lookahead].pack("I*")
|
47
47
|
payload
|
48
48
|
end
|
49
49
|
|
@@ -98,13 +98,13 @@ module Tapyrus
|
|
98
98
|
def type
|
99
99
|
case purpose
|
100
100
|
when PURPOSE_TYPE[:legacy]
|
101
|
-
|
101
|
+
"pubkeyhash"
|
102
102
|
when PURPOSE_TYPE[:nested_witness]
|
103
|
-
|
103
|
+
"p2wpkh-p2sh"
|
104
104
|
when PURPOSE_TYPE[:native_segwit]
|
105
|
-
|
105
|
+
"p2wpkh"
|
106
106
|
else
|
107
|
-
|
107
|
+
"unknown"
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
@@ -146,10 +146,10 @@ module Tapyrus
|
|
146
146
|
end
|
147
147
|
|
148
148
|
def validate_params!(account_key, purpose, index)
|
149
|
-
raise
|
150
|
-
raise
|
149
|
+
raise "account_key must be an instance of Tapyrus::ExtPubkey." unless account_key.is_a?(Tapyrus::ExtPubkey)
|
150
|
+
raise "Account key and index does not match." unless account_key.number == (index + Tapyrus::HARDENED_THRESHOLD)
|
151
151
|
version_bytes = Tapyrus::ExtPubkey.version_from_purpose(purpose + Tapyrus::HARDENED_THRESHOLD)
|
152
|
-
raise
|
152
|
+
raise "The purpose and the account key do not match." unless account_key.version == version_bytes
|
153
153
|
end
|
154
154
|
end
|
155
155
|
end
|