bitcoinrb 0.5.0 → 0.9.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.
Files changed (68) 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 +11 -1
  6. data/bitcoinrb.gemspec +7 -6
  7. data/lib/bitcoin/block_filter.rb +14 -0
  8. data/lib/bitcoin/chain_params.rb +9 -0
  9. data/lib/bitcoin/chainparams/signet.yml +39 -0
  10. data/lib/bitcoin/constants.rb +45 -4
  11. data/lib/bitcoin/descriptor.rb +1 -1
  12. data/lib/bitcoin/errors.rb +19 -0
  13. data/lib/bitcoin/ext/array_ext.rb +22 -0
  14. data/lib/bitcoin/ext/ecdsa.rb +36 -0
  15. data/lib/bitcoin/ext.rb +1 -0
  16. data/lib/bitcoin/ext_key.rb +36 -20
  17. data/lib/bitcoin/key.rb +85 -28
  18. data/lib/bitcoin/message/addr_v2.rb +34 -0
  19. data/lib/bitcoin/message/base.rb +16 -0
  20. data/lib/bitcoin/message/cfcheckpt.rb +2 -2
  21. data/lib/bitcoin/message/cfheaders.rb +1 -1
  22. data/lib/bitcoin/message/cfilter.rb +1 -1
  23. data/lib/bitcoin/message/fee_filter.rb +1 -1
  24. data/lib/bitcoin/message/filter_load.rb +3 -3
  25. data/lib/bitcoin/message/header_and_short_ids.rb +1 -1
  26. data/lib/bitcoin/message/inventory.rb +1 -1
  27. data/lib/bitcoin/message/merkle_block.rb +1 -1
  28. data/lib/bitcoin/message/network_addr.rb +141 -18
  29. data/lib/bitcoin/message/ping.rb +1 -1
  30. data/lib/bitcoin/message/pong.rb +1 -1
  31. data/lib/bitcoin/message/send_addr_v2.rb +13 -0
  32. data/lib/bitcoin/message/send_cmpct.rb +2 -2
  33. data/lib/bitcoin/message/tx.rb +1 -1
  34. data/lib/bitcoin/message.rb +72 -0
  35. data/lib/bitcoin/message_sign.rb +47 -0
  36. data/lib/bitcoin/mnemonic.rb +2 -2
  37. data/lib/bitcoin/network/peer_discovery.rb +1 -3
  38. data/lib/bitcoin/node/configuration.rb +3 -1
  39. data/lib/bitcoin/node/spv.rb +8 -0
  40. data/lib/bitcoin/opcodes.rb +14 -1
  41. data/lib/bitcoin/payment_code.rb +2 -2
  42. data/lib/bitcoin/payments/payment.pb.rb +1 -1
  43. data/lib/bitcoin/psbt/hd_key_path.rb +1 -1
  44. data/lib/bitcoin/psbt/input.rb +4 -4
  45. data/lib/bitcoin/psbt/output.rb +1 -1
  46. data/lib/bitcoin/psbt/tx.rb +14 -5
  47. data/lib/bitcoin/psbt.rb +8 -0
  48. data/lib/bitcoin/rpc/bitcoin_core_client.rb +1 -1
  49. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  50. data/lib/bitcoin/script/script.rb +80 -30
  51. data/lib/bitcoin/script/script_error.rb +27 -1
  52. data/lib/bitcoin/script/script_interpreter.rb +164 -62
  53. data/lib/bitcoin/script/tx_checker.rb +62 -14
  54. data/lib/bitcoin/secp256k1/native.rb +184 -17
  55. data/lib/bitcoin/secp256k1/ruby.rb +108 -21
  56. data/lib/bitcoin/sighash_generator.rb +157 -0
  57. data/lib/bitcoin/taproot/leaf_node.rb +23 -0
  58. data/lib/bitcoin/taproot/simple_builder.rb +155 -0
  59. data/lib/bitcoin/taproot.rb +45 -0
  60. data/lib/bitcoin/tx.rb +30 -96
  61. data/lib/bitcoin/tx_in.rb +1 -1
  62. data/lib/bitcoin/tx_out.rb +2 -3
  63. data/lib/bitcoin/util.rb +15 -6
  64. data/lib/bitcoin/version.rb +1 -1
  65. data/lib/bitcoin/wallet/account.rb +1 -1
  66. data/lib/bitcoin.rb +32 -24
  67. metadata +58 -18
  68. data/.travis.yml +0 -12
@@ -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
@@ -0,0 +1,47 @@
1
+ module Bitcoin
2
+
3
+ module MessageSign
4
+
5
+ class Error < StandardError; end
6
+
7
+ module_function
8
+
9
+ # Sign a message.
10
+ # @param [Bitcoin::Key] key Private key to sign with.
11
+ # @param [String] message The message to sign.
12
+ # @return [String] Signature, base64 encoded.
13
+ def sign_message(key, message, prefix: Bitcoin.chain_params.message_magic)
14
+ digest = message_hash(message, prefix: prefix)
15
+ compact_sig = key.sign_compact(digest)
16
+ Base64.strict_encode64(compact_sig)
17
+ end
18
+
19
+ # Verify a signed message.
20
+ # @param [String] address Signer's bitcoin address, it must refer to a public key.
21
+ # @param [String] signature The signature in base64 format.
22
+ # @param [String] message The message that was signed.
23
+ # @return [Boolean] Verification result.
24
+ def verify_message(address, signature, message, prefix: Bitcoin.chain_params.message_magic)
25
+ validate_address!(address)
26
+ sig = Base64.decode64(signature)
27
+ raise ArgumentError, 'Invalid signature length' unless sig.bytesize == Bitcoin::Key::COMPACT_SIGNATURE_SIZE
28
+ digest = message_hash(message, prefix: prefix)
29
+ pubkey = Bitcoin::Key.recover_compact(digest, sig)
30
+ return false unless pubkey
31
+ pubkey.to_p2pkh == address
32
+ end
33
+
34
+ # Hashes a message for signing and verification.
35
+ def message_hash(message, prefix: Bitcoin.chain_params.message_magic)
36
+ Bitcoin.double_sha256(Bitcoin.pack_var_string(prefix) << Bitcoin.pack_var_string(message))
37
+ end
38
+
39
+ def validate_address!(address)
40
+ raise ArgumentError, 'Invalid address' unless Bitcoin.valid_address?(address)
41
+ script = Bitcoin::Script.parse_from_addr(address)
42
+ raise ArgumentError, 'Address has no key' unless script.p2pkh?
43
+ end
44
+
45
+ private_class_method :validate_address!
46
+ end
47
+ end
@@ -39,7 +39,7 @@ module Bitcoin
39
39
  # @return [Array] the array of mnemonic word.
40
40
  def to_mnemonic(entropy)
41
41
  raise ArgumentError, 'entropy is empty.' if entropy.nil? || entropy.empty?
42
- e = entropy.htb.unpack('B*').first
42
+ e = entropy.htb.unpack1('B*')
43
43
  seed = e + checksum(e)
44
44
  mnemonic_index = seed.chars.each_slice(11).map{|i|i.join.to_i(2)}
45
45
  word_master = load_words
@@ -61,7 +61,7 @@ module Bitcoin
61
61
  # @param [String] entropy an entropy with bit string format
62
62
  # @return [String] an entropy checksum with bit string format
63
63
  def checksum(entropy)
64
- b = Bitcoin.sha256([entropy].pack('B*')).unpack('B*').first
64
+ b = Bitcoin.sha256([entropy].pack('B*')).unpack1('B*')
65
65
  b.slice(0, (entropy.length/32))
66
66
  end
67
67
 
@@ -30,9 +30,7 @@ module Bitcoin
30
30
  logger.debug 'discover peer address from DNS seeds.'
31
31
  dns_seeds.map { |seed|
32
32
  begin
33
- # x5 is a prefix for finding nodes that support NODE_BLOOM.
34
- # https://github.com/bitcoin/bitcoin/pull/8083#issuecomment-221552835
35
- Socket.getaddrinfo("x5.#{seed}", Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
33
+ Socket.getaddrinfo("#{seed}", Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
36
34
  rescue SocketError => e
37
35
  logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
38
36
  nil
@@ -4,8 +4,10 @@ module Bitcoin
4
4
  module Node
5
5
  class Configuration
6
6
 
7
- attr_reader :conf
7
+ attr_reader :conf # Hash
8
8
 
9
+ # initialize configuration
10
+ # @param [Hash] opts parameter for node.
9
11
  def initialize(opts = {})
10
12
  # TODO apply configuration file.
11
13
  opts[:network] = :mainnet unless opts[:network]
@@ -13,6 +13,14 @@ module Bitcoin
13
13
  attr_accessor :wallet
14
14
  attr_accessor :bloom
15
15
 
16
+ # Initialize spv settings
17
+ # @param [Bitcoin::Node::Configuration] configuration configuration for spv.
18
+ #
19
+ # ```ruby
20
+ # config = Bitcoin::Node::Configuration.new(network: :mainnet)
21
+ # spv = Bitcoin::Node::SPV.new(config)
22
+ # spv.run
23
+ # ````
16
24
  def initialize(configuration)
17
25
  @chain = Bitcoin::Store::SPVChain.new
18
26
  @configuration = configuration
@@ -136,6 +136,8 @@ module Bitcoin
136
136
  OP_NOP9 = 0xb8
137
137
  OP_NOP10 = 0xb9
138
138
 
139
+ OP_CHECKSIGADD = 0xba # BIP 342 opcodes (Tapscript)
140
+
139
141
  # https://en.bitcoin.it/wiki/Script#Pseudo-words
140
142
  OP_PUBKEYHASH = 0xfd
141
143
  OP_PUBKEY = 0xfe
@@ -145,6 +147,9 @@ module Bitcoin
145
147
  OPCODES_MAP = Hash[*(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map { |c| [const_get(c), c.to_s] }.flatten]
146
148
  NAME_MAP = Hash[*constants.grep(/^OP_/).map { |c| [c.to_s, const_get(c)] }.flatten]
147
149
 
150
+ OP_SUCCESSES = [0x50, 0x62, 0x89, 0x8a, 0x8d, 0x8e, (0x7e..0x81).to_a,
151
+ (0x83..0x86).to_a, (0x95..0x99).to_a, (0xbb..0xfe).to_a].flatten
152
+
148
153
  def opcode_to_name(opcode)
149
154
  return OPCODES_MAP[opcode].delete('OP_') if opcode == OP_0 || (opcode <= OP_16 && opcode >= OP_1)
150
155
  OPCODES_MAP[opcode]
@@ -156,7 +161,8 @@ module Bitcoin
156
161
  end
157
162
 
158
163
  # whether opcode is predefined opcode
159
- def defined?(opcode)
164
+ def defined?(opcode, allow_success = false)
165
+ return true if allow_success && op_success?(opcode)
160
166
  !opcode_to_name(opcode).nil?
161
167
  end
162
168
 
@@ -174,5 +180,12 @@ module Bitcoin
174
180
  nil
175
181
  end
176
182
 
183
+ # Check whether +opcode+ is OP_SUCCESSx or not?
184
+ # @param [Integer] opcode an opcode.
185
+ # @return [Boolean] if +opcode+ is OP_SUCCESSx return true, otherwise false.
186
+ def op_success?(opcode)
187
+ OP_SUCCESSES.include?(opcode)
188
+ end
189
+
177
190
  end
178
191
  end
@@ -61,8 +61,8 @@ module Bitcoin
61
61
  raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
62
62
  raise ArgumentError, 'invalid version' unless PaymentCode.support_version?(version)
63
63
  raise ArgumentError, 'invalid sign' unless PaymentCode.support_sign?(sign)
64
- raise ArgumentError, 'invalid public key' unless Bitcoin::Key.new(priv_key: nil, pubkey: sign + public_key).fully_valid_pubkey?
65
- raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
64
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless Bitcoin::Key.new(priv_key: nil, pubkey: sign + public_key).fully_valid_pubkey?
65
+ raise ArgumentError, Errors::Messages::INVALID_CHECKSUM unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
66
66
 
67
67
  x_value = payment_code[8..71]
68
68
  chain_code_hex = payment_code[72..135]
@@ -17,7 +17,7 @@ module Bitcoin
17
17
  end
18
18
 
19
19
  def transactions
20
- @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx)}
20
+ @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx, strict: true)}
21
21
  end
22
22
 
23
23
  end
@@ -12,7 +12,7 @@ module Bitcoin
12
12
  pubkey = pubkey.encoding == Encoding::ASCII_8BIT ? pubkey : pubkey.htb
13
13
  raise ArgumentError, 'Size of key was not the expected size for the type BIP32 keypath.' unless [Bitcoin::Key::PUBLIC_KEY_SIZE, Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE].include?(pubkey.bytesize)
14
14
  pubkey = Bitcoin::Key.new(pubkey: pubkey.bth)
15
- raise ArgumentError, 'Invalid pubkey' unless pubkey.fully_valid_pubkey?
15
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless pubkey.fully_valid_pubkey?
16
16
  @pubkey = pubkey.pubkey
17
17
  @info = info
18
18
  end
@@ -36,7 +36,7 @@ module Bitcoin
36
36
  found_sep = true
37
37
  break
38
38
  end
39
- key_type = buf.read(1).unpack('C').first
39
+ key_type = buf.read(1).unpack1('C')
40
40
  key = buf.read(key_len - 1)
41
41
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
42
42
 
@@ -44,7 +44,7 @@ module Bitcoin
44
44
  when PSBT_IN_TYPES[:non_witness_utxo]
45
45
  raise ArgumentError, 'Invalid non-witness utxo typed key.' unless key_len == 1
46
46
  raise ArgumentError, 'Duplicate Key, input non-witness utxo already provided.' if input.non_witness_utxo
47
- input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value)
47
+ input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value, strict: true)
48
48
  when PSBT_IN_TYPES[:witness_utxo]
49
49
  raise ArgumentError, 'Invalid input witness utxo typed key.' unless key_len == 1
50
50
  raise ArgumentError, 'Duplicate Key, input witness utxo already provided.' if input.witness_utxo
@@ -54,13 +54,13 @@ module Bitcoin
54
54
  raise ArgumentError, 'Size of key was not the expected size for the type partial signature pubkey.'
55
55
  end
56
56
  pubkey = Bitcoin::Key.new(pubkey: key.bth)
57
- raise ArgumentError, 'Invalid pubkey.' unless pubkey.fully_valid_pubkey?
57
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless pubkey.fully_valid_pubkey?
58
58
  raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if input.partial_sigs[pubkey.pubkey]
59
59
  input.partial_sigs[pubkey.pubkey] = value
60
60
  when PSBT_IN_TYPES[:sighash]
61
61
  raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
62
62
  raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
63
- input.sighash_type = value.unpack('I').first
63
+ input.sighash_type = value.unpack1('I')
64
64
  when PSBT_IN_TYPES[:redeem_script]
65
65
  raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
66
66
  raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
@@ -26,7 +26,7 @@ module Bitcoin
26
26
  found_sep = true
27
27
  break
28
28
  end
29
- key_type = buf.read(1).unpack('C').first
29
+ key_type = buf.read(1).unpack1('C')
30
30
  key = buf.read(key_len - 1)
31
31
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
32
32
  case key_type
@@ -55,7 +55,7 @@ module Bitcoin
55
55
  # @return [Bitcoin::PartiallySignedTx]
56
56
  def self.parse_from_payload(payload)
57
57
  buf = StringIO.new(payload)
58
- raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack('N').first == PSBT_MAGIC_BYTES
58
+ raise ArgumentError, 'Invalid PSBT magic bytes.' unless buf.read(4).unpack1('N') == PSBT_MAGIC_BYTES
59
59
  raise ArgumentError, 'Invalid PSBT separator.' unless buf.read(1).bth.to_i(16) == 0xff
60
60
  partial_tx = self.new
61
61
  found_sep = false
@@ -66,7 +66,7 @@ module Bitcoin
66
66
  found_sep = true
67
67
  break
68
68
  end
69
- key_type = buf.read(1).unpack('C').first
69
+ key_type = buf.read(1).unpack1('C')
70
70
  key = buf.read(key_len - 1)
71
71
  value = buf.read(Bitcoin.unpack_var_int_from_io(buf))
72
72
 
@@ -74,20 +74,20 @@ module Bitcoin
74
74
  when PSBT_GLOBAL_TYPES[:unsigned_tx]
75
75
  raise ArgumentError, 'Invalid global transaction typed key.' unless key_len == 1
76
76
  raise ArgumentError, 'Duplicate Key, unsigned tx already provided.' if partial_tx.tx
77
- partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true)
77
+ partial_tx.tx = Bitcoin::Tx.parse_from_payload(value, non_witness: true, strict: true)
78
78
  partial_tx.tx.in.each do |tx_in|
79
79
  raise ArgumentError, 'Unsigned tx does not have empty scriptSigs and scriptWitnesses.' if !tx_in.script_sig.empty? || !tx_in.script_witness.empty?
80
80
  end
81
81
  when PSBT_GLOBAL_TYPES[:xpub]
82
82
  raise ArgumentError, 'Size of key was not the expected size for the type global xpub.' unless key.size == Bitcoin::BIP32_EXTKEY_WITH_VERSION_SIZE
83
83
  xpub = Bitcoin::ExtPubkey.parse_from_payload(key)
84
- raise ArgumentError, 'Invalid pubkey.' unless xpub.key.fully_valid_pubkey?
84
+ raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless xpub.key.fully_valid_pubkey?
85
85
  raise ArgumentError, 'Duplicate key, global xpub already provided' if partial_tx.xpubs.any?{|x|x.xpub == xpub}
86
86
  info = Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value)
87
87
  raise ArgumentError, "global xpub's depth and the number of indexes not matched." unless xpub.depth == info.key_paths.size
88
88
  partial_tx.xpubs << Bitcoin::PSBT::GlobalXpub.new(xpub, info)
89
89
  when PSBT_GLOBAL_TYPES[:ver]
90
- partial_tx.version_number = value.unpack('V').first
90
+ partial_tx.version_number = value.unpack1('V')
91
91
  raise ArgumentError, "An unsupported version was detected." if SUPPORT_VERSION < partial_tx.version_number
92
92
  else
93
93
  raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if partial_tx.unknowns[key]
@@ -162,6 +162,15 @@ module Bitcoin
162
162
  Base64.strict_encode64(to_payload)
163
163
  end
164
164
 
165
+ # Store the PSBT to a file.
166
+ # @param [String] path File path to store.
167
+ def to_file(path)
168
+ raise ArgumentError, 'The file already exists' if File.exist?(path)
169
+ File.open(path, 'w') do |f|
170
+ f.write(to_payload)
171
+ end
172
+ end
173
+
165
174
  # update input key-value maps.
166
175
  # @param [Bitcoin::Tx] prev_tx previous tx reference by input.
167
176
  # @param [Bitcoin::Script] redeem_script redeem script to set input.
data/lib/bitcoin/psbt.rb CHANGED
@@ -31,6 +31,14 @@ module Bitcoin
31
31
  s << Bitcoin.pack_var_int(value.bytesize) << value
32
32
  s
33
33
  end
34
+
35
+ # Load PSBT from file.
36
+ # @param [String] path File path of PSBT.
37
+ # @return [Bitcoin::PSBT::Tx] PSBT object.
38
+ def load_from_file(path)
39
+ raise ArgumentError, 'File not found' unless File.exist?(path)
40
+ Bitcoin::PSBT::Tx.parse_from_payload(File.read(path))
41
+ end
34
42
  end
35
43
 
36
44
  end
@@ -63,7 +63,7 @@ module Bitcoin
63
63
  response = http.request(request)
64
64
  body = response.body
65
65
  response = Bitcoin::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
66
- raise response['error'].to_s if response['error']
66
+ raise response['error'].to_json if response['error']
67
67
  response['result']
68
68
  end
69
69
 
@@ -49,7 +49,7 @@ module Bitcoin
49
49
  # Returns connected peer information.
50
50
  def getpeerinfo
51
51
  node.pool.peers.map do |peer|
52
- local_addr = "#{peer.remote_version.remote_addr.ip}:18333"
52
+ local_addr = "#{peer.remote_version.remote_addr.addr_string}:18333"
53
53
  {
54
54
  id: peer.id,
55
55
  addr: "#{peer.host}:#{peer.port}",
@@ -75,7 +75,7 @@ module Bitcoin
75
75
 
76
76
  # broadcast transaction
77
77
  def sendrawtransaction(hex_tx)
78
- tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
78
+ tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true)
79
79
  # TODO check wether tx is valid
80
80
  node.broadcast(tx)
81
81
  tx.txid
@@ -84,7 +84,7 @@ module Bitcoin
84
84
  # decode tx data.
85
85
  def decoderawtransaction(hex_tx)
86
86
  begin
87
- Bitcoin::Tx.parse_from_payload(hex_tx.htb).to_h
87
+ Bitcoin::Tx.parse_from_payload(hex_tx.htb, strict: true ).to_h
88
88
  rescue Exception
89
89
  raise ArgumentError.new('TX decode failed')
90
90
  end
@@ -21,7 +21,7 @@ module Bitcoin
21
21
 
22
22
  # generate P2WPKH script
23
23
  def self.to_p2wpkh(pubkey_hash)
24
- new << WITNESS_VERSION << pubkey_hash
24
+ new << WITNESS_VERSION_V0 << pubkey_hash
25
25
  end
26
26
 
27
27
  # generate m of n multisig p2sh script
@@ -52,7 +52,7 @@ module Bitcoin
52
52
  end
53
53
 
54
54
  # generate m of n multisig script
55
- # @param [String] m the number of signatures required for multisig
55
+ # @param [Integer] m the number of signatures required for multisig
56
56
  # @param [Array] pubkeys array of public keys that compose multisig
57
57
  # @return [Script] multisig script.
58
58
  def self.to_multisig_script(m, pubkeys, sort: false)
@@ -64,7 +64,7 @@ module Bitcoin
64
64
  # @param [Script] redeem_script target redeem script
65
65
  # @param [Script] p2wsh script
66
66
  def self.to_p2wsh(redeem_script)
67
- new << WITNESS_VERSION << redeem_script.to_sha256
67
+ new << WITNESS_VERSION_V0 << redeem_script.to_sha256
68
68
  end
69
69
 
70
70
  # generate script from string.
@@ -87,17 +87,21 @@ module Bitcoin
87
87
  def self.parse_from_addr(addr)
88
88
  begin
89
89
  segwit_addr = Bech32::SegwitAddr.new(addr)
90
- raise 'Invalid hrp.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
90
+ raise ArgumentError, 'Invalid address.' unless Bitcoin.chain_params.bech32_hrp == segwit_addr.hrp
91
91
  Bitcoin::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
92
92
  rescue Exception => e
93
+ begin
93
94
  hex, addr_version = Bitcoin.decode_base58_address(addr)
95
+ rescue
96
+ raise ArgumentError, 'Invalid address.'
97
+ end
94
98
  case addr_version
95
99
  when Bitcoin.chain_params.address_version
96
100
  Bitcoin::Script.to_p2pkh(hex)
97
101
  when Bitcoin.chain_params.p2sh_version
98
102
  Bitcoin::Script.to_p2sh(hex)
99
103
  else
100
- throw e
104
+ raise ArgumentError, 'Invalid address.'
101
105
  end
102
106
  end
103
107
  end
@@ -110,25 +114,28 @@ module Bitcoin
110
114
  if opcode.pushdata?
111
115
  pushcode = opcode.ord
112
116
  packed_size = nil
117
+ if buf.eof?
118
+ s.chunks << opcode
119
+ return s
120
+ end
113
121
  len = case pushcode
114
122
  when OP_PUSHDATA1
115
123
  packed_size = buf.read(1)
116
- packed_size.unpack('C').first
124
+ packed_size.unpack1('C')
117
125
  when OP_PUSHDATA2
118
126
  packed_size = buf.read(2)
119
- packed_size.unpack('v').first
127
+ packed_size.unpack1('v')
120
128
  when OP_PUSHDATA4
121
129
  packed_size = buf.read(4)
122
- packed_size.unpack('V').first
130
+ packed_size.unpack1('V')
123
131
  else
124
- pushcode if pushcode < OP_PUSHDATA1
132
+ pushcode < OP_PUSHDATA1 ? pushcode : 0
125
133
  end
126
- if len
127
- s.chunks << [len].pack('C') if buf.eof?
128
- unless buf.eof?
129
- chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
130
- s.chunks << chunk
131
- end
134
+ if buf.eof?
135
+ s.chunks << [len].pack('C')
136
+ else buf.eof?
137
+ chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
138
+ s.chunks << chunk
132
139
  end
133
140
  else
134
141
  if Opcodes.defined?(opcode.ord)
@@ -141,8 +148,12 @@ module Bitcoin
141
148
  s
142
149
  end
143
150
 
144
- def to_payload
145
- chunks.join
151
+ # Output script payload.
152
+ # @param [Boolean] length_prefixed Flag whether the length of the payload should be given at the beginning.(default: false)
153
+ # @return [String] payload
154
+ def to_payload(length_prefixed = false)
155
+ p = chunks.join
156
+ length_prefixed ? (Bitcoin.pack_var_int(p.length) << p) : p
146
157
  end
147
158
 
148
159
  def empty?
@@ -170,27 +181,40 @@ module Bitcoin
170
181
 
171
182
  # check whether standard script.
172
183
  def standard?
173
- p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
184
+ p2pkh? | p2sh? | p2wpkh? | p2wsh? | p2tr? | multisig? | standard_op_return?
174
185
  end
175
186
 
176
- # whether this script is a P2PKH format script.
187
+ # Check whether this script is a P2PKH format script.
188
+ # @return [Boolean] if P2PKH return true, otherwise false
177
189
  def p2pkh?
178
190
  return false unless chunks.size == 5
179
191
  [OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
180
192
  (chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
181
193
  end
182
194
 
183
- # whether this script is a P2WPKH format script.
195
+ # Check whether this script is a P2WPKH format script.
196
+ # @return [Boolean] if P2WPKH return true, otherwise false
184
197
  def p2wpkh?
185
198
  return false unless chunks.size == 2
186
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 21
199
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 21
187
200
  end
188
201
 
202
+ # Check whether this script is a P2WPSH format script.
203
+ # @return [Boolean] if P2WPSH return true, otherwise false
189
204
  def p2wsh?
190
205
  return false unless chunks.size == 2
191
- chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 33
206
+ chunks[0].ord == WITNESS_VERSION_V0 && chunks[1].bytesize == 33
207
+ end
208
+
209
+ # Check whether this script is a P2TR format script.
210
+ # @return [Boolean] if P2TR return true, otherwise false
211
+ def p2tr?
212
+ return false unless chunks.size == 2
213
+ chunks[0].ord == WITNESS_VERSION_V1 && chunks[1].bytesize == 33
192
214
  end
193
215
 
216
+ # Check whether this script is a P2SH format script.
217
+ # @return [Boolean] if P2SH return true, otherwise false
194
218
  def p2sh?
195
219
  return false unless chunks.size == 3
196
220
  OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
@@ -242,7 +266,7 @@ module Bitcoin
242
266
  return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
243
267
  return false unless chunks[1].pushdata?
244
268
 
245
- if size == (chunks[1][0].unpack('C').first + 2)
269
+ if size == (chunks[1][0].unpack1('C') + 2)
246
270
  program_size = chunks[1].pushed_data.bytesize
247
271
  return program_size >= 2 && program_size <= 40
248
272
  end
@@ -343,10 +367,14 @@ module Bitcoin
343
367
  v
344
368
  else
345
369
  data = c.pushed_data
346
- if data.bytesize <= 4
347
- Script.decode_number(data.bth) # for scriptnum
370
+ if data
371
+ if data.bytesize <= 4
372
+ Script.decode_number(data.bth) # for scriptnum
373
+ else
374
+ data.bth
375
+ end
348
376
  else
349
- data.bth
377
+ c.bth
350
378
  end
351
379
  end
352
380
  else
@@ -393,8 +421,8 @@ module Bitcoin
393
421
  hex = '0' + hex unless (hex.length % 2).zero?
394
422
  v = hex.htb.reverse # change endian
395
423
 
396
- v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack('C').first & 0x80) == 0
397
- v[-1] = [v[-1].unpack('C').first | 0x80].pack('C') if negative
424
+ v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack1('C') & 0x80) == 0
425
+ v[-1] = [v[-1].unpack1('C') | 0x80].pack('C') if negative
398
426
  v.bth
399
427
  end
400
428
 
@@ -402,7 +430,7 @@ module Bitcoin
402
430
  def self.decode_number(s)
403
431
  v = s.htb.reverse
404
432
  return 0 if v.length.zero?
405
- mbs = v[0].unpack('C').first
433
+ mbs = v[0].unpack1('C')
406
434
  v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
407
435
  result = v.bth.to_i(16)
408
436
  result = -result unless (mbs & 0x80) == 0
@@ -487,7 +515,7 @@ module Bitcoin
487
515
  end
488
516
 
489
517
  def ==(other)
490
- return false unless other
518
+ return false unless other.is_a?(Script)
491
519
  chunks == other.chunks
492
520
  end
493
521
 
@@ -553,6 +581,28 @@ module Bitcoin
553
581
  segwit_addr.addr
554
582
  end
555
583
 
584
+ # Check whether push data length is valid.
585
+ # @return [Boolean] if valid return true, otherwise false.
586
+ def valid_pushdata_length?(chunk)
587
+ buf = StringIO.new(chunk)
588
+ opcode = buf.read(1).ord
589
+ offset = 1
590
+ len = case opcode
591
+ when OP_PUSHDATA1
592
+ offset += 1
593
+ buf.read(1).unpack1('C')
594
+ when OP_PUSHDATA2
595
+ offset += 2
596
+ buf.read(2).unpack1('v')
597
+ when OP_PUSHDATA4
598
+ offset += 4
599
+ buf.read(4).unpack1('V')
600
+ else
601
+ opcode
602
+ end
603
+ chunk.bytesize == len + offset
604
+ end
605
+
556
606
  end
557
607
 
558
608
  end