bitcoinrb 0.3.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +37 -0
- data/.rspec_parallel +2 -0
- data/.ruby-version +1 -1
- data/README.md +17 -6
- data/bitcoinrb.gemspec +9 -8
- data/exe/bitcoinrbd +5 -0
- data/lib/bitcoin.rb +37 -19
- data/lib/bitcoin/bip85_entropy.rb +111 -0
- data/lib/bitcoin/block_filter.rb +14 -0
- data/lib/bitcoin/block_header.rb +2 -0
- data/lib/bitcoin/chain_params.rb +9 -8
- data/lib/bitcoin/chainparams/regtest.yml +1 -1
- data/lib/bitcoin/chainparams/signet.yml +39 -0
- data/lib/bitcoin/chainparams/testnet.yml +1 -1
- data/lib/bitcoin/constants.rb +44 -10
- data/lib/bitcoin/descriptor.rb +1 -1
- data/lib/bitcoin/errors.rb +19 -0
- data/lib/bitcoin/ext.rb +6 -0
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +36 -0
- data/lib/bitcoin/ext/json_parser.rb +46 -0
- data/lib/bitcoin/ext_key.rb +51 -20
- data/lib/bitcoin/key.rb +89 -30
- data/lib/bitcoin/key_path.rb +12 -5
- data/lib/bitcoin/message.rb +79 -0
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +17 -0
- data/lib/bitcoin/message/cf_parser.rb +16 -0
- data/lib/bitcoin/message/cfcheckpt.rb +36 -0
- data/lib/bitcoin/message/cfheaders.rb +40 -0
- data/lib/bitcoin/message/cfilter.rb +35 -0
- data/lib/bitcoin/message/fee_filter.rb +1 -1
- data/lib/bitcoin/message/filter_load.rb +3 -3
- data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
- data/lib/bitcoin/message/get_cfheaders.rb +24 -0
- data/lib/bitcoin/message/get_cfilters.rb +25 -0
- data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
- data/lib/bitcoin/message/inventory.rb +1 -1
- data/lib/bitcoin/message/merkle_block.rb +1 -1
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/ping.rb +1 -1
- data/lib/bitcoin/message/pong.rb +1 -1
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/send_cmpct.rb +2 -2
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message/version.rb +7 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/mnemonic.rb +7 -7
- data/lib/bitcoin/network/peer.rb +9 -4
- data/lib/bitcoin/network/peer_discovery.rb +1 -1
- data/lib/bitcoin/node/cli.rb +14 -10
- data/lib/bitcoin/node/configuration.rb +3 -1
- data/lib/bitcoin/node/spv.rb +9 -1
- data/lib/bitcoin/opcodes.rb +14 -1
- data/lib/bitcoin/out_point.rb +2 -0
- data/lib/bitcoin/payment_code.rb +92 -0
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +9 -18
- data/lib/bitcoin/psbt/output.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +12 -17
- data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
- data/lib/bitcoin/rpc/request_handler.rb +5 -5
- data/lib/bitcoin/script/script.rb +96 -39
- data/lib/bitcoin/script/script_error.rb +27 -1
- data/lib/bitcoin/script/script_interpreter.rb +166 -66
- data/lib/bitcoin/script/tx_checker.rb +62 -14
- data/lib/bitcoin/secp256k1.rb +1 -0
- data/lib/bitcoin/secp256k1/native.rb +184 -17
- data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
- data/lib/bitcoin/secp256k1/ruby.rb +112 -56
- data/lib/bitcoin/sighash_generator.rb +156 -0
- data/lib/bitcoin/store.rb +1 -0
- data/lib/bitcoin/store/chain_entry.rb +1 -0
- data/lib/bitcoin/store/utxo_db.rb +226 -0
- data/lib/bitcoin/taproot.rb +9 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +139 -0
- data/lib/bitcoin/tx.rb +34 -104
- data/lib/bitcoin/tx_in.rb +4 -5
- data/lib/bitcoin/tx_out.rb +2 -3
- data/lib/bitcoin/util.rb +22 -6
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet.rb +1 -0
- data/lib/bitcoin/wallet/account.rb +2 -1
- data/lib/bitcoin/wallet/base.rb +2 -2
- data/lib/bitcoin/wallet/master_key.rb +1 -0
- data/lib/bitcoin/wallet/utxo.rb +37 -0
- metadata +86 -32
- data/.travis.yml +0 -11
data/lib/bitcoin/key.rb
CHANGED
@@ -10,6 +10,7 @@ module Bitcoin
|
|
10
10
|
COMPRESSED_PUBLIC_KEY_SIZE = 33
|
11
11
|
SIGNATURE_SIZE = 72
|
12
12
|
COMPACT_SIGNATURE_SIZE = 65
|
13
|
+
COMPACT_SIG_HEADER_BYTE = 0x1b
|
13
14
|
|
14
15
|
attr_accessor :priv_key
|
15
16
|
attr_accessor :pubkey
|
@@ -28,7 +29,7 @@ module Bitcoin
|
|
28
29
|
# @param [Integer] key_type a key type which determine address type.
|
29
30
|
# @param [Boolean] compressed [Deprecated] whether public key is compressed.
|
30
31
|
# @return [Bitcoin::Key] a key object.
|
31
|
-
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true)
|
32
|
+
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
32
33
|
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
33
34
|
if key_type
|
34
35
|
@key_type = key_type
|
@@ -39,13 +40,14 @@ module Bitcoin
|
|
39
40
|
@secp256k1_module = Bitcoin.secp_impl
|
40
41
|
@priv_key = priv_key
|
41
42
|
if @priv_key
|
42
|
-
raise ArgumentError,
|
43
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
|
43
44
|
end
|
44
45
|
if pubkey
|
45
46
|
@pubkey = pubkey
|
46
47
|
else
|
47
48
|
@pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
|
48
49
|
end
|
50
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
|
49
51
|
end
|
50
52
|
|
51
53
|
# generate key pair
|
@@ -63,9 +65,9 @@ module Bitcoin
|
|
63
65
|
data = hex[2...-8].htb
|
64
66
|
checksum = hex[-8..-1]
|
65
67
|
raise ArgumentError, 'invalid version' unless version == Bitcoin.chain_params.privkey_version
|
66
|
-
raise ArgumentError,
|
68
|
+
raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(version + data.bth) == checksum
|
67
69
|
key_len = data.bytesize
|
68
|
-
if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].
|
70
|
+
if key_len == COMPRESSED_PUBLIC_KEY_SIZE && data[-1].unpack1('C') == 1
|
69
71
|
key_type = TYPES[:compressed]
|
70
72
|
data = data[0..-2]
|
71
73
|
elsif key_len == 32
|
@@ -76,6 +78,23 @@ module Bitcoin
|
|
76
78
|
new(priv_key: data.bth, key_type: key_type)
|
77
79
|
end
|
78
80
|
|
81
|
+
# Generate from xonly public key.
|
82
|
+
# @param [String] xonly_pubkey xonly public key with hex format.
|
83
|
+
# @return [Bitcoin::Key] key object has public key.
|
84
|
+
def self.from_xonly_pubkey(xonly_pubkey)
|
85
|
+
raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
|
86
|
+
Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
|
87
|
+
end
|
88
|
+
|
89
|
+
# Generate from public key point.
|
90
|
+
# @param [ECDSA::Point] point Public key point.
|
91
|
+
# @param [Boolean] compressed whether compressed or not.
|
92
|
+
# @return [Bitcoin::Key]
|
93
|
+
def self.from_point(point, compressed: true)
|
94
|
+
pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
|
95
|
+
Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
|
96
|
+
end
|
97
|
+
|
79
98
|
# export private key with wif format
|
80
99
|
def to_wif
|
81
100
|
version = Bitcoin.chain_params.privkey_version
|
@@ -88,30 +107,69 @@ module Bitcoin
|
|
88
107
|
# sign +data+ with private key
|
89
108
|
# @param [String] data a data to be signed with binary format
|
90
109
|
# @param [Boolean] low_r flag to apply low-R.
|
91
|
-
# @param [String] extra_entropy the extra entropy for rfc6979.
|
110
|
+
# @param [String] extra_entropy the extra entropy with binary format for rfc6979.
|
111
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
92
112
|
# @return [String] signature data with binary format
|
93
|
-
def sign(data, low_r = true, extra_entropy = nil)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
113
|
+
def sign(data, low_r = true, extra_entropy = nil, algo: :ecdsa)
|
114
|
+
case algo
|
115
|
+
when :ecdsa
|
116
|
+
sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
|
117
|
+
if low_r && !sig_has_low_r?(sig)
|
118
|
+
counter = 1
|
119
|
+
until sig_has_low_r?(sig)
|
120
|
+
extra_entropy = [counter].pack('I*').bth.ljust(64, '0').htb
|
121
|
+
sig = secp256k1_module.sign_data(data, priv_key, extra_entropy)
|
122
|
+
counter += 1
|
123
|
+
end
|
101
124
|
end
|
125
|
+
sig
|
126
|
+
when :schnorr
|
127
|
+
secp256k1_module.sign_data(data, priv_key, extra_entropy, algo: :schnorr)
|
128
|
+
else
|
129
|
+
raise ArgumentError "Unsupported algo specified: #{algo}"
|
102
130
|
end
|
103
|
-
|
131
|
+
end
|
132
|
+
|
133
|
+
# Sign compact signature.
|
134
|
+
# @param [String] data message digest to be signed.
|
135
|
+
# @return [String] compact signature with binary format.
|
136
|
+
def sign_compact(data)
|
137
|
+
signature, rec = secp256k1_module.sign_compact(data, priv_key)
|
138
|
+
rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
|
139
|
+
[rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
|
140
|
+
ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Recover public key from compact signature.
|
144
|
+
# @param [String] data message digest using signature.
|
145
|
+
# @param [String] signature signature with binary format.
|
146
|
+
# @return [Bitcoin::Key] Recovered public key.
|
147
|
+
def self.recover_compact(data, signature)
|
148
|
+
rec_id = signature.unpack1('C')
|
149
|
+
rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
|
150
|
+
raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
|
151
|
+
rec = rec & 3
|
152
|
+
compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
|
153
|
+
Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
|
104
154
|
end
|
105
155
|
|
106
156
|
# verify signature using public key
|
107
157
|
# @param [String] sig signature data with binary format
|
108
|
-
# @param [String]
|
158
|
+
# @param [String] data original message
|
159
|
+
# @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.
|
109
160
|
# @return [Boolean] verify result
|
110
|
-
def verify(sig,
|
161
|
+
def verify(sig, data, algo: :ecdsa)
|
111
162
|
return false unless valid_pubkey?
|
112
163
|
begin
|
113
|
-
|
114
|
-
|
164
|
+
case algo
|
165
|
+
when :ecdsa
|
166
|
+
sig = ecdsa_signature_parse_der_lax(sig)
|
167
|
+
secp256k1_module.verify_sig(data, sig, pubkey)
|
168
|
+
when :schnorr
|
169
|
+
secp256k1_module.verify_sig(data, sig, xonly_pubkey, algo: :schnorr)
|
170
|
+
else
|
171
|
+
false
|
172
|
+
end
|
115
173
|
rescue Exception
|
116
174
|
false
|
117
175
|
end
|
@@ -125,19 +183,19 @@ module Bitcoin
|
|
125
183
|
# get pay to pubkey hash address
|
126
184
|
# @deprecated
|
127
185
|
def to_p2pkh
|
128
|
-
Bitcoin::Script.to_p2pkh(hash160).
|
186
|
+
Bitcoin::Script.to_p2pkh(hash160).to_addr
|
129
187
|
end
|
130
188
|
|
131
189
|
# get pay to witness pubkey hash address
|
132
190
|
# @deprecated
|
133
191
|
def to_p2wpkh
|
134
|
-
Bitcoin::Script.to_p2wpkh(hash160).
|
192
|
+
Bitcoin::Script.to_p2wpkh(hash160).to_addr
|
135
193
|
end
|
136
194
|
|
137
195
|
# get p2wpkh address nested in p2sh.
|
138
196
|
# @deprecated
|
139
197
|
def to_nested_p2wpkh
|
140
|
-
Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.
|
198
|
+
Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
|
141
199
|
end
|
142
200
|
|
143
201
|
def compressed?
|
@@ -148,10 +206,17 @@ module Bitcoin
|
|
148
206
|
# @return [ECDSA::Point]
|
149
207
|
def to_point
|
150
208
|
p = pubkey
|
151
|
-
p ||= generate_pubkey(priv_key, compressed: compressed)
|
209
|
+
p ||= generate_pubkey(priv_key, compressed: compressed?)
|
152
210
|
ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
|
153
211
|
end
|
154
212
|
|
213
|
+
# get xonly public key (32 bytes).
|
214
|
+
# @return [String] xonly public key with hex format
|
215
|
+
def xonly_pubkey
|
216
|
+
puts "Derive a public key whose y-coordinate is different from this public key." if compressed? && pubkey[0...2] != '02'
|
217
|
+
pubkey[2..65]
|
218
|
+
end
|
219
|
+
|
155
220
|
# check +pubkey+ (hex) is compress or uncompress pubkey.
|
156
221
|
def self.compress_or_uncompress_pubkey?(pubkey)
|
157
222
|
p = pubkey.htb
|
@@ -225,14 +290,8 @@ module Bitcoin
|
|
225
290
|
end
|
226
291
|
|
227
292
|
# fully validate whether this is a valid public key (more expensive than IsValid())
|
228
|
-
def fully_valid_pubkey?
|
229
|
-
|
230
|
-
begin
|
231
|
-
point = ECDSA::Format::PointOctetString.decode(pubkey.htb, ECDSA::Group::Secp256k1)
|
232
|
-
ECDSA::Group::Secp256k1.valid_public_key?(point)
|
233
|
-
rescue ECDSA::Format::DecodeError
|
234
|
-
false
|
235
|
-
end
|
293
|
+
def fully_valid_pubkey?(allow_hybrid = false)
|
294
|
+
valid_pubkey? && secp256k1_module.parse_ec_pubkey?(pubkey, allow_hybrid)
|
236
295
|
end
|
237
296
|
|
238
297
|
private
|
data/lib/bitcoin/key_path.rb
CHANGED
@@ -4,14 +4,21 @@ module Bitcoin
|
|
4
4
|
# key path convert an array of derive number
|
5
5
|
# @param [String] path_string
|
6
6
|
# @return [Array[Integer]] key path numbers.
|
7
|
+
# @raise [ArgumentError] if invalid +path_string+.
|
7
8
|
def parse_key_path(path_string)
|
8
|
-
path_string.split('/')
|
9
|
+
paths = path_string.split('/')
|
10
|
+
raise ArgumentError, "Invalid path." if path_string.include?(" ")
|
11
|
+
raise ArgumentError, "Invalid path." unless path_string.count("/") <= paths.size
|
12
|
+
paths.map.with_index do|p, index|
|
9
13
|
if index == 0
|
10
|
-
|
11
|
-
|
14
|
+
next if p == 'm'
|
15
|
+
raise ArgumentError, "Invalid path." unless p == 'm'
|
12
16
|
end
|
13
|
-
raise ArgumentError.
|
14
|
-
|
17
|
+
raise ArgumentError, "Invalid path." if p.count("'") > 1 || (p.count("'") == 1 && p[-1] != "'")
|
18
|
+
raise ArgumentError, "Invalid path." unless p.delete("'") =~ /^[0-9]+$/
|
19
|
+
value = (p[-1] == "'" ? p.delete("'").to_i + Bitcoin::HARDENED_THRESHOLD : p.to_i)
|
20
|
+
raise ArgumentError, "Invalid path." if value > 4294967295 # 4294967295 = 0xFFFFFFFF (uint32 max)
|
21
|
+
value
|
15
22
|
end[1..-1]
|
16
23
|
end
|
17
24
|
|
data/lib/bitcoin/message.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Bitcoin
|
2
2
|
module Message
|
3
3
|
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
4
6
|
autoload :Base, 'bitcoin/message/base'
|
5
7
|
autoload :Inventory, 'bitcoin/message/inventory'
|
6
8
|
autoload :InventoriesParser, 'bitcoin/message/inventories_parser'
|
@@ -37,6 +39,15 @@ module Bitcoin
|
|
37
39
|
autoload :BlockTransactionRequest, 'bitcoin/message/block_transaction_request'
|
38
40
|
autoload :BlockTxn, 'bitcoin/message/block_txn'
|
39
41
|
autoload :BlockTransactions, 'bitcoin/message/block_transactions'
|
42
|
+
autoload :GetCFilters, 'bitcoin/message/get_cfilters'
|
43
|
+
autoload :GetCFHeaders, 'bitcoin/message/get_cfheaders'
|
44
|
+
autoload :CFParser, 'bitcoin/message/cf_parser'
|
45
|
+
autoload :GetCFCheckpt, 'bitcoin/message/get_cfcheckpt'
|
46
|
+
autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
|
47
|
+
autoload :CFilter, 'bitcoin/message/cfilter'
|
48
|
+
autoload :CFHeaders, 'bitcoin/message/cfheaders'
|
49
|
+
autoload :SendAddrV2, 'bitcoin/message/send_addr_v2'
|
50
|
+
autoload :AddrV2, 'bitcoin/message/addr_v2'
|
40
51
|
|
41
52
|
USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
|
42
53
|
|
@@ -66,5 +77,73 @@ module Bitcoin
|
|
66
77
|
compact_witness: 70015
|
67
78
|
}
|
68
79
|
|
80
|
+
module_function
|
81
|
+
|
82
|
+
# Decode P2P message.
|
83
|
+
# @param [String] command P2P message command string.
|
84
|
+
# @param [String] payload P2P message payload with hex format..
|
85
|
+
# @return [Bitcoin::Message::]
|
86
|
+
def decode(command, payload = nil)
|
87
|
+
payload = payload.htb if payload
|
88
|
+
case command
|
89
|
+
when Bitcoin::Message::Version::COMMAND
|
90
|
+
Bitcoin::Message::Version.parse_from_payload(payload)
|
91
|
+
when Bitcoin::Message::VerAck::COMMAND
|
92
|
+
Bitcoin::Message::VerAck.new
|
93
|
+
when Bitcoin::Message::GetAddr::COMMAND
|
94
|
+
Bitcoin::Message::GetAddr.new
|
95
|
+
when Bitcoin::Message::Addr::COMMAND
|
96
|
+
Bitcoin::Message::Addr.parse_from_payload(payload)
|
97
|
+
when Bitcoin::Message::SendHeaders::COMMAND
|
98
|
+
Bitcoin::Message::SendHeaders.new
|
99
|
+
when Bitcoin::Message::FeeFilter::COMMAND
|
100
|
+
Bitcoin::Message::FeeFilter.parse_from_payload(payload)
|
101
|
+
when Bitcoin::Message::Ping::COMMAND
|
102
|
+
Bitcoin::Message::Ping.parse_from_payload(payload)
|
103
|
+
when Bitcoin::Message::Pong::COMMAND
|
104
|
+
Bitcoin::Message::Pong.parse_from_payload(payload)
|
105
|
+
when Bitcoin::Message::GetHeaders::COMMAND
|
106
|
+
Bitcoin::Message::GetHeaders.parse_from_payload(payload)
|
107
|
+
when Bitcoin::Message::Headers::COMMAND
|
108
|
+
Bitcoin::Message::Headers.parse_from_payload(payload)
|
109
|
+
when Bitcoin::Message::Block::COMMAND
|
110
|
+
Bitcoin::Message::Block.parse_from_payload(payload)
|
111
|
+
when Bitcoin::Message::Tx::COMMAND
|
112
|
+
Bitcoin::Message::Tx.parse_from_payload(payload)
|
113
|
+
when Bitcoin::Message::NotFound::COMMAND
|
114
|
+
Bitcoin::Message::NotFound.parse_from_payload(payload)
|
115
|
+
when Bitcoin::Message::MemPool::COMMAND
|
116
|
+
Bitcoin::Message::MemPool.new
|
117
|
+
when Bitcoin::Message::Reject::COMMAND
|
118
|
+
Bitcoin::Message::Reject.parse_from_payload(payload)
|
119
|
+
when Bitcoin::Message::SendCmpct::COMMAND
|
120
|
+
Bitcoin::Message::SendCmpct.parse_from_payload(payload)
|
121
|
+
when Bitcoin::Message::Inv::COMMAND
|
122
|
+
Bitcoin::Message::Inv.parse_from_payload(payload)
|
123
|
+
when Bitcoin::Message::MerkleBlock::COMMAND
|
124
|
+
Bitcoin::Message::MerkleBlock.parse_from_payload(payload)
|
125
|
+
when Bitcoin::Message::CmpctBlock::COMMAND
|
126
|
+
Bitcoin::Message::CmpctBlock.parse_from_payload(payload)
|
127
|
+
when Bitcoin::Message::GetData::COMMAND
|
128
|
+
Bitcoin::Message::GetData.parse_from_payload(payload)
|
129
|
+
when Bitcoin::Message::GetCFHeaders::COMMAND
|
130
|
+
Bitcoin::Message::GetCFHeaders.parse_from_payload(payload)
|
131
|
+
when Bitcoin::Message::GetCFilters::COMMAND
|
132
|
+
Bitcoin::Message::GetCFilters.parse_from_payload(payload)
|
133
|
+
when Bitcoin::Message::GetCFCheckpt::COMMAND
|
134
|
+
Bitcoin::Message::GetCFCheckpt.parse_from_payload(payload)
|
135
|
+
when Bitcoin::Message::CFCheckpt::COMMAND
|
136
|
+
Bitcoin::Message::CFCheckpt.parse_from_payload(payload)
|
137
|
+
when Bitcoin::Message::CFHeaders::COMMAND
|
138
|
+
Bitcoin::Message::CFHeaders.parse_from_payload(payload)
|
139
|
+
when Bitcoin::Message::CFilter::COMMAND
|
140
|
+
Bitcoin::Message::CFilter.parse_from_payload(payload)
|
141
|
+
when Bitcoin::Message::SendAddrV2::COMMAND
|
142
|
+
Bitcoin::Message::SendAddrV2.new
|
143
|
+
when Bitcoin::Message::AddrV2::COMMAND
|
144
|
+
Bitcoin::Message::AddrV2.parse_from_payload(payload)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
69
148
|
end
|
70
149
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# addrv2 message class.
|
5
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki
|
6
|
+
class AddrV2 < Base
|
7
|
+
|
8
|
+
COMMAND = 'addrv2'
|
9
|
+
|
10
|
+
attr_reader :addrs
|
11
|
+
|
12
|
+
def initialize(addrs = [])
|
13
|
+
@addrs = addrs
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_from_payload(payload)
|
17
|
+
buf = StringIO.new(payload)
|
18
|
+
addr_count = Bitcoin.unpack_var_int_from_io(buf)
|
19
|
+
v2 = new
|
20
|
+
addr_count.times do
|
21
|
+
v2.addrs << NetworkAddr.parse_from_payload(buf, type: NetworkAddr::TYPE[:addr_v2])
|
22
|
+
end
|
23
|
+
v2
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_payload
|
27
|
+
buf = Bitcoin.pack_var_int(addrs.size)
|
28
|
+
buf << (addrs.map { |a| a.to_payload(type: NetworkAddr::TYPE[:addr_v2])}.join)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/lib/bitcoin/message/base.rb
CHANGED
@@ -3,6 +3,7 @@ module Bitcoin
|
|
3
3
|
|
4
4
|
# Base message class
|
5
5
|
class Base
|
6
|
+
include Bitcoin::HexConverter
|
6
7
|
include Bitcoin::Util
|
7
8
|
extend Bitcoin::Util
|
8
9
|
|
@@ -22,6 +23,22 @@ module Bitcoin
|
|
22
23
|
raise 'to_payload must be implemented in a child class.'
|
23
24
|
end
|
24
25
|
|
26
|
+
# Decode message data to message object.
|
27
|
+
# @param [String] message with binary format.
|
28
|
+
# @return [Bitcoin::Message::XXX] An instance of a class that inherits Bitcoin::Message::Base
|
29
|
+
# @raise [ArgumentError] Occurs for data that cannot be decoded.
|
30
|
+
def self.from_pkt(message)
|
31
|
+
buf = StringIO.new(message)
|
32
|
+
magic = buf.read(4)
|
33
|
+
raise ArgumentError, 'Invalid magic.' unless magic == Bitcoin.chain_params.magic_head.htb
|
34
|
+
command = buf.read(12).delete("\x00")
|
35
|
+
length = buf.read(4).unpack1('V')
|
36
|
+
checksum = buf.read(4)
|
37
|
+
payload = buf.read(length)
|
38
|
+
raise ArgumentError, 'Checksum do not match.' unless checksum == Bitcoin.double_sha256(payload)[0...4]
|
39
|
+
Bitcoin::Message.decode(command, payload&.bth)
|
40
|
+
end
|
41
|
+
|
25
42
|
end
|
26
43
|
|
27
44
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
module CFParser
|
4
|
+
|
5
|
+
def parse_from_payload(payload)
|
6
|
+
type, start, hash = payload.unpack('CLH*')
|
7
|
+
self.new(type, start, hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_payload
|
11
|
+
[filter_type, start_height, stop_hash].pack('CLH*')
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Message
|
3
|
+
|
4
|
+
# cfcheckpt message for BIP-157
|
5
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfcheckpt
|
6
|
+
class CFCheckpt < Base
|
7
|
+
|
8
|
+
COMMAND = 'cfcheckpt'
|
9
|
+
|
10
|
+
attr_accessor :filter_type
|
11
|
+
attr_accessor :stop_hash # little endian
|
12
|
+
attr_accessor :filter_headers # little endian
|
13
|
+
|
14
|
+
def initialize(filter_type, stop_hash, filter_headers)
|
15
|
+
@filter_type = filter_type
|
16
|
+
@stop_hash = stop_hash
|
17
|
+
@filter_headers = filter_headers
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse_from_payload(payload)
|
21
|
+
buf = StringIO.new(payload)
|
22
|
+
type = buf.read(1).unpack1('C')
|
23
|
+
hash = buf.read(32).unpack1('H*')
|
24
|
+
count = Bitcoin.unpack_var_int_from_io(buf)
|
25
|
+
headers = count.times.map{buf.read(32).bth}
|
26
|
+
self.new(type, hash, headers)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_payload
|
30
|
+
[filter_type, stop_hash].pack('CH*') +
|
31
|
+
Bitcoin.pack_var_int(filter_headers.size) + filter_headers.map(&:htb).join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|