bitcoinrb 0.2.9 → 0.5.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -2
  4. data/README.md +7 -6
  5. data/bitcoinrb.gemspec +4 -4
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +33 -1
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_header.rb +2 -0
  10. data/lib/bitcoin/chain_params.rb +0 -8
  11. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  12. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  13. data/lib/bitcoin/constants.rb +3 -10
  14. data/lib/bitcoin/descriptor.rb +147 -0
  15. data/lib/bitcoin/ext.rb +5 -0
  16. data/lib/bitcoin/ext/json_parser.rb +46 -0
  17. data/lib/bitcoin/ext_key.rb +19 -4
  18. data/lib/bitcoin/key.rb +9 -5
  19. data/lib/bitcoin/key_path.rb +12 -5
  20. data/lib/bitcoin/message.rb +7 -0
  21. data/lib/bitcoin/message/base.rb +1 -0
  22. data/lib/bitcoin/message/cf_parser.rb +16 -0
  23. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  24. data/lib/bitcoin/message/cfheaders.rb +40 -0
  25. data/lib/bitcoin/message/cfilter.rb +35 -0
  26. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  27. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  28. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  29. data/lib/bitcoin/message/network_addr.rb +31 -12
  30. data/lib/bitcoin/message/version.rb +14 -22
  31. data/lib/bitcoin/mnemonic.rb +5 -5
  32. data/lib/bitcoin/network/peer.rb +12 -11
  33. data/lib/bitcoin/network/peer_discovery.rb +3 -1
  34. data/lib/bitcoin/node/cli.rb +14 -10
  35. data/lib/bitcoin/node/spv.rb +1 -1
  36. data/lib/bitcoin/out_point.rb +14 -7
  37. data/lib/bitcoin/payment_code.rb +92 -0
  38. data/lib/bitcoin/psbt.rb +3 -1
  39. data/lib/bitcoin/psbt/input.rb +7 -16
  40. data/lib/bitcoin/psbt/tx.rb +18 -12
  41. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  42. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  43. data/lib/bitcoin/script/script.rb +18 -10
  44. data/lib/bitcoin/script/script_interpreter.rb +3 -5
  45. data/lib/bitcoin/secp256k1.rb +1 -0
  46. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  47. data/lib/bitcoin/secp256k1/ruby.rb +4 -35
  48. data/lib/bitcoin/slip39.rb +93 -0
  49. data/lib/bitcoin/slip39/share.rb +122 -0
  50. data/lib/bitcoin/slip39/sss.rb +245 -0
  51. data/lib/bitcoin/slip39/wordlist/english.txt +1024 -0
  52. data/lib/bitcoin/store.rb +2 -1
  53. data/lib/bitcoin/store/chain_entry.rb +1 -0
  54. data/lib/bitcoin/store/db/level_db.rb +2 -2
  55. data/lib/bitcoin/store/utxo_db.rb +226 -0
  56. data/lib/bitcoin/tx.rb +6 -10
  57. data/lib/bitcoin/tx_in.rb +4 -5
  58. data/lib/bitcoin/util.rb +29 -1
  59. data/lib/bitcoin/version.rb +1 -1
  60. data/lib/bitcoin/wallet.rb +1 -0
  61. data/lib/bitcoin/wallet/account.rb +1 -0
  62. data/lib/bitcoin/wallet/base.rb +3 -3
  63. data/lib/bitcoin/wallet/db.rb +1 -1
  64. data/lib/bitcoin/wallet/master_key.rb +1 -0
  65. data/lib/bitcoin/wallet/utxo.rb +37 -0
  66. metadata +45 -26
@@ -1,3 +1,5 @@
1
+ require 'ipaddr'
2
+
1
3
  module Bitcoin
2
4
  module Message
3
5
 
@@ -12,30 +14,47 @@ module Bitcoin
12
14
  # The services the node advertised in its version message.
13
15
  attr_accessor :services
14
16
 
15
- attr_accessor :ip
17
+ attr_accessor :ip_addr # IPAddr
16
18
 
17
19
  attr_accessor :port
18
20
 
19
- def initialize
20
- @time = Time.now.to_i
21
- @services = Bitcoin::Message::SERVICE_FLAGS[:network]
21
+ attr_reader :skip_time
22
+
23
+ def initialize(ip: '127.0.0.1', port: Bitcoin.chain_params.default_port, services: DEFAULT_SERVICE_FLAGS, time: Time.now.to_i)
24
+ @time = time
25
+ @ip_addr = IPAddr.new(ip)
26
+ @port = port
27
+ @services = services
22
28
  end
23
29
 
24
30
  def self.parse_from_payload(payload)
25
31
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
26
- addr = new
27
- addr.time = buf.read(4).unpack('V').first
32
+ has_time = buf.size > 26
33
+ addr = new(time: nil)
34
+ addr.time = buf.read(4).unpack('V').first if has_time
28
35
  addr.services = buf.read(8).unpack('Q').first
29
- ip = IPAddr::new_ntoh(buf.read(16))
30
- addr.ip = ip.ipv4_mapped? ? ip.native : ip.to_s
36
+ addr.ip_addr = IPAddr::new_ntoh(buf.read(16))
31
37
  addr.port = buf.read(2).unpack('n').first
32
38
  addr
33
39
  end
34
40
 
35
- def to_payload
36
- ip_addr = IPAddr.new (ip)
37
- ip_addr = ip_addr.ipv4_mapped if ip_addr.ipv4?
38
- [time, services].pack('VQ') << ip_addr.hton << [port].pack('n')
41
+ def self.local_addr
42
+ addr = new
43
+ addr.ip_addr = IPAddr.new('127.0.0.1')
44
+ addr.port = Bitcoin.chain_params.default_port
45
+ addr.services = DEFAULT_SERVICE_FLAGS
46
+ addr
47
+ end
48
+
49
+ def ip
50
+ ip_addr.ipv4_mapped? ? ip_addr.native : ip_addr.to_s
51
+ end
52
+
53
+ def to_payload(skip_time = false)
54
+ p = ''
55
+ 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')
39
58
  end
40
59
 
41
60
  end
@@ -22,8 +22,8 @@ module Bitcoin
22
22
  @version = Bitcoin.chain_params.protocol_version
23
23
  @services = DEFAULT_SERVICE_FLAGS
24
24
  @timestamp = Time.now.to_i
25
- @local_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
26
- @remote_addr = "127.0.0.1:#{Bitcoin.chain_params.default_port}"
25
+ @local_addr = NetworkAddr.local_addr
26
+ @remote_addr = NetworkAddr.local_addr
27
27
  @nonce = SecureRandom.random_number(0xffffffffffffffff)
28
28
  @user_agent = Bitcoin::Message::USER_AGENT
29
29
  @start_height = 0
@@ -32,13 +32,13 @@ module Bitcoin
32
32
  end
33
33
 
34
34
  def self.parse_from_payload(payload)
35
- version, services, timestamp, remote_addr, local_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
35
+ version, services, timestamp, local_addr, remote_addr, nonce, rest = payload.unpack('VQQa26a26Qa*')
36
36
  v = new
37
37
  v.version = version
38
38
  v.services = services
39
39
  v.timestamp = timestamp
40
- v.remote_addr = v.unpack_addr(remote_addr)
41
- v.local_addr = v.unpack_addr(local_addr)
40
+ v.local_addr = NetworkAddr.parse_from_payload(local_addr)
41
+ v.remote_addr = NetworkAddr.parse_from_payload(remote_addr)
42
42
  v.nonce = nonce
43
43
  user_agent, rest = unpack_var_string(rest)
44
44
  start_height, rest = rest.unpack('Va*')
@@ -51,8 +51,8 @@ module Bitcoin
51
51
  def to_payload
52
52
  [
53
53
  [version, services, timestamp].pack('VQQ'),
54
- pack_addr(local_addr),
55
- pack_addr(remote_addr),
54
+ local_addr.to_payload(true),
55
+ remote_addr.to_payload(true),
56
56
  [nonce].pack('Q'),
57
57
  pack_var_string(user_agent),
58
58
  [start_height].pack('V'),
@@ -60,25 +60,17 @@ module Bitcoin
60
60
  ].join
61
61
  end
62
62
 
63
- def pack_addr(addr)
64
- separator = addr.rindex(':')
65
- ip = addr[0...separator]
66
- port = addr[separator + 1..-1].to_i
67
- ip_addr = IPAddr.new(ip)
68
- ip_addr = ip_addr.ipv4_mapped if ip_addr.ipv4?
69
- [1].pack('Q') << ip_addr.hton << [port].pack('n')
70
- # [[1].pack('Q'), "\x00" * 10, "\xFF\xFF", sockaddr[4...8], sockaddr[2...4]].join
71
- end
72
-
73
- def unpack_addr(addr)
74
- host, port = addr.unpack('x8x12a4n')
75
- "#{host.unpack('C*').join('.')}:#{port}"
76
- end
77
-
78
63
  def unpack_relay_field(payload)
79
64
  ( version >= 70001 && payload ) ? unpack_boolean(payload) : [ true, nil ]
80
65
  end
81
66
 
67
+ # Check whether +service_flag+ support this version.
68
+ # @param [Integer] service_flag the service flags.
69
+ # @return [Boolean] whether support +service_flag+
70
+ def support?(service_flag)
71
+ (services & service_flag) != 0
72
+ end
73
+
82
74
  end
83
75
  end
84
76
  end
@@ -6,11 +6,11 @@ module Bitcoin
6
6
 
7
7
  WORD_DIR = "#{__dir__}/mnemonic/wordlist"
8
8
 
9
- attr_reader :word_list
9
+ attr_reader :language
10
10
 
11
- def initialize(word_list)
12
- raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(word_list)
13
- @word_list = word_list
11
+ def initialize(language)
12
+ raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(language)
13
+ @language = language
14
14
  end
15
15
 
16
16
  # get support language list
@@ -69,7 +69,7 @@ module Bitcoin
69
69
 
70
70
  # load word list contents
71
71
  def load_words
72
- File.readlines("#{WORD_DIR}/#{word_list}.txt").map(&:strip)
72
+ File.readlines("#{WORD_DIR}/#{language}.txt").map(&:strip)
73
73
  end
74
74
 
75
75
  end
@@ -50,7 +50,8 @@ module Bitcoin
50
50
  @bytes_recv = 0
51
51
  @relay = configuration.conf[:relay]
52
52
  current_height = @chain.latest_block.height
53
- @local_version = Bitcoin::Message::Version.new(remote_addr: addr, start_height: current_height, relay: @relay)
53
+ remote_addr = Bitcoin::Message::NetworkAddr.new(ip: host, port: port, time: nil)
54
+ @local_version = Bitcoin::Message::Version.new(remote_addr: remote_addr, start_height: current_height, relay: @relay)
54
55
  end
55
56
 
56
57
  def connect
@@ -82,10 +83,15 @@ module Bitcoin
82
83
 
83
84
  def post_handshake
84
85
  @connected = true
85
- pool.handle_new_peer(self)
86
- # require remote peer to use headers message instead fo inv message.
87
- conn.send_message(Bitcoin::Message::SendHeaders.new)
88
- EM.add_periodic_timer(PING_INTERVAL) {send_ping}
86
+ if remote_version.support?(Bitcoin::Message::SERVICE_FLAGS[:bloom])
87
+ pool.handle_new_peer(self)
88
+ # require remote peer to use headers message instead fo inv message.
89
+ conn.send_message(Bitcoin::Message::SendHeaders.new)
90
+ EM.add_periodic_timer(PING_INTERVAL) {send_ping}
91
+ else
92
+ close("peer does not support NODE_BLOOM.")
93
+ pool.pending_peers.delete(self)
94
+ end
89
95
  end
90
96
 
91
97
  # start block header download
@@ -164,12 +170,7 @@ module Bitcoin
164
170
  # @return [Bitcoin::Message::NetworkAddr]
165
171
  def to_network_addr
166
172
  v = remote_version
167
- addr = Bitcoin::Message::NetworkAddr.new
168
- addr.time = v.timestamp
169
- addr.services = v.services
170
- addr.ip = host
171
- addr.port = port
172
- addr
173
+ Bitcoin::Message::NetworkAddr.new(ip: host, port: port, services: v.services, time: v.timestamp)
173
174
  end
174
175
 
175
176
  # send +addr+ message to remote peer
@@ -30,7 +30,9 @@ module Bitcoin
30
30
  logger.debug 'discover peer address from DNS seeds.'
31
31
  dns_seeds.map { |seed|
32
32
  begin
33
- Socket.getaddrinfo(seed, Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
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
34
36
  rescue SocketError => e
35
37
  logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
36
38
  nil
@@ -1,4 +1,4 @@
1
- require 'rest-client'
1
+ require 'net/http'
2
2
  require 'thor'
3
3
  require 'json'
4
4
 
@@ -92,15 +92,19 @@ module Bitcoin
92
92
  :id => 'jsonrpc'
93
93
  }
94
94
  begin
95
- RestClient::Request.execute(method: :post, url: config.server_url, payload: data.to_json,
96
- headers: {content_type: :json}) do |response, request, result|
97
- return false if !result.kind_of?(Net::HTTPSuccess) && response.empty?
98
- begin
99
- json = JSON.parse(response.to_str)
100
- puts JSON.pretty_generate(json)
101
- rescue Exception
102
- puts response.to_str
103
- end
95
+ uri = URI.parse(config.server_url)
96
+ http = Net::HTTP.new(uri.hostname, uri.port)
97
+ http.use_ssl = uri.scheme === "https"
98
+ request = Net::HTTP::Post.new('/')
99
+ request.content_type = 'application/json'
100
+ request.body = data.to_json
101
+ response = http.request(request)
102
+ body = response.body
103
+ begin
104
+ json = JSON.parse(body.to_str)
105
+ puts JSON.pretty_generate(json)
106
+ rescue Exception
107
+ puts body.to_str
104
108
  end
105
109
  rescue Exception => e
106
110
  puts e.message
@@ -45,7 +45,7 @@ module Bitcoin
45
45
  # broadcast a transaction
46
46
  def broadcast(tx)
47
47
  pool.broadcast(tx)
48
- logger.debug "broadcast tx: #{tx.to_payload.bth}"
48
+ logger.debug "broadcast tx: #{tx.to_hex}"
49
49
  end
50
50
 
51
51
  # add filter element to bloom filter.
@@ -3,14 +3,16 @@ module Bitcoin
3
3
  # outpoint class
4
4
  class OutPoint
5
5
 
6
+ include Bitcoin::HexConverter
7
+
6
8
  COINBASE_HASH = '0000000000000000000000000000000000000000000000000000000000000000'
7
9
  COINBASE_INDEX = 4294967295
8
10
 
9
- attr_reader :hash
11
+ attr_reader :tx_hash
10
12
  attr_reader :index
11
13
 
12
- def initialize(hash, index = -1)
13
- @hash = hash
14
+ def initialize(tx_hash, index = -1)
15
+ @tx_hash = tx_hash
14
16
  @index = index
15
17
  end
16
18
 
@@ -19,11 +21,11 @@ module Bitcoin
19
21
  end
20
22
 
21
23
  def coinbase?
22
- hash == COINBASE_HASH && index == COINBASE_INDEX
24
+ tx_hash == COINBASE_HASH && index == COINBASE_INDEX
23
25
  end
24
26
 
25
27
  def to_payload
26
- [hash.htb, index].pack('a32V')
28
+ [tx_hash.htb, index].pack('a32V')
27
29
  end
28
30
 
29
31
  def self.create_coinbase_outpoint
@@ -31,12 +33,17 @@ module Bitcoin
31
33
  end
32
34
 
33
35
  def valid?
34
- index >= 0 && (!coinbase? && hash != COINBASE_HASH)
36
+ index >= 0 && (!coinbase? && tx_hash != COINBASE_HASH)
35
37
  end
36
38
 
37
39
  # convert hash to txid
38
40
  def txid
39
- hash.rhex
41
+ tx_hash.rhex
42
+ end
43
+
44
+ def to_s
45
+ return "[#{index}]" unless tx_hash
46
+ "#{txid}[#{index}]"
40
47
  end
41
48
 
42
49
  end
@@ -0,0 +1,92 @@
1
+ module Bitcoin
2
+
3
+ # BIP47 payment code
4
+ class PaymentCode < ExtKey
5
+
6
+ include Bitcoin::HexConverter
7
+
8
+ attr_accessor :x_value
9
+ attr_accessor :sign
10
+
11
+ VERSION_BYTE = '47'
12
+ SUPPORT_VERSIONS = ['01']
13
+ SUPPORT_SIGNS = ['02', '03']
14
+
15
+ def initialize
16
+ @version = '01'
17
+ @features_bits = '00'
18
+ @reserve_field = '0' * 26
19
+ end
20
+
21
+ # generate master key from seed.
22
+ # @params [String] seed a seed data with hex format.
23
+ def self.generate_master(seed)
24
+ master_ext_key = super.derive(47, harden=true).derive(0, harden=true).derive(0, harden=true)
25
+ compressed_pubkey = master_ext_key.pub
26
+
27
+ payment_code = PaymentCode.new
28
+ payment_code.depth = master_ext_key.depth
29
+ payment_code.key = master_ext_key.key
30
+ payment_code.sign = compressed_pubkey[0..1]
31
+ payment_code.x_value = compressed_pubkey[2..-1]
32
+ payment_code.chain_code = master_ext_key.chain_code
33
+ payment_code
34
+ end
35
+
36
+ # Base58 encoded payment code
37
+ def to_base58
38
+ payment_code_with_version_byte = VERSION_BYTE + to_hex
39
+ Bitcoin::Base58.encode(payment_code_with_version_byte + Bitcoin.calc_checksum(payment_code_with_version_byte))
40
+ end
41
+
42
+ # serialize payment code
43
+ def to_payload
44
+ @version.htb << @features_bits.htb << @sign.htb << @x_value.htb << @chain_code << @reserve_field.htb
45
+ end
46
+
47
+ # get notification address
48
+ def notification_address
49
+ ext_pubkey.derive(0).addr
50
+ end
51
+
52
+ # decode base58 encoded payment code
53
+ # @params [String] base58_payment_code base58 encoded payment code
54
+ def self.from_base58(base58_payment_code)
55
+ hex = Bitcoin::Base58.decode(base58_payment_code)
56
+ version = hex[2..3]
57
+ sign = hex[6..7]
58
+ public_key = hex[8..71]
59
+ payment_code = hex[0...-8]
60
+
61
+ raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
62
+ raise ArgumentError, 'invalid version' unless PaymentCode.support_version?(version)
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]
66
+
67
+ x_value = payment_code[8..71]
68
+ chain_code_hex = payment_code[72..135]
69
+
70
+ payment_code_pubkey = PaymentCode.new
71
+ payment_code_pubkey.depth = 3
72
+ payment_code_pubkey.sign = sign
73
+ payment_code_pubkey.x_value = x_value
74
+ payment_code_pubkey.chain_code = [chain_code_hex].pack('H*')
75
+
76
+ payment_code_pubkey.to_payload
77
+ end
78
+
79
+ # check whether +version+ is supported version bytes.
80
+ def self.support_version?(version)
81
+ SUPPORT_VERSIONS.include?(version)
82
+ end
83
+
84
+ # check whether +sign+ is supported version bytes.
85
+ def self.support_sign?(sign)
86
+ SUPPORT_SIGNS.include?(sign)
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
@@ -12,13 +12,15 @@ module Bitcoin
12
12
 
13
13
  # constants for PSBT
14
14
  PSBT_MAGIC_BYTES = 0x70736274
15
- PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00, xpub: 0x01}
15
+ PSBT_GLOBAL_TYPES = {unsigned_tx: 0x00, xpub: 0x01, ver: 0xfb}
16
16
  PSBT_IN_TYPES = {non_witness_utxo: 0x00, witness_utxo: 0x01, partial_sig: 0x02,
17
17
  sighash: 0x03, redeem_script: 0x04, witness_script: 0x05,
18
18
  bip32_derivation: 0x06, script_sig: 0x07, script_witness: 0x08}
19
19
  PSBT_OUT_TYPES = {redeem_script: 0x00, witness_script: 0x01, bip32_derivation: 0x02}
20
20
  PSBT_SEPARATOR = 0x00
21
21
 
22
+ SUPPORT_VERSION = 0
23
+
22
24
  module_function
23
25
 
24
26
  def self.serialize_to_vector(key_type, key: nil, value: nil)
@@ -93,7 +93,8 @@ module Bitcoin
93
93
 
94
94
  def to_payload
95
95
  payload = ''
96
- payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value: non_witness_utxo.to_payload) if non_witness_utxo
96
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value:
97
+ (witness_utxo && valid_witness_input?) ? non_witness_utxo.serialize_old_format : non_witness_utxo.to_payload) if non_witness_utxo
97
98
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
98
99
  if final_script_sig.nil? && final_script_witness.nil?
99
100
  payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
@@ -109,15 +110,6 @@ module Bitcoin
109
110
  payload
110
111
  end
111
112
 
112
- # Sanity check
113
- # @return [Boolean]
114
- def sane?
115
- return false if non_witness_utxo && witness_utxo
116
- return false if witness_script && witness_utxo.nil?
117
- return false if final_script_witness && witness_utxo.nil?
118
- true
119
- end
120
-
121
113
  # Check whether input's scriptPubkey is correct witness.
122
114
  # @return [Boolean]
123
115
  def valid_witness_input?
@@ -141,7 +133,6 @@ module Bitcoin
141
133
  # @param [Bitcoin::TxOut] utxo utxo object which input refers.
142
134
  # @return [Boolean]
143
135
  def ready_to_sign?(utxo)
144
- return false unless sane?
145
136
  return valid_witness_input? if witness_utxo
146
137
  valid_non_witness_input?(utxo) # non_witness_utxo
147
138
  end
@@ -156,7 +147,7 @@ module Bitcoin
156
147
  # @param [String] pubkey a public key with hex format.
157
148
  # @param [String] sig a signature.
158
149
  def add_sig(pubkey, sig)
159
- raise ArgumentError, 'The sighash in signature is invalid.' unless sig.unpack('C*')[-1] == sighash_type
150
+ raise ArgumentError, 'The sighash in signature is invalid.' if sighash_type && sig.unpack('C*')[-1] != sighash_type
160
151
  raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if partial_sigs[pubkey]
161
152
  partial_sigs[pubkey] = sig
162
153
  end
@@ -168,7 +159,7 @@ module Bitcoin
168
159
  raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Input.' unless psbi.is_a?(Bitcoin::PSBT::Input)
169
160
  raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo == psbi.non_witness_utxo
170
161
  raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo == psbi.witness_utxo
171
- raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' unless sighash_type == psbi.sighash_type
162
+ raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' if sighash_type && psbi.sighash_type && sighash_type != psbi.sighash_type
172
163
  raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless redeem_script == psbi.redeem_script
173
164
  raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless witness_script == psbi.witness_script
174
165
  combined = Bitcoin::PSBT::Input.new(non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
@@ -177,8 +168,8 @@ module Bitcoin
177
168
  combined.witness_script = witness_script
178
169
  combined.sighash_type = sighash_type
179
170
  sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
180
- redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script && redeem_script.multisig?
181
- witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script && witness_script.multisig?
171
+ redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script&.multisig?
172
+ witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script&.multisig?
182
173
  combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
183
174
  combined
184
175
  end
@@ -190,7 +181,7 @@ module Bitcoin
190
181
  if non_witness_utxo
191
182
  self.final_script_sig = Bitcoin::Script.new << Bitcoin::Opcodes::OP_0 if redeem_script.multisig?
192
183
  partial_sigs.values.each {|sig|final_script_sig << sig}
193
- final_script_sig << redeem_script.to_payload.bth
184
+ final_script_sig << redeem_script.to_hex
194
185
  self.partial_sigs = {}
195
186
  self.hd_key_paths = {}
196
187
  self.redeem_script = nil