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

Returns a new instance of 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.

Parameters:

  • buf (StringIO)

    psbt buffer.

Returns:

  • (Bitcoin::PSBTInput)

    psbt input.

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.

Parameters:

  • pubkey (String)

    a public key with hex format.

  • sig (String)

    a signature.

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.' unless 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.

Returns:



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.

Parameters:

  • psbi (Bitcoin::PSBT::Input)

    PSBT input to be combined which must have same property in PSBT Input.

Returns:

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.' unless 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.

Parameters:

Returns:

  • (Boolean)


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

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)

    return true if 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.

Returns:

  • (Boolean)


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.

Returns:

  • (Boolean)


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