tapyrus 0.3.4 → 0.3.5
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 +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
|