bitcoinrb 0.6.0 → 1.0.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 +1 -1
- data/bitcoinrb.gemspec +4 -3
- data/lib/bitcoin/constants.rb +24 -17
- data/lib/bitcoin/ext/array_ext.rb +22 -0
- data/lib/bitcoin/ext/ecdsa.rb +5 -0
- data/lib/bitcoin/ext.rb +1 -0
- data/lib/bitcoin/ext_key.rb +1 -1
- data/lib/bitcoin/key.rb +42 -2
- data/lib/bitcoin/message/addr_v2.rb +34 -0
- data/lib/bitcoin/message/base.rb +16 -0
- data/lib/bitcoin/message/network_addr.rb +141 -18
- data/lib/bitcoin/message/send_addr_v2.rb +13 -0
- data/lib/bitcoin/message/tx.rb +1 -1
- data/lib/bitcoin/message.rb +72 -0
- data/lib/bitcoin/message_sign.rb +47 -0
- data/lib/bitcoin/payments/payment.pb.rb +1 -1
- data/lib/bitcoin/psbt/input.rb +1 -1
- data/lib/bitcoin/psbt/tx.rb +10 -1
- data/lib/bitcoin/psbt.rb +8 -0
- data/lib/bitcoin/rpc/request_handler.rb +3 -3
- data/lib/bitcoin/script/script.rb +29 -12
- data/lib/bitcoin/script/script_interpreter.rb +6 -3
- data/lib/bitcoin/script/tx_checker.rb +2 -4
- data/lib/bitcoin/secp256k1/native.rb +68 -14
- data/lib/bitcoin/secp256k1/ruby.rb +31 -3
- data/lib/bitcoin/sighash_generator.rb +1 -0
- data/lib/bitcoin/taproot/leaf_node.rb +23 -0
- data/lib/bitcoin/taproot/simple_builder.rb +155 -0
- data/lib/bitcoin/taproot.rb +45 -0
- data/lib/bitcoin/tx.rb +17 -16
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin.rb +3 -8
- metadata +44 -8
- data/.travis.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffa8f8b7dbf104190628e7ff38dbb950aa14927ab208377c821ad7f5aeaab862
|
4
|
+
data.tar.gz: f24e89dd6de49e066fae37fe5c4118d955f31a9a751af5901d6cde32528aa8fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 306f65243198a07403c785b212d5d86f5a6eb27b189aa9454d27d280f4d93500b967e93fb7f995e8c998ce7f8f9ff4ba40a838c7f4f430b8e2fef0c04cb11206
|
7
|
+
data.tar.gz: 9e4d92cc79249f5027f105a8c7f69320f35b06b3bc3e9b5cc0416f74e7698f47910f4b63afba1821ec49e647671b1b67d4955acb80fe587d79bb34d0507ae98e
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
strategy:
|
21
|
+
matrix:
|
22
|
+
ruby-version: ['2.6', '2.7', '3.0']
|
23
|
+
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- name: Install leveldb
|
27
|
+
run: sudo apt-get install libleveldb-dev
|
28
|
+
- name: Set up Ruby
|
29
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
30
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
31
|
+
# uses: ruby/setup-ruby@v1
|
32
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
33
|
+
with:
|
34
|
+
ruby-version: ${{ matrix.ruby-version }}
|
35
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
36
|
+
- name: Run tests
|
37
|
+
run: bundle exec rake spec
|
data/.rspec_parallel
ADDED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-3.0.
|
1
|
+
ruby-3.0.2
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bitcoinrb [](https://github.com/chaintope/bitcoinrb/actions/workflows/ruby.yml) [](https://badge.fury.io/rb/bitcoinrb) [](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
|
2
2
|
|
3
3
|
|
4
4
|
Bitcoinrb is a Ruby implementation of Bitcoin Protocol.
|
data/bitcoinrb.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_runtime_dependency 'ecdsa'
|
24
24
|
spec.add_runtime_dependency 'eventmachine'
|
25
25
|
spec.add_runtime_dependency 'murmurhash3'
|
26
|
-
spec.add_runtime_dependency 'bech32', '~> 1.0
|
26
|
+
spec.add_runtime_dependency 'bech32', '~> 1.1.0'
|
27
27
|
spec.add_runtime_dependency 'daemon-spawn'
|
28
28
|
spec.add_runtime_dependency 'thor'
|
29
29
|
spec.add_runtime_dependency 'ffi'
|
@@ -33,7 +33,8 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_runtime_dependency 'siphash'
|
34
34
|
spec.add_runtime_dependency 'protobuf', '3.8.5'
|
35
35
|
spec.add_runtime_dependency 'json_pure', '>= 2.3.1'
|
36
|
-
spec.add_runtime_dependency 'bip-schnorr', '>= 0.
|
36
|
+
spec.add_runtime_dependency 'bip-schnorr', '>= 0.4.0'
|
37
|
+
spec.add_runtime_dependency 'base32', '>= 0.3.4'
|
37
38
|
|
38
39
|
# for options
|
39
40
|
spec.add_development_dependency 'leveldb-native'
|
@@ -43,5 +44,5 @@ Gem::Specification.new do |spec|
|
|
43
44
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
45
|
spec.add_development_dependency 'timecop'
|
45
46
|
spec.add_development_dependency 'webmock', '>= 3.11.1'
|
46
|
-
|
47
|
+
spec.add_development_dependency 'parallel', '>= 1.20.1'
|
47
48
|
end
|
data/lib/bitcoin/constants.rb
CHANGED
@@ -53,27 +53,34 @@ module Bitcoin
|
|
53
53
|
MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
|
54
54
|
|
55
55
|
# Standard script verification flags that standard transactions will comply with.
|
56
|
-
STANDARD_SCRIPT_VERIFY_FLAGS = [
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
56
|
+
STANDARD_SCRIPT_VERIFY_FLAGS = [
|
57
|
+
MANDATORY_SCRIPT_VERIFY_FLAGS,
|
58
|
+
SCRIPT_VERIFY_DERSIG,
|
59
|
+
SCRIPT_VERIFY_STRICTENC,
|
60
|
+
SCRIPT_VERIFY_MINIMALDATA,
|
61
|
+
SCRIPT_VERIFY_NULLDUMMY,
|
62
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
63
|
+
SCRIPT_VERIFY_CLEANSTACK,
|
64
|
+
SCRIPT_VERIFY_MINIMALIF,
|
65
|
+
SCRIPT_VERIFY_NULLFAIL,
|
66
|
+
SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
|
67
|
+
SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
|
68
|
+
SCRIPT_VERIFY_LOW_S,
|
69
|
+
SCRIPT_VERIFY_WITNESS,
|
70
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
|
71
|
+
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
|
72
|
+
SCRIPT_VERIFY_CONST_SCRIPTCODE,
|
73
|
+
SCRIPT_VERIFY_TAPROOT |
|
74
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
|
75
|
+
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
|
76
|
+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE
|
77
|
+
].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
|
72
78
|
|
73
79
|
# for script
|
74
80
|
|
75
81
|
# witness version
|
76
|
-
|
82
|
+
WITNESS_VERSION_V0 = 0x00
|
83
|
+
WITNESS_VERSION_V1 = Bitcoin::Opcodes::OP_1
|
77
84
|
|
78
85
|
# Maximum script length in bytes
|
79
86
|
MAX_SCRIPT_SIZE = 10000
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Ext
|
3
|
+
module ArrayExt
|
4
|
+
|
5
|
+
refine Array do
|
6
|
+
|
7
|
+
# resize array content with +initial_value+.
|
8
|
+
# expect to behave like vec#resize in c++.
|
9
|
+
def resize!(new_size, initial_value = 0)
|
10
|
+
if size < new_size
|
11
|
+
(new_size - size).times{self.<< initial_value}
|
12
|
+
elsif size > new_size
|
13
|
+
(size - new_size).times{delete_at(-1)}
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/bitcoin/ext/ecdsa.rb
CHANGED
data/lib/bitcoin/ext.rb
CHANGED
data/lib/bitcoin/ext_key.rb
CHANGED
@@ -280,7 +280,7 @@ module Bitcoin
|
|
280
280
|
l = Bitcoin.hmac_sha512(chain_code, data)
|
281
281
|
left = l[0..31].bth.to_i(16)
|
282
282
|
raise 'invalid key' if left >= CURVE_ORDER
|
283
|
-
p1 = Bitcoin::
|
283
|
+
p1 = Bitcoin::Key.new(priv_key: left.to_s(16), key_type: Bitcoin::Key::TYPES[:uncompressed]).to_point
|
284
284
|
p2 = Bitcoin::Key.new(pubkey: pubkey, key_type: key_type).to_point
|
285
285
|
new_key.pubkey = (p1 + p2).to_hex
|
286
286
|
new_key.chain_code = l[32..-1]
|
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
|
@@ -29,7 +30,6 @@ module Bitcoin
|
|
29
30
|
# @param [Boolean] compressed [Deprecated] whether public key is compressed.
|
30
31
|
# @return [Bitcoin::Key] a key object.
|
31
32
|
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
32
|
-
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
33
33
|
if key_type
|
34
34
|
@key_type = key_type
|
35
35
|
compressed = @key_type != TYPES[:uncompressed]
|
@@ -77,6 +77,23 @@ module Bitcoin
|
|
77
77
|
new(priv_key: data.bth, key_type: key_type)
|
78
78
|
end
|
79
79
|
|
80
|
+
# Generate from xonly public key.
|
81
|
+
# @param [String] xonly_pubkey xonly public key with hex format.
|
82
|
+
# @return [Bitcoin::Key] key object has public key.
|
83
|
+
def self.from_xonly_pubkey(xonly_pubkey)
|
84
|
+
raise ArgumentError, 'xonly_pubkey must be 32 bytes' unless xonly_pubkey.htb.bytesize == 32
|
85
|
+
Bitcoin::Key.new(pubkey: "02#{xonly_pubkey}", key_type: TYPES[:compressed])
|
86
|
+
end
|
87
|
+
|
88
|
+
# Generate from public key point.
|
89
|
+
# @param [ECDSA::Point] point Public key point.
|
90
|
+
# @param [Boolean] compressed whether compressed or not.
|
91
|
+
# @return [Bitcoin::Key]
|
92
|
+
def self.from_point(point, compressed: true)
|
93
|
+
pubkey = ECDSA::Format::PointOctetString.encode(point, compression: compressed).bth
|
94
|
+
Bitcoin::Key.new(pubkey: pubkey, key_type: TYPES[:compressed])
|
95
|
+
end
|
96
|
+
|
80
97
|
# export private key with wif format
|
81
98
|
def to_wif
|
82
99
|
version = Bitcoin.chain_params.privkey_version
|
@@ -112,6 +129,29 @@ module Bitcoin
|
|
112
129
|
end
|
113
130
|
end
|
114
131
|
|
132
|
+
# Sign compact signature.
|
133
|
+
# @param [String] data message digest to be signed.
|
134
|
+
# @return [String] compact signature with binary format.
|
135
|
+
def sign_compact(data)
|
136
|
+
signature, rec = secp256k1_module.sign_compact(data, priv_key)
|
137
|
+
rec = Bitcoin::Key::COMPACT_SIG_HEADER_BYTE + rec + (compressed? ? 4 : 0)
|
138
|
+
[rec].pack('C') + ECDSA::Format::IntegerOctetString.encode(signature.r, 32) +
|
139
|
+
ECDSA::Format::IntegerOctetString.encode(signature.s, 32)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Recover public key from compact signature.
|
143
|
+
# @param [String] data message digest using signature.
|
144
|
+
# @param [String] signature signature with binary format.
|
145
|
+
# @return [Bitcoin::Key] Recovered public key.
|
146
|
+
def self.recover_compact(data, signature)
|
147
|
+
rec_id = signature.unpack1('C')
|
148
|
+
rec = rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE
|
149
|
+
raise ArgumentError, 'Invalid signature parameter' if rec < 0 || rec > 15
|
150
|
+
rec = rec & 3
|
151
|
+
compressed = (rec_id - Bitcoin::Key::COMPACT_SIG_HEADER_BYTE) & 4 != 0
|
152
|
+
Bitcoin.secp_impl.recover_compact(data, signature, rec, compressed)
|
153
|
+
end
|
154
|
+
|
115
155
|
# verify signature using public key
|
116
156
|
# @param [String] sig signature data with binary format
|
117
157
|
# @param [String] data original message
|
@@ -165,7 +205,7 @@ module Bitcoin
|
|
165
205
|
# @return [ECDSA::Point]
|
166
206
|
def to_point
|
167
207
|
p = pubkey
|
168
|
-
p ||= generate_pubkey(priv_key, compressed: compressed)
|
208
|
+
p ||= generate_pubkey(priv_key, compressed: compressed?)
|
169
209
|
ECDSA::Format::PointOctetString.decode(p.htb, Bitcoin::Secp256k1::GROUP)
|
170
210
|
end
|
171
211
|
|
@@ -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
@@ -23,6 +23,22 @@ module Bitcoin
|
|
23
23
|
raise 'to_payload must be implemented in a child class.'
|
24
24
|
end
|
25
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
|
+
|
26
42
|
end
|
27
43
|
|
28
44
|
end
|
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'ipaddr'
|
2
|
+
require 'base32'
|
2
3
|
|
3
4
|
module Bitcoin
|
4
5
|
module Message
|
5
6
|
|
7
|
+
NETWORK_ID = {ipv4: 0x01, ipv6: 0x02, tor_v2: 0x03, tor_v3: 0x04, i2p: 0x05, cjdns: 0x06}
|
8
|
+
INTERNAL_IN_IPV6_PREFIX = "fd6b:88c0:8724"
|
9
|
+
|
6
10
|
class NetworkAddr
|
7
11
|
|
12
|
+
TYPE = {legacy: 0x01, addr_v2: 0x02}
|
13
|
+
|
8
14
|
# unix time.
|
9
15
|
# Nodes advertising their own IP address set this to the current time.
|
10
16
|
# Nodes advertising IP addresses they’ve connected to set this to the last time they connected to that node.
|
@@ -14,47 +20,164 @@ module Bitcoin
|
|
14
20
|
# The services the node advertised in its version message.
|
15
21
|
attr_accessor :services
|
16
22
|
|
17
|
-
attr_accessor :
|
23
|
+
attr_accessor :net # network ID that defined by BIP-155
|
24
|
+
|
25
|
+
# Network address. The interpretation depends on networkID.
|
26
|
+
# If ipv4 or ipv6 this field is a IPAddr object, otherwise hex string.
|
27
|
+
attr_accessor :addr
|
18
28
|
|
19
29
|
attr_accessor :port
|
20
30
|
|
21
31
|
attr_reader :skip_time
|
22
32
|
|
23
|
-
def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port,
|
33
|
+
def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port,
|
34
|
+
services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i, net: NETWORK_ID[:ipv4])
|
24
35
|
@time = time
|
25
|
-
@ip_addr = IPAddr.new(ip)
|
26
36
|
@port = port
|
27
37
|
@services = services
|
38
|
+
@net = net
|
39
|
+
case net
|
40
|
+
when NETWORK_ID[:ipv4], NETWORK_ID[:ipv6]
|
41
|
+
@addr = IPAddr.new(ip) if ip
|
42
|
+
end
|
28
43
|
end
|
29
44
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
# Parse addr payload
|
46
|
+
# @param [String] payload payload of addr
|
47
|
+
# @param [Integer] type Address format type
|
48
|
+
# @return [NetworkAddr]
|
49
|
+
def self.parse_from_payload(payload, type: TYPE[:legacy])
|
50
|
+
case type
|
51
|
+
when TYPE[:legacy]
|
52
|
+
load_legacy_payload(payload)
|
53
|
+
when TYPE[:addr_v2]
|
54
|
+
load_addr_v2_payload(payload)
|
55
|
+
else
|
56
|
+
raise Bitcoin::Message::Error, "Unknown type: #{type}."
|
57
|
+
end
|
39
58
|
end
|
40
59
|
|
41
60
|
def self.local_addr
|
42
61
|
addr = new
|
43
|
-
addr.
|
62
|
+
addr.addr = IPAddr.new('127.0.0.1')
|
44
63
|
addr.port = Bitcoin.chain_params.default_port
|
45
64
|
addr.services = DEFAULT_SERVICE_FLAGS
|
46
65
|
addr
|
47
66
|
end
|
48
67
|
|
49
|
-
|
50
|
-
|
68
|
+
# Show addr string. e.g 127.0.0.1
|
69
|
+
def addr_string
|
70
|
+
case net
|
71
|
+
when NETWORK_ID[:ipv4]
|
72
|
+
addr.native
|
73
|
+
when NETWORK_ID[:ipv6]
|
74
|
+
if addr.to_s.start_with?(INTERNAL_IN_IPV6_PREFIX)
|
75
|
+
Base32.encode(addr.hton[6..-1]).downcase.delete('=') + ".internal"
|
76
|
+
else
|
77
|
+
addr.to_s
|
78
|
+
end
|
79
|
+
when NETWORK_ID[:tor_v2]
|
80
|
+
Base32.encode(addr.htb).downcase + ".onion"
|
81
|
+
when NETWORK_ID[:tor_v3]
|
82
|
+
# TORv3 onion_address = base32(PUBKEY | CHECKSUM | VERSION) + ".onion"
|
83
|
+
pubkey = addr.htb
|
84
|
+
checksum = OpenSSL::Digest.new('SHA3-256').digest('.onion checksum' + pubkey + "\x03")
|
85
|
+
Base32.encode(pubkey + checksum[0...2] + "\x03").downcase + ".onion"
|
86
|
+
when NETWORK_ID[:i2p]
|
87
|
+
Base32.encode(addr.htb).downcase.delete('=') + ".b32.i2p"
|
88
|
+
when NETWORK_ID[:cjdns]
|
89
|
+
addr.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_payload(skip_time = false, type: TYPE[:legacy])
|
94
|
+
case type
|
95
|
+
when TYPE[:legacy]
|
96
|
+
legacy_payload(skip_time)
|
97
|
+
when TYPE[:addr_v2]
|
98
|
+
v2_payload
|
99
|
+
else
|
100
|
+
raise Bitcoin::Message::Error, "Unknown type: #{type}."
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Load addr payload with legacy format.
|
105
|
+
def self.load_legacy_payload(payload)
|
106
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
107
|
+
has_time = buf.size > 26
|
108
|
+
addr = NetworkAddr.new(time: nil)
|
109
|
+
addr.time = buf.read(4).unpack1('V') if has_time
|
110
|
+
addr.services = buf.read(8).unpack1('Q')
|
111
|
+
addr.addr = IPAddr::new_ntoh(buf.read(16))
|
112
|
+
addr.port = buf.read(2).unpack1('n')
|
113
|
+
addr
|
114
|
+
end
|
115
|
+
|
116
|
+
# Load addr payload with addr v2 format.
|
117
|
+
def self.load_addr_v2_payload(payload)
|
118
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
119
|
+
addr = NetworkAddr.new(time: buf.read(4).unpack1('V'))
|
120
|
+
addr.services = Bitcoin.unpack_var_int_from_io(buf)
|
121
|
+
addr.net = buf.read(1).unpack1('C')
|
122
|
+
raise Bitcoin::Message::Error, "Unknown network id: #{addr.net}" unless NETWORK_ID.value?(addr.net)
|
123
|
+
addr_len = Bitcoin.unpack_var_int_from_io(buf)
|
124
|
+
addr.addr = case addr.net
|
125
|
+
when NETWORK_ID[:ipv4]
|
126
|
+
raise Bitcoin::Message::Error, "Invalid IPv4 address." unless addr_len == 4
|
127
|
+
IPAddr::new_ntoh(buf.read(addr_len))
|
128
|
+
when NETWORK_ID[:ipv6]
|
129
|
+
raise Bitcoin::Message::Error, "Invalid IPv6 address." unless addr_len == 16
|
130
|
+
a = IPAddr::new_ntoh(buf.read(addr_len))
|
131
|
+
raise Bitcoin::Message::Error, "Invalid IPv6 address." if a.ipv4_mapped?
|
132
|
+
a
|
133
|
+
when NETWORK_ID[:tor_v2]
|
134
|
+
raise Bitcoin::Message::Error, "Invalid Tor v2 address." unless addr_len == 10
|
135
|
+
buf.read(addr_len).bth
|
136
|
+
when NETWORK_ID[:tor_v3]
|
137
|
+
raise Bitcoin::Message::Error, "Invalid Tor v3 address." unless addr_len == 32
|
138
|
+
buf.read(addr_len).bth
|
139
|
+
when NETWORK_ID[:i2p]
|
140
|
+
raise Bitcoin::Message::Error, "Invalid I2P address." unless addr_len == 32
|
141
|
+
buf.read(addr_len).bth
|
142
|
+
when NETWORK_ID[:cjdns]
|
143
|
+
raise Bitcoin::Message::Error, "Invalid CJDNS address." unless addr_len == 16
|
144
|
+
a = IPAddr::new_ntoh(buf.read(addr_len))
|
145
|
+
raise Bitcoin::Message::Error, "Invalid CJDNS address." unless a.to_s.start_with?('fc00:')
|
146
|
+
a
|
147
|
+
end
|
148
|
+
addr.port = buf.read(2).unpack1('n')
|
149
|
+
addr
|
51
150
|
end
|
52
151
|
|
53
|
-
def
|
152
|
+
def legacy_payload(skip_time)
|
54
153
|
p = ''
|
55
154
|
p << [time].pack('V') unless skip_time
|
56
|
-
|
57
|
-
p << [services].pack('Q') <<
|
155
|
+
ip = addr.ipv4? ? addr.ipv4_mapped : addr
|
156
|
+
p << [services].pack('Q') << ip.hton << [port].pack('n')
|
157
|
+
end
|
158
|
+
|
159
|
+
def v2_payload
|
160
|
+
p = [time].pack('V')
|
161
|
+
p << Bitcoin.pack_var_int(services)
|
162
|
+
p << [net].pack('C')
|
163
|
+
case net
|
164
|
+
when NETWORK_ID[:ipv4]
|
165
|
+
p << Bitcoin.pack_var_int(4)
|
166
|
+
p << addr.to_i.to_s(16).htb
|
167
|
+
when NETWORK_ID[:ipv6]
|
168
|
+
p << Bitcoin.pack_var_int(16)
|
169
|
+
p << addr.hton
|
170
|
+
when NETWORK_ID[:tor_v2]
|
171
|
+
p << Bitcoin.pack_var_int(10)
|
172
|
+
when NETWORK_ID[:tor_v3]
|
173
|
+
p << Bitcoin.pack_var_int(32)
|
174
|
+
when NETWORK_ID[:i2p]
|
175
|
+
p << Bitcoin.pack_var_int(32)
|
176
|
+
when NETWORK_ID[:cjdns]
|
177
|
+
p << Bitcoin.pack_var_int(16)
|
178
|
+
end
|
179
|
+
p << [port].pack('n')
|
180
|
+
p
|
58
181
|
end
|
59
182
|
|
60
183
|
end
|
data/lib/bitcoin/message/tx.rb
CHANGED
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'
|
@@ -44,6 +46,8 @@ module Bitcoin
|
|
44
46
|
autoload :CFCheckpt, 'bitcoin/message/cfcheckpt'
|
45
47
|
autoload :CFilter, 'bitcoin/message/cfilter'
|
46
48
|
autoload :CFHeaders, 'bitcoin/message/cfheaders'
|
49
|
+
autoload :SendAddrV2, 'bitcoin/message/send_addr_v2'
|
50
|
+
autoload :AddrV2, 'bitcoin/message/addr_v2'
|
47
51
|
|
48
52
|
USER_AGENT = "/bitcoinrb:#{Bitcoin::VERSION}/"
|
49
53
|
|
@@ -73,5 +77,73 @@ module Bitcoin
|
|
73
77
|
compact_witness: 70015
|
74
78
|
}
|
75
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
|
+
|
76
148
|
end
|
77
149
|
end
|