Class: Bitcoin::PSBT::Input

Inherits:
Object show all
Defined in:
lib/bitcoin/psbt/input.rb

Overview

Class for PSBTs which contain per-input information

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(non_witness_utxo: nil, witness_utxo: nil) ⇒ Input



19
20
21
22
23
24
25
# File 'lib/bitcoin/psbt/input.rb', line 19

def initialize(non_witness_utxo: nil, witness_utxo: nil)
  @non_witness_utxo = non_witness_utxo
  @witness_utxo = witness_utxo
  @partial_sigs = {}
  @hd_key_paths = {}
  @unknowns = {}
end

Instance Attribute Details

#final_script_sigObject

Returns the value of attribute final_script_sig.



12
13
14
# File 'lib/bitcoin/psbt/input.rb', line 12

def final_script_sig
  @final_script_sig
end

#final_script_witnessObject

Returns the value of attribute final_script_witness.



13
14
15
# File 'lib/bitcoin/psbt/input.rb', line 13

def final_script_witness
  @final_script_witness
end

#hd_key_pathsObject

Returns the value of attribute hd_key_paths.



14
15
16
# File 'lib/bitcoin/psbt/input.rb', line 14

def hd_key_paths
  @hd_key_paths
end

#non_witness_utxoObject

Bitcoin::Tx



8
9
10
# File 'lib/bitcoin/psbt/input.rb', line 8

def non_witness_utxo
  @non_witness_utxo
end

#partial_sigsObject

Returns the value of attribute partial_sigs.



15
16
17
# File 'lib/bitcoin/psbt/input.rb', line 15

def partial_sigs
  @partial_sigs
end

#redeem_scriptObject

Returns the value of attribute redeem_script.



10
11
12
# File 'lib/bitcoin/psbt/input.rb', line 10

def redeem_script
  @redeem_script
end

#sighash_typeObject

Returns the value of attribute sighash_type.



16
17
18
# File 'lib/bitcoin/psbt/input.rb', line 16

def sighash_type
  @sighash_type
end

#unknownsObject

Returns the value of attribute unknowns.



17
18
19
# File 'lib/bitcoin/psbt/input.rb', line 17

def unknowns
  @unknowns
end

#witness_scriptObject

Returns the value of attribute witness_script.



11
12
13
# File 'lib/bitcoin/psbt/input.rb', line 11

def witness_script
  @witness_script
end

#witness_utxoObject

Bitcoin::TxOut



9
10
11
# File 'lib/bitcoin/psbt/input.rb', line 9

def witness_utxo
  @witness_utxo
end

Class Method Details

.parse_from_buf(buf) ⇒ Bitcoin::PSBTInput

parse PSBT input data form buffer.

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/bitcoin/psbt/input.rb', line 30

def self.parse_from_buf(buf)
  input = self.new
  found_sep = false
  until buf.eof?
    key_len = Bitcoin.unpack_var_int_from_io(buf)
    if key_len == 0
      found_sep = true
      break
    end
    key_type = buf.read(1).unpack('C').first
    key = buf.read(key_len - 1)
    value = buf.read(Bitcoin.unpack_var_int_from_io(buf))

    case key_type
    when PSBT_IN_TYPES[:non_witness_utxo]
      raise ArgumentError, 'Invalid non-witness utxo typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input non-witness utxo already provided.' if input.non_witness_utxo
      input.non_witness_utxo = Bitcoin::Tx.parse_from_payload(value)
    when PSBT_IN_TYPES[:witness_utxo]
      raise ArgumentError, 'Invalid input witness utxo typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input witness utxo already provided.' if input.witness_utxo
      input.witness_utxo = Bitcoin::TxOut.parse_from_payload(value)
    when PSBT_IN_TYPES[:partial_sig]
      if key.size != Bitcoin::Key::PUBLIC_KEY_SIZE && key.size != Bitcoin::Key::COMPRESSED_PUBLIC_KEY_SIZE
        raise ArgumentError, 'Size of key was not the expected size for the type partial signature pubkey.'
      end
      pubkey = Bitcoin::Key.new(pubkey: key.bth)
      raise ArgumentError, 'Invalid pubkey.' unless pubkey.fully_valid_pubkey?
      raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if input.partial_sigs[pubkey.pubkey]
      input.partial_sigs[pubkey.pubkey] = value
    when PSBT_IN_TYPES[:sighash]
      raise ArgumentError, 'Invalid input sighash type typed key.' unless key_len == 1
      raise ArgumentError 'Duplicate Key, input sighash type already provided.' if input.sighash_type
      input.sighash_type = value.unpack('I').first
    when PSBT_IN_TYPES[:redeem_script]
      raise ArgumentError, 'Invalid redeemscript typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input redeemScript already provided.' if input.redeem_script
      input.redeem_script = Bitcoin::Script.parse_from_payload(value)
    when PSBT_IN_TYPES[:witness_script]
      raise ArgumentError, 'Invalid witnessscript typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input witnessScript already provided.' if input.witness_script
      input.witness_script = Bitcoin::Script.parse_from_payload(value)
    when PSBT_IN_TYPES[:bip32_derivation]
      raise ArgumentError, 'Invalid bip32 typed key.' unless key_len
      raise ArgumentError, 'Duplicate Key, pubkey derivation path already provided.' if input.hd_key_paths[key.bth]
      input.hd_key_paths[key.bth] = Bitcoin::PSBT::HDKeyPath.new(key, Bitcoin::PSBT::KeyOriginInfo.parse_from_payload(value))
    when PSBT_IN_TYPES[:script_sig]
      raise ArgumentError, 'Invalid final scriptsig typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input final scriptSig already provided.' if input.final_script_sig
      input.final_script_sig = Bitcoin::Script.parse_from_payload(value)
    when PSBT_IN_TYPES[:script_witness]
      raise ArgumentError, 'Invalid final script witness typed key.' unless key_len == 1
      raise ArgumentError, 'Duplicate Key, input final scriptWitness already provided.' if input.final_script_witness
      input.final_script_witness = Bitcoin::ScriptWitness.parse_from_payload(value)
    else
      unknown_key = ([key_type].pack('C') + key).bth
      raise ArgumentError, 'Duplicate Key, key for unknown value already provided.' if input.unknowns[unknown_key]
      input.unknowns[unknown_key] = value
    end
  end
  raise ArgumentError, 'Separator is missing at the end of an input map.' unless found_sep
  input
end

Instance Method Details

#add_sig(pubkey, sig) ⇒ Object

add signature as partial sig.

Raises:

  • (ArgumentError)


158
159
160
161
162
# File 'lib/bitcoin/psbt/input.rb', line 158

def add_sig(pubkey, sig)
  raise ArgumentError, 'The sighash in signature is invalid.' if sighash_type && sig.unpack('C*')[-1] != sighash_type
  raise ArgumentError, 'Duplicate Key, input partial signature for pubkey already provided.' if partial_sigs[pubkey]
  partial_sigs[pubkey] = sig
end

#finalize!Bitcoin::PSBT::Input

finalize input. TODO This feature is experimental and support only multisig.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/bitcoin/psbt/input.rb', line 189

def finalize!
  if non_witness_utxo
    self.final_script_sig = Bitcoin::Script.new << Bitcoin::Opcodes::OP_0 if redeem_script.multisig?
    partial_sigs.values.each {|sig|final_script_sig << sig}
    final_script_sig << redeem_script.to_payload.bth
    self.partial_sigs = {}
    self.hd_key_paths = {}
    self.redeem_script = nil
    self.sighash_type = nil
  else
    if redeem_script
      self.final_script_sig = Bitcoin::Script.parse_from_payload(Bitcoin::Script.pack_pushdata(redeem_script.to_payload))
      self.redeem_script = nil
    end
    if witness_script
      self.final_script_witness = Bitcoin::ScriptWitness.new
      final_script_witness.stack << '' if witness_script.multisig?
      partial_sigs.values.each {|sig| final_script_witness.stack << sig}
      final_script_witness.stack << witness_script.to_payload
      self.witness_script = nil
    end
    self.sighash_type = nil
    self.partial_sigs = {}
    self.hd_key_paths = {}
  end
  self
end

#merge(psbi) ⇒ Bitcoin::PSBT::Input

merge two PSBT inputs to create one PSBT.

Raises:

  • (ArgumentError)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/bitcoin/psbt/input.rb', line 167

def merge(psbi)
  raise ArgumentError, 'The argument psbt must be an instance of Bitcoin::PSBT::Input.' unless psbi.is_a?(Bitcoin::PSBT::Input)
  raise ArgumentError, 'The Partially Signed Input\'s non_witness_utxo are different.' unless non_witness_utxo == psbi.non_witness_utxo
  raise ArgumentError, 'The Partially Signed Input\'s witness_utxo are different.' unless witness_utxo == psbi.witness_utxo
  raise ArgumentError, 'The Partially Signed Input\'s sighash_type are different.' if sighash_type && psbi.sighash_type && sighash_type != psbi.sighash_type
  raise ArgumentError, 'The Partially Signed Input\'s redeem_script are different.' unless redeem_script == psbi.redeem_script
  raise ArgumentError, 'The Partially Signed Input\'s witness_script are different.' unless witness_script == psbi.witness_script
  combined = Bitcoin::PSBT::Input.new(non_witness_utxo: non_witness_utxo, witness_utxo: witness_utxo)
  combined.unknowns = Hash[unknowns.merge(psbi.unknowns).sort]
  combined.redeem_script = redeem_script
  combined.witness_script = witness_script
  combined.sighash_type = sighash_type
  sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
  redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script && redeem_script.multisig?
  witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script && witness_script.multisig?
  combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
  combined
end

#ready_to_sign?(utxo) ⇒ Boolean

Check whether the signer can sign this input.



143
144
145
146
147
# File 'lib/bitcoin/psbt/input.rb', line 143

def ready_to_sign?(utxo)
  return false unless sane?
  return valid_witness_input? if witness_utxo
  valid_non_witness_input?(utxo) # non_witness_utxo
end

#sane?Boolean

Sanity check



114
115
116
117
118
119
# File 'lib/bitcoin/psbt/input.rb', line 114

def sane?
  return false if non_witness_utxo && witness_utxo
  return false if witness_script && witness_utxo.nil?
  return false if final_script_witness && witness_utxo.nil?
  true
end

#signed?Boolean

Checks whether a PSBTInput is already signed.



151
152
153
# File 'lib/bitcoin/psbt/input.rb', line 151

def signed?
  final_script_sig || final_script_witness
end

#to_payloadObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/bitcoin/psbt/input.rb', line 94

def to_payload
  payload = ''
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value: non_witness_utxo.to_payload) if non_witness_utxo
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
  if final_script_sig.nil? && final_script_witness.nil?
    payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
    payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:sighash], value: [sighash_type].pack('I')) if sighash_type
    payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:redeem_script], value: redeem_script.to_payload) if redeem_script
    payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_script], value: witness_script.to_payload) if witness_script
    payload << hd_key_paths.values.map(&:to_payload).join
  end
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_sig], value: final_script_sig.to_payload) if final_script_sig
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:script_witness], value: final_script_witness.to_payload) if final_script_witness
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
  payload << PSBT_SEPARATOR.itb
  payload
end

#valid_non_witness_input?(utxo) ⇒ Boolean

Check whether input’s scriptPubkey is correct witness.



136
137
138
# File 'lib/bitcoin/psbt/input.rb', line 136

def valid_non_witness_input?(utxo)
  utxo.script_pubkey.p2sh? && redeem_script.to_p2sh == utxo.script_pubkey
end

#valid_witness_input?Boolean

Check whether input’s scriptPubkey is correct witness.



123
124
125
126
127
128
129
130
131
132
# File 'lib/bitcoin/psbt/input.rb', line 123

def valid_witness_input?
  return true if witness_utxo&.script_pubkey.p2wpkh? # P2WPKH
  return true if witness_utxo&.script_pubkey.p2wsh? && witness_utxo&.script_pubkey == redeem_script.to_p2wsh # P2WSH
  # segwit nested in P2SH
  if witness_utxo&.script_pubkey.p2sh? && redeem_script&.witness_program? && redeem_script.to_p2sh == witness_utxo&.script_pubkey
    return true if redeem_script.p2wpkh?# nested p2wpkh
    return true if witness_script&.to_sha256 == redeem_script.witness_data[1].bth # nested p2wsh
  end
  false
end