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.



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
# File 'lib/bitcoin/psbt/input.rb', line 30

def self.parse_from_buf(buf)
  input = self.new
  until buf.eof?
    key_len = Bitcoin.unpack_var_int_from_io(buf)
    break if key_len == 0
    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.parse_from_payload(key, 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
  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)


118
119
120
121
122
# File 'lib/bitcoin/psbt/input.rb', line 118

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:



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/bitcoin/psbt/input.rb', line 149

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)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/bitcoin/psbt/input.rb', line 127

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 = unknowns.merge(psbi.unknowns)
  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

#sane?Boolean

Sanity check

Returns:

  • (Boolean)


108
109
110
111
112
113
# File 'lib/bitcoin/psbt/input.rb', line 108

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

#to_payloadObject



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/bitcoin/psbt/input.rb', line 89

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.to_even_length_hex.htb
  payload
end