bitcoinrb 0.2.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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