bitcoinrb 0.3.2 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.rspec_parallel +2 -0
  4. data/.ruby-version +1 -1
  5. data/README.md +17 -6
  6. data/bitcoinrb.gemspec +9 -8
  7. data/exe/bitcoinrbd +5 -0
  8. data/lib/bitcoin.rb +37 -19
  9. data/lib/bitcoin/bip85_entropy.rb +111 -0
  10. data/lib/bitcoin/block_filter.rb +14 -0
  11. data/lib/bitcoin/block_header.rb +2 -0
  12. data/lib/bitcoin/chain_params.rb +9 -8
  13. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  14. data/lib/bitcoin/chainparams/signet.yml +39 -0
  15. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  16. data/lib/bitcoin/constants.rb +44 -10
  17. data/lib/bitcoin/descriptor.rb +1 -1
  18. data/lib/bitcoin/errors.rb +19 -0
  19. data/lib/bitcoin/ext.rb +6 -0
  20. data/lib/bitcoin/ext/array_ext.rb +22 -0
  21. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  22. data/lib/bitcoin/ext/json_parser.rb +46 -0
  23. data/lib/bitcoin/ext_key.rb +51 -20
  24. data/lib/bitcoin/key.rb +89 -30
  25. data/lib/bitcoin/key_path.rb +12 -5
  26. data/lib/bitcoin/message.rb +79 -0
  27. data/lib/bitcoin/message/addr_v2.rb +34 -0
  28. data/lib/bitcoin/message/base.rb +17 -0
  29. data/lib/bitcoin/message/cf_parser.rb +16 -0
  30. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  31. data/lib/bitcoin/message/cfheaders.rb +40 -0
  32. data/lib/bitcoin/message/cfilter.rb +35 -0
  33. data/lib/bitcoin/message/fee_filter.rb +1 -1
  34. data/lib/bitcoin/message/filter_load.rb +3 -3
  35. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  36. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  37. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  38. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  39. data/lib/bitcoin/message/inventory.rb +1 -1
  40. data/lib/bitcoin/message/merkle_block.rb +1 -1
  41. data/lib/bitcoin/message/network_addr.rb +141 -18
  42. data/lib/bitcoin/message/ping.rb +1 -1
  43. data/lib/bitcoin/message/pong.rb +1 -1
  44. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  45. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  46. data/lib/bitcoin/message/tx.rb +1 -1
  47. data/lib/bitcoin/message/version.rb +7 -0
  48. data/lib/bitcoin/message_sign.rb +47 -0
  49. data/lib/bitcoin/mnemonic.rb +7 -7
  50. data/lib/bitcoin/network/peer.rb +9 -4
  51. data/lib/bitcoin/network/peer_discovery.rb +1 -1
  52. data/lib/bitcoin/node/cli.rb +14 -10
  53. data/lib/bitcoin/node/configuration.rb +3 -1
  54. data/lib/bitcoin/node/spv.rb +9 -1
  55. data/lib/bitcoin/opcodes.rb +14 -1
  56. data/lib/bitcoin/out_point.rb +2 -0
  57. data/lib/bitcoin/payment_code.rb +92 -0
  58. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  59. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  60. data/lib/bitcoin/psbt/input.rb +9 -18
  61. data/lib/bitcoin/psbt/output.rb +1 -1
  62. data/lib/bitcoin/psbt/tx.rb +12 -17
  63. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  64. data/lib/bitcoin/rpc/request_handler.rb +5 -5
  65. data/lib/bitcoin/script/script.rb +96 -39
  66. data/lib/bitcoin/script/script_error.rb +27 -1
  67. data/lib/bitcoin/script/script_interpreter.rb +166 -66
  68. data/lib/bitcoin/script/tx_checker.rb +62 -14
  69. data/lib/bitcoin/secp256k1.rb +1 -0
  70. data/lib/bitcoin/secp256k1/native.rb +184 -17
  71. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  72. data/lib/bitcoin/secp256k1/ruby.rb +112 -56
  73. data/lib/bitcoin/sighash_generator.rb +156 -0
  74. data/lib/bitcoin/store.rb +1 -0
  75. data/lib/bitcoin/store/chain_entry.rb +1 -0
  76. data/lib/bitcoin/store/utxo_db.rb +226 -0
  77. data/lib/bitcoin/taproot.rb +9 -0
  78. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  79. data/lib/bitcoin/taproot/simple_builder.rb +139 -0
  80. data/lib/bitcoin/tx.rb +34 -104
  81. data/lib/bitcoin/tx_in.rb +4 -5
  82. data/lib/bitcoin/tx_out.rb +2 -3
  83. data/lib/bitcoin/util.rb +22 -6
  84. data/lib/bitcoin/version.rb +1 -1
  85. data/lib/bitcoin/wallet.rb +1 -0
  86. data/lib/bitcoin/wallet/account.rb +2 -1
  87. data/lib/bitcoin/wallet/base.rb +2 -2
  88. data/lib/bitcoin/wallet/master_key.rb +1 -0
  89. data/lib/bitcoin/wallet/utxo.rb +37 -0
  90. metadata +86 -32
  91. data/.travis.yml +0 -11
@@ -0,0 +1,40 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfheaders message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfheaders
6
+ class CFHeaders < Base
7
+
8
+ COMMAND = 'cfheaders'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+ attr_accessor :prev_filter_header # little endian
13
+ attr_accessor :filter_hashes # little endian
14
+
15
+ def initialize(filter_type, stop_hash, prev_filter_header, filter_hashes)
16
+ @filter_type = filter_type
17
+ @stop_hash = stop_hash
18
+ @prev_filter_header = prev_filter_header
19
+ @filter_hashes = filter_hashes
20
+ end
21
+
22
+ def self.parse_from_payload(payload)
23
+ buf = StringIO.new(payload)
24
+ type = buf.read(1).unpack1("C")
25
+ hash = buf.read(32).bth
26
+ header = buf.read(32).bth
27
+ count = Bitcoin.unpack_var_int_from_io(buf)
28
+ hashes = count.times.map{buf.read(32).bth}
29
+ self.new(type, hash, header, hashes)
30
+ end
31
+
32
+ def to_payload
33
+ [filter_type].pack('C') + stop_hash.htb + prev_filter_header.htb +
34
+ Bitcoin.pack_var_int(filter_hashes.size) + filter_hashes.map(&:htb).join
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfilter message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfilter
6
+ class CFilter < Base
7
+
8
+ COMMAND = 'cfilter'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :block_hash # little endian
12
+ attr_accessor :filter # little endian
13
+
14
+ def initialize(filter_type, block_hash, filter)
15
+ @filter_type = filter_type
16
+ @block_hash = block_hash
17
+ @filter = filter
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).bth
24
+ len = Bitcoin.unpack_var_int_from_io(buf)
25
+ filter = buf.read(len).bth
26
+ self.new(type, hash, filter)
27
+ end
28
+
29
+ def to_payload
30
+ [filter_type, block_hash].pack('CH*') + Bitcoin.pack_var_string(filter.htb)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -15,7 +15,7 @@ module Bitcoin
15
15
  end
16
16
 
17
17
  def self.parse_from_payload(payload)
18
- new(payload.unpack('Q').first)
18
+ new(payload.unpack1('Q'))
19
19
  end
20
20
 
21
21
  def to_payload
@@ -23,9 +23,9 @@ module Bitcoin
23
23
  buf = StringIO.new(payload)
24
24
  filter_count = Bitcoin.unpack_var_int_from_io(buf)
25
25
  filter = buf.read(filter_count).unpack('C*')
26
- func_count = buf.read(4).unpack('V').first
27
- tweak = buf.read(4).unpack('V').first
28
- flag = buf.read(1).unpack('C').first
26
+ func_count = buf.read(4).unpack1('V')
27
+ tweak = buf.read(4).unpack1('V')
28
+ flag = buf.read(1).unpack1('C')
29
29
  FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
30
30
  end
31
31
 
@@ -0,0 +1,29 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfcheckpt message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfcheckpt
6
+ class GetCFCheckpt < Base
7
+
8
+ COMMAND = 'getcfcheckpt'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+
13
+ def initialize(filter_type, stop_hash)
14
+ @filter_type = filter_type
15
+ @stop_hash = stop_hash
16
+ end
17
+
18
+ def self.parse_from_payload(payload)
19
+ type, hash = payload.unpack('CH*')
20
+ self.new(type, hash)
21
+ end
22
+
23
+ def to_payload
24
+ [filter_type, stop_hash].pack('CH*')
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfheaders message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfheaders
6
+ class GetCFHeaders < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfheaders'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfilters message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfilters
6
+ class GetCFilters < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfilters'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -22,7 +22,7 @@ module Bitcoin
22
22
  def self.parse_from_payload(payload)
23
23
  buf = StringIO.new(payload)
24
24
  header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
25
- nonce = buf.read(8).unpack('q*').first
25
+ nonce = buf.read(8).unpack1('q*')
26
26
  short_ids_len = Bitcoin.unpack_var_int_from_io(buf)
27
27
  short_ids = short_ids_len.times.map do
28
28
  buf.read(6).reverse.bth.to_i(16)
@@ -26,7 +26,7 @@ module Bitcoin
26
26
  # parse inventory payload
27
27
  def self.parse_from_payload(payload)
28
28
  raise Error, 'invalid inventory size.' if payload.bytesize != 36
29
- identifier = payload[0..4].unpack('V').first
29
+ identifier = payload[0..4].unpack1('V')
30
30
  hash = payload[4..-1].bth # internal byte order
31
31
  new(identifier, hash)
32
32
  end
@@ -20,7 +20,7 @@ module Bitcoin
20
20
  m = new
21
21
  buf = StringIO.new(payload)
22
22
  m.header = Bitcoin::BlockHeader.parse_from_payload(buf.read(80))
23
- m.tx_count = buf.read(4).unpack('V').first
23
+ m.tx_count = buf.read(4).unpack1('V')
24
24
  hash_count = Bitcoin.unpack_var_int_from_io(buf)
25
25
  hash_count.times do
26
26
  m.hashes << buf.read(32).bth
@@ -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 :ip_addr # IPAddr
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, services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i)
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
- def self.parse_from_payload(payload)
31
- buf = payload.is_a?(String) ? StringIO.new(payload) : payload
32
- has_time = buf.size > 26
33
- addr = new(time: nil)
34
- addr.time = buf.read(4).unpack('V').first if has_time
35
- addr.services = buf.read(8).unpack('Q').first
36
- addr.ip_addr = IPAddr::new_ntoh(buf.read(16))
37
- addr.port = buf.read(2).unpack('n').first
38
- addr
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.ip_addr = IPAddr.new('127.0.0.1')
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
- def ip
50
- ip_addr.ipv4_mapped? ? ip_addr.native : ip_addr.to_s
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
51
102
  end
52
103
 
53
- def to_payload(skip_time = false)
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
150
+ end
151
+
152
+ def legacy_payload(skip_time)
54
153
  p = ''
55
154
  p << [time].pack('V') unless skip_time
56
- addr = ip_addr.ipv4? ? ip_addr.ipv4_mapped : ip_addr
57
- p << [services].pack('Q') << addr.hton << [port].pack('n')
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
@@ -14,7 +14,7 @@ module Bitcoin
14
14
  end
15
15
 
16
16
  def self.parse_from_payload(payload)
17
- new(payload.unpack('Q').first)
17
+ new(payload.unpack1('Q'))
18
18
  end
19
19
 
20
20
  def to_payload
@@ -14,7 +14,7 @@ module Bitcoin
14
14
  end
15
15
 
16
16
  def self.parse_from_payload(payload)
17
- new(payload.unpack('Q').first)
17
+ new(payload.unpack1('Q'))
18
18
  end
19
19
 
20
20
  def to_payload
@@ -0,0 +1,13 @@
1
+ module Bitcoin
2
+ module Message
3
+ class SendAddrV2 < Base
4
+
5
+ COMMAND = 'sendaddrv2'
6
+
7
+ def to_payload
8
+ ''
9
+ end
10
+
11
+ end
12
+ end
13
+ end