Commit c54b89379d00a3054ba9f45787b3317810116891

Authored by Marius Hanne
1 parent bfb350bbd3

offline tx WIP

Showing 13 changed files with 321 additions and 98 deletions Side-by-side Diff

... ... @@ -36,7 +36,7 @@
36 36 end
37 37  
38 38 opts.on("--command [HOST:PORT]",
39   - "Node command socket (default: #{options[:command]})") do |command|
  39 + "Node command socket (default: #{options[:command]})") do |command|
40 40 options[:command] = command
41 41 end
42 42  
... ... @@ -102,6 +102,40 @@
102 102 when "new"
103 103 puts "Generated new key with address: #{wallet.get_new_addr}"
104 104  
  105 +when "add"
  106 + key = {:label => ARGV[2]}
  107 + case ARGV[0]
  108 + when "pub"
  109 + k = Bitcoin::Key.new(nil, ARGV[1])
  110 + key[:key] = k
  111 + key[:addr] = k.addr
  112 + when "priv"
  113 + k = Bitcoin::Key.new(ARGV[1], nil)
  114 + k.regenerate_pubkey
  115 + key[:key] = k
  116 + key[:addr] = k.addr
  117 + when "addr"
  118 + key[:addr] = ARGV[1]
  119 + else
  120 + raise "unknown type #{ARGV[0]}"
  121 + end
  122 + wallet.add_key key
  123 +
  124 +when "label"
  125 + wallet.label(ARGV[0], ARGV[1])
  126 +
  127 +when "flag"
  128 + wallet.flag(ARGV[0], *ARGV[1].split("="))
  129 +
  130 +when "key"
  131 + key = wallet.keystore.key(ARGV[0])
  132 + puts "Label: #{key[:label]}"
  133 + puts "Address: #{key[:addr]}"
  134 + puts "Pubkey: #{key[:key].pub}"
  135 + puts "Privkey: #{key[:key].priv}" if ARGV[1] == '-p'
  136 + puts "Mine: #{key[:mine]}"
  137 +
  138 +
105 139 when "import"
106 140 if wallet.keystore.respond_to?(:import)
107 141 addr = wallet.keystore.import(cmdopts[0])
108 142  
... ... @@ -117,12 +151,10 @@
117 151  
118 152 when "list"
119 153 if cmdopts && cmdopts.size == 1
120   - unless Bitcoin.valid_address?(cmdopts[0])
121   - puts "#{cmdopts[0]} - not a valid bitcoin address"; exit
122   - end
123 154 depth = storage.get_depth
124 155 total = 0
125   - storage.get_txouts_for_address(cmdopts[0]).each do |txout|
  156 + key = wallet.keystore.key(cmdopts[0])
  157 + storage.get_txouts_for_address(key[:addr]).each do |txout|
126 158 total += txout.value
127 159 tx = txout.get_tx
128 160 blocks = depth - tx.get_block.depth rescue 0
129 161  
... ... @@ -153,9 +185,10 @@
153 185 else
154 186 puts "Wallet addresses:"
155 187 total = 0
156   - wallet.list.each do |addr, balance|
  188 + wallet.list.each do |key, balance|
157 189 total += balance
158   - puts " #{addr.ljust(34)} - #{("%.8f" % (balance / 1e8)).rjust(15)}"
  190 + icon = key[:key] && key[:key].priv ? "P" : (key[:mine] ? "M" : " ")
  191 + puts " #{icon} #{key[:label].to_s.ljust(10)} (#{key[:addr].to_s.ljust(34)}) - #{("%.8f" % (balance / 1e8)).rjust(15)}"
159 192 end
160 193 puts "Total balance: #{str_val wallet.get_balance}"
161 194 end
... ... @@ -175,6 +208,16 @@
175 208  
176 209 tx = wallet.tx(to, fee)
177 210  
  211 + if tx.is_a?(Bitcoin::Wallet::TxDP)
  212 + puts "Transaction needs to be signed by additional keys."
  213 + print "Filename to save TxDP: [./#{tx.id}.txdp] "
  214 + $stdout.flush
  215 + filename = $stdin.gets.strip
  216 + filename = "./#{tx.id}.txdp" if filename == ""
  217 + File.open(filename, "w") {|f| f.write(tx.serialize) }
  218 + exit
  219 + end
  220 +
178 221 unless tx
179 222 puts "Error creating tx."; exit
180 223 end
181 224  
... ... @@ -192,13 +235,70 @@
192 235 tx.out.each do |txout|
193 236 total -= txout.value
194 237 script = Bitcoin::Script.new(txout.pk_script)
195   - if script.is_hash160?
  238 + if script.is_pubkey?
  239 + puts "#{str_val txout.value} #{script.get_pubkey} (pubkey)"
  240 + elsif script.is_hash160?
196 241 puts "#{str_val txout.value} #{script.get_address} (address)"
197 242 elsif script.is_multisig?
198 243 puts "#{str_val txout.value} #{script.get_addresses.join(' ')} (multisig)"
199 244 end
200 245 end
201 246 puts "Fee: #{str_val total}"
  247 +
  248 + $stdout.sync = true
  249 + print "Really send transaction? (y/N) " and $stdout.flush
  250 + unless $stdin.gets.chomp.downcase == 'y'
  251 + puts "Aborted."; exit
  252 + end
  253 +
  254 + EM.run do
  255 + EM.connect(*options[:command].split(":")) do |conn|
  256 + conn.send_data(["relay_tx", tx.to_payload.unpack("H*")[0]].to_json)
  257 + def conn.receive_data(data)
  258 + (@buf ||= BufferedTokenizer.new("\x00")).extract(data).each do |packet|
  259 + res = JSON.load(packet)
  260 + puts "Transaction relayed: #{res[1]["hash"]}"
  261 + EM.stop
  262 + end
  263 + end
  264 + end
  265 + end
  266 +
  267 +when "sign"
  268 + txt = File.read(ARGV[0])
  269 + txdp = Bitcoin::Wallet::TxDP.parse(txt)
  270 + puts txdp.tx[0].to_json
  271 +
  272 + print "Really sign transaction? (y/N) " and $stdout.flush
  273 + unless $stdin.gets.chomp.downcase == 'y'
  274 + puts "Aborted."; exit
  275 + end
  276 +
  277 + txdp.sign_inputs do |tx, prev_tx, i, addr|
  278 + key = keystore.key(addr)[:key] rescue nil
  279 + next nil unless key && !key.priv.nil?
  280 + sig_hash = tx.signature_hash_for_input(i, prev_tx)
  281 + sig = key.sign(sig_hash)
  282 + script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key.pub].pack("H*"))
  283 + script_sig.unpack("H*")[0]
  284 + end
  285 + File.open(ARGV[0], "w") {|f| f.write txdp.serialize }
  286 +
  287 +when "relay"
  288 + txt = File.read(ARGV[0])
  289 + txdp = Bitcoin::Wallet::TxDP.parse(txt)
  290 + tx = txdp.tx[0]
  291 + puts tx.to_json
  292 + txdp.inputs.each_with_index do |s, i|
  293 + value, sigs = *s
  294 + tx.in[i].script_sig = [sigs[0][1]].pack("H*")
  295 + end
  296 + tx.in.each_with_index do |txin, i|
  297 + p txdp.tx.map(&:hash)
  298 + prev_tx = storage.get_tx(txin.prev_out.reverse.unpack("H*")[0])
  299 + raise "prev tx #{txin.prev_out.reverse.unpack("H*")[0]} not found" unless prev_tx
  300 + raise "signature error" unless tx.verify_input_signature(i, prev_tx)
  301 + end
202 302  
203 303 $stdout.sync = true
204 304 print "Really send transaction? (y/N) " and $stdout.flush
lib/bitcoin/protocol/tx.rb
... ... @@ -89,6 +89,11 @@
89 89  
90 90 in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
91 91 [[@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time].pack("I")].join
  92 + rescue
  93 + p $!
  94 + puts *$@
  95 + binding.pry
  96 + raise "ERR"
92 97 end
93 98  
94 99 # generate a signature hash for input +input_idx+.
lib/bitcoin/protocol/txout.rb
1   -module Bitcoin
  1 +xbmodule Bitcoin
2 2 module Protocol
3 3  
4 4 class TxOut
lib/bitcoin/storage/models.rb
... ... @@ -79,13 +79,14 @@
79 79 # Transaction output retrieved from storage.
80 80 class TxOut < Bitcoin::Protocol::TxOut
81 81  
82   - attr_reader :store, :id, :tx_id, :tx_idx
  82 + attr_reader :store, :id, :tx_id, :tx_idx, :type
83 83  
84 84 def initialize store, data
85 85 @store = store
86 86 @id = data[:id]
87 87 @tx_id = data[:tx_id]
88 88 @tx_idx = data[:tx_idx]
  89 + @type = data[:type]
89 90 end
90 91  
91 92 def hash160
lib/bitcoin/storage/sequel.rb
... ... @@ -15,6 +15,8 @@
15 15 SIDE = 1
16 16 ORPHAN = 2
17 17  
  18 + SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig]
  19 +
18 20 attr_accessor :db
19 21  
20 22 include Bitcoin::Storage::Backends::SequelMigrations
21 23  
22 24  
23 25  
... ... @@ -151,34 +153,37 @@
151 153 end
152 154  
153 155 def store_txin(tx_id, txin, idx)
154   - @db[:txin].insert({
155   - :tx_id => tx_id,
156   - :tx_idx => idx,
157   - :script_sig => txin.script_sig.to_sequel_blob,
158   - :prev_out => txin.prev_out.to_sequel_blob,
159   - :prev_out_index => txin.prev_out_index,
160   - :sequence => txin.sequence.unpack("I")[0],
161   - })
  156 + @db.transaction do
  157 + @db[:txin].insert({
  158 + :tx_id => tx_id,
  159 + :tx_idx => idx,
  160 + :script_sig => txin.script_sig.to_sequel_blob,
  161 + :prev_out => txin.prev_out.to_sequel_blob,
  162 + :prev_out_index => txin.prev_out_index,
  163 + :sequence => txin.sequence.unpack("I")[0],
  164 + })
  165 + end
162 166 end
163 167  
164 168 def store_txout(tx_id, txout, idx)
165   - txout_id = @db[:txout].insert({
166   - :tx_id => tx_id,
167   - :tx_idx => idx,
168   - :pk_script => txout.pk_script.to_sequel_blob,
169   - :value => txout.value,
170   - })
171   - script = Bitcoin::Script.new(txout.pk_script)
172   - if script.is_hash160? || script.is_pubkey?
173   - store_addr(txout_id, script.get_hash160)
174   - elsif script.is_multisig?
175   - script.get_multisig_pubkeys.map do |pubkey|
176   - store_addr(txout_id, Bitcoin.hash160(pubkey.unpack("H*")[0]))
  169 + @db.transaction do
  170 + script = Bitcoin::Script.new(txout.pk_script)
  171 + txout_id = @db[:txout].insert({
  172 + :tx_id => tx_id,
  173 + :tx_idx => idx,
  174 + :pk_script => txout.pk_script.to_sequel_blob,
  175 + :value => txout.value,
  176 + :type => SCRIPT_TYPES.index(script.type)
  177 + })
  178 + if script.is_hash160? || script.is_pubkey?
  179 + store_addr(txout_id, script.get_hash160)
  180 + elsif script.is_multisig?
  181 + script.get_multisig_pubkeys.map do |pubkey|
  182 + store_addr(txout_id, Bitcoin.hash160(pubkey.unpack("H*")[0]))
  183 + end
177 184 end
178   - else
179   - # unknown script
  185 + txout_id
180 186 end
181   - txout_id
182 187 end
183 188  
184 189 def store_addr(txout_id, hash160)
... ... @@ -297,7 +302,6 @@
297 302 tx = Bitcoin::Storage::Models::Tx.new(self, data)
298 303  
299 304 inputs = db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
300   -
301 305 inputs.each { |i| tx.add_in(wrap_txin(i)) }
302 306  
303 307 outputs = db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
... ... @@ -323,7 +327,7 @@
323 327 def wrap_txout(output)
324 328 return nil unless output
325 329 data = {:id => output[:id], :tx_id => output[:tx_id], :tx_idx => output[:tx_idx],
326   - :hash160 => output[:hash160]}
  330 + :hash160 => output[:hash160], :type => SCRIPT_TYPES[output[:type]]}
327 331 txout = Bitcoin::Storage::Models::TxOut.new(self, data)
328 332 txout.value = output[:value]
329 333 txout.pk_script = output[:pk_script]
lib/bitcoin/storage/sequel_store/sequel_migrations.rb
... ... @@ -55,6 +55,7 @@
55 55 column :tx_idx, :int, :null => false
56 56 column :pk_script, :bytea, :null => false, :index => true
57 57 column :value, :bigint
  58 + column :type, :int, :null => false, :index => true
58 59 end
59 60 end
60 61  
lib/bitcoin/storage/storage.rb
... ... @@ -144,7 +144,8 @@
144 144 # get balance for given +hash160+
145 145 def get_balance(hash160)
146 146 txouts = get_txouts_for_hash160(hash160)
147   - unspent = txouts.select {|o| o.get_next_in.nil?}
  147 + confirmed = txouts.select {|o| !!o.get_tx.get_block}
  148 + unspent = confirmed.select {|o| o.get_next_in.nil?}
148 149 unspent.map(&:value).inject {|a,b| a+=b; a} || 0
149 150 rescue
150 151 nil
lib/bitcoin/wallet/coinselector.rb
... ... @@ -12,7 +12,7 @@
12 12 begin
13 13 next if txout.get_next_in
14 14 next unless txout.get_address
15   - # next unless txout.get_tx.get_block
  15 + next unless txout.get_tx.get_block
16 16 txouts << txout
17 17 return txouts if txouts.map(&:value).inject(:+) >= value
18 18 rescue
lib/bitcoin/wallet/keystore.rb
... ... @@ -16,7 +16,7 @@
16 16 # List all stored keys.
17 17 def keys(need = nil)
18 18 @keys.select do |key|
19   - next !key[:hidden] unless need
  19 + next !(key[:hidden] && key[:hidden] == "true") unless need
20 20 case need
21 21 when :label
22 22 !!key[:label]
23 23  
... ... @@ -57,8 +57,15 @@
57 57 key
58 58 end
59 59  
60   - def flag_key(name, flag, value)
  60 + def label_key(name, label)
61 61 find_key(name) do |key|
  62 + key[:label] = label
  63 + end
  64 + save_keys
  65 + end
  66 +
  67 + def flag_key(name, flag, value)
  68 + find_key(name, true) do |key|
62 69 key[flag.to_sym] = value
63 70 end
64 71 save_keys
... ... @@ -122,7 +129,7 @@
122 129  
123 130 private
124 131  
125   - def find_key(name)
  132 + def find_key(name, hidden = false)
126 133 key = if Bitcoin.valid_address?(name)
127 134 @keys.find{|k| k[:addr] == name }
128 135 elsif name.size == 130
... ... @@ -130,6 +137,7 @@
130 137 else
131 138 @keys.find{|k| k[:label] == name }
132 139 end
  140 + return nil if !key || (!hidden && key[:hidden] == "true")
133 141 block_given? ? yield(key) : key
134 142 end
135 143  
lib/bitcoin/wallet/txdp.rb
1 1 class Bitcoin::Wallet::TxDP
2 2  
3 3 attr_accessor :id, :tx, :inputs
4   - def initialize
5   - @tx = []
  4 + def initialize tx = []
  5 + @id = Bitcoin.int_to_base58(rand(1e14))
  6 + @tx = tx
6 7 @inputs = []
  8 + return unless tx.any?
  9 + @tx[0].in.each_with_index do |input, i|
  10 + prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
  11 + prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
  12 + raise "prev tx #{prev_out_hash} not found" unless prev_tx
  13 + prev_out = prev_tx.out[input.prev_out_index]
  14 + raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
  15 + out_script = Bitcoin::Script.new(prev_out.pk_script)
  16 + out_script.get_addresses.each do |addr|
  17 + add_sig(i, prev_out.value, addr, input.script_sig)
  18 + end
  19 + end
7 20 end
8 21  
  22 + def add_sig(in_idx, value, addr, sig)
  23 + sig = sig ? [[addr, sig.unpack("H*")[0]]] : []
  24 + @inputs[in_idx] = [value, sig]
  25 + end
  26 +
  27 + def sign_inputs
  28 + @inputs.each_with_index do |txin, i|
  29 + input = @tx[0].in[i]
  30 + prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
  31 + prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
  32 + raise "prev tx #{prev_out_hash} not found" unless prev_tx
  33 + prev_out = prev_tx.out[input.prev_out_index]
  34 + raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
  35 + out_script = Bitcoin::Script.new(prev_out.pk_script)
  36 + out_script.get_addresses.each do |addr|
  37 + sig = yield(@tx[0], prev_tx, i, addr)
  38 + if sig
  39 + @inputs[i][1] ||= []
  40 + @inputs[i][1] << [addr, sig]
  41 + break
  42 + end
  43 + end
  44 + end
  45 + end
  46 +
9 47 def serialize
10 48 lines = []
11 49 lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
12   - lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_00a0" #TODO size
  50 + size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
  51 + lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
13 52 tx = @tx.map(&:to_payload).join.unpack("H*")[0]
14 53 tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
15 54 lines << tx_str.strip
16 55 @inputs.each_with_index do |input, idx|
17 56 lines << "_TXINPUT_#{idx.to_s.rjust(2, '0')}_#{"%.8f" % (input[0].to_f / 1e8)}"
  57 + next unless input[1]
18 58 input[1].each do |sig|
19   - lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_008c" # TODO size
  59 + size = [sig[1]].pack("H*").bytesize
  60 + size = [size].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
  61 + lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
20 62 sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
21 63 lines << sig_str.strip
22 64 end
23 65  
... ... @@ -49,14 +91,15 @@
49 91 end
50 92  
51 93 def parse_input input
52   - m = input.match(/(\d+)_(\d+).(\d+)\n(.*)/m)
53   - _, idx, maj, min, sigs = *m
54   - value = ("#{maj}.#{min}".to_f * 1e8).to_i
  94 + m = input.match(/(\d+)_(\d+\.\d+)\n(.*)/m)
  95 + _, idx, value, sigs = *m
  96 + value = (value.sub('.','').to_i)
55 97 sigs = parse_sigs(sigs)
56 98 @inputs[idx.to_i] = [value, sigs]
57 99 end
58 100  
59 101 def parse_sigs sigs
  102 + return nil unless sigs["_SIG_"]
60 103 sigs = sigs.split("_SIG_").map do |s|
61 104 if s == ""
62 105 nil
lib/bitcoin/wallet/wallet.rb
... ... @@ -9,9 +9,10 @@
9 9 @selector = selector
10 10 end
11 11  
12   - def get_txouts
13   - @keystore.keys.map {|k|
  12 + def get_txouts(unconfirmed = false)
  13 + txouts = @keystore.keys.map {|k|
14 14 @storage.get_txouts_for_address(k[:addr])}.flatten.uniq
  15 + txouts.select! {|o| !!o.get_tx.get_block} unless unconfirmed
15 16 end
16 17  
17 18 def get_balance
18 19  
... ... @@ -23,9 +24,21 @@
23 24 @keystore.keys.map{|k| k[:key].addr}
24 25 end
25 26  
  27 + def add_key key
  28 + @keystore.add_key(key)
  29 + end
  30 +
  31 + def label old, new
  32 + @keystore.label_key(old, new)
  33 + end
  34 +
  35 + def flag name, flag, value
  36 + @keystore.flag_key(name, flag, value)
  37 + end
  38 +
26 39 def list
27 40 @keystore.keys.map do |key|
28   - [key[:addr], @storage.get_balance(Bitcoin.hash160_from_address(key[:addr]))]
  41 + [key, @storage.get_balance(Bitcoin.hash160_from_address(key[:addr]))]
29 42 end
30 43 end
31 44  
32 45  
... ... @@ -54,8 +67,19 @@
54 67 outputs.each do |type, *addrs, value|
55 68 script = nil
56 69 case type
  70 + when :pubkey
  71 + pubkey = @keystore.key(addrs[0])
  72 + raise "Public key for #{addrs[0]} not known" unless pubkey
  73 + binding.pry
  74 + script = Bitcoin::Script.to_pubkey_script(pubkey[:key].pub)
57 75 when :address
58   - script = Bitcoin::Script.to_address_script(addrs[0])
  76 + if Bitcoin.valid_address?(addrs[0])
  77 + addr = addrs[0]
  78 + else
  79 + addr = @keystore.key(addrs[0])[:addr] rescue nil
  80 + end
  81 + raise "Invalid address: #{addr}" unless Bitcoin.valid_address?(addr)
  82 + script = Bitcoin::Script.to_address_script(addr)
59 83 when :multisig
60 84 m, *addrs = addrs
61 85 addrs.map!{|a| keystore.key(a)[:key].pub rescue raise("public key for #{a} not known")}
62 86  
63 87  
64 88  
65 89  
... ... @@ -81,32 +105,42 @@
81 105 tx.add_in(txin)
82 106 end
83 107  
  108 + sigs_missing = false
84 109 prev_outs.each_with_index do |prev_out, idx|
85 110 prev_tx = prev_out.get_tx
86 111 pk_script = Bitcoin::Script.new(prev_out.pk_script)
87 112 if pk_script.is_pubkey? || pk_script.is_hash160?
88 113 key = @keystore.key(prev_out.get_address)
89   - sig_hash = tx.signature_hash_for_input(idx, prev_tx)
90   - sig = key[:key].sign(sig_hash)
91   - script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key[:key].pub].pack("H*"))
  114 + if key && key[:key] && !key[:key].priv.nil?
  115 + sig_hash = tx.signature_hash_for_input(idx, prev_tx)
  116 + sig = key[:key].sign(sig_hash)
  117 + script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key[:key].pub].pack("H*"))
  118 + end
92 119 elsif pk_script.is_multisig?
93 120 sigs = []
94 121 required_sigs = pk_script.get_signatures_required
95 122 pk_script.get_multisig_pubkeys.each do |pub|
96 123 break if sigs.size == required_sigs
97   - key = @keystore.key(pub.unpack("H*")[0])[:key]
  124 + key = @keystore.key(pub.unpack("H*")[0])[:key] rescue nil
98 125 next unless key && key.priv
99 126 sig_hash = tx.signature_hash_for_input(idx, prev_tx)
100 127 sig = [key.sign(sig_hash), "\x01"].join
101 128 sigs << sig
102 129 end
103   - raise "Need #{required_sigs} signatures, only have #{sigs.size} private keys" if sigs.size < required_sigs
104   -
105   - script_sig = Bitcoin::Script.to_multisig_script_sig(*sigs)
  130 + if sigs.size == required_sigs
  131 + script_sig = Bitcoin::Script.to_multisig_script_sig(*sigs)
  132 + else
  133 + puts "Need #{required_sigs} signatures, only have #{sigs.size} private keys"
  134 + sigs_missing = true
  135 + end
106 136 end
107   - tx.in[idx].script_sig_length = script_sig.bytesize
108   - tx.in[idx].script_sig = script_sig
109   - raise "Signature error" unless tx.verify_input_signature(idx, prev_tx)
  137 + if script_sig
  138 + tx.in[idx].script_sig_length = script_sig.bytesize
  139 + tx.in[idx].script_sig = script_sig
  140 + raise "Signature error" unless tx.verify_input_signature(idx, prev_tx)
  141 + else
  142 + return Bitcoin::Wallet::TxDP.new([tx, *prev_outs.map(&:get_tx)])
  143 + end
110 144 end
111 145  
112 146 Bitcoin::Protocol::Tx.new(tx.to_payload)
spec/bitcoin/storage_spec.rb
... ... @@ -3,7 +3,7 @@
3 3 include Bitcoin::Builder
4 4  
5 5 [
6   - # { :name => :dummy },
  6 +# { :name => :dummy },
7 7 { :name => :sequel, :db => 'sqlite:/' }, # in memory
8 8 # { :name => :sequel, :db => 'sqlite:///tmp/bitcoin_test.db' },
9 9 # { :name => :sequel, :db => 'postgres://localhost/bitcoin_test' },
... ... @@ -224,6 +224,11 @@
224 224 end
225 225 end
226 226  
  227 + it "should index output script type" do
  228 + @store.store_tx(@tx)
  229 + @store.get_tx(@tx.hash).out.first.type.should == :hash160
  230 + end
  231 +
227 232 describe "reorg" do
228 233  
229 234 def create_block prev, store = true
... ... @@ -306,6 +311,7 @@
306 311 end
307 312  
308 313 end
  314 +
309 315 end
310 316 end
spec/bitcoin/wallet/txdp_spec.rb
1 1 require_relative '../spec_helper'
2 2 include Bitcoin
3 3 include Bitcoin::Wallet
  4 +include MiniTest
4 5  
5 6 describe "Bitcoin::Wallet::TxDP" do
6 7  
7 8 before do
8 9 Bitcoin.network = :testnet
9   - @txdp = <<-EOS
10   ------BEGIN-TRANSACTION-3fX59xPj-------------------------------------------------
11   -_TXDIST_fabfb5da_3fX59xPj_00a0
12   -010000000292807c8e70a28c687daea2998d6273d074e56fa8a55a0b10556974cf2b526e61000000
13   -0000ffffffffe3c1ee0711611b01af3dee55b1484f0d6b65d17dce4eff0e6e06242e6cf457e10000
14   -000000ffffffff02b0feea0b000000001976a91457996661391fa4e95bed27d7e8fe47f47cb8e428
15   -88ac00a0acb9030000001976a914dc504e07b1107110f601fb679dd3f56cee9ff71e88ac00000000
16   -0100000001eb626e4f73d88f415a8e8cb32b8d73eed47aa1039d0ed2f013abdc741ce6828c010000
17   -008c493046022100b0da540e4924518f8989a9da798ca2d9e761b69a173b8cc41a3e3e3c6d77cd50
18   -022100ecfa61730e58005338420516744ef680428dcfc05022dec70a851365c8575b190141042dc5
19   -be3afa5887aee4a377032ed014361b0b9b61eb3ea6b8a8821bfe13ee4b65cd25d9630e4f227a53e8
20   -bf637f85452c9981bcbd64ef77e22ce97b0f547c783effffffff0200d6117e030000001976a914cf
21   -f580fd243f64f0ad7bf69faf41c0bf42d86d8988ac00205fa0120000001976a9148d573ef6984fd9
22   -f8847d420001f7ac49b222a24988ac000000000100000001f2782db40ae147398a31cff9c7cc3423
23   -014a073a92e463741244330cc304168f000000008c493046022100c9311b9eef0cc69219cb96838f
24   -dd621530a80c46269a00dccc66498bc03ccf7a0221003742ee652a0a76fd28ad81aa73bb7f7a0a6a
25   -81850af58f62d9a184d10e5eec30014104f815e8ef4cad584e04974889d7636e8933803d2e72991d
26   -b5288c9e953c2465533905f98b7b688898c7c1f0708f2e49f0dd0abc06859ffed5144e8a1018a4e8
27   -63ffffffff02008c8647000000001976a914d4e211215967f8e3744693bf85f47eb4ee9567fc88ac
28   -603d4e95010000001976a914e9a6b50901c1969d2b0fd43a3ccfa3fef3291efe88ac00000000
29   -_TXINPUT_00_150.00000000
30   -_SIG_mzUYGfqGpyXmppYpmWJ31Y4zTxR4ZCod22_00_008c
31   -4930460221007699967c3ec09d072599558d2e7082fae0820206b63aa66afea124634ed11a080221
32   -0003346f7e963e645ecae2855026dc7332eb7237012539b34cd441c3cef97fbd4d01410497d5e1a0
33   -0e1db90e893d1f2e547e2ee83b5d6bf4ddaa3d514e6dc2d94b6bcb5a72be1fcec766b8c382502caa
34   -9ec09fe478bad07d3f38ff47b2eb42e681c384cc
35   -_TXINPUT_01_12.00000000
36   -_SIG_mzvaN8JUhHLz3Gdec1zBRxs5rNaYLQnbD1_01_008c
37   -49304602210081554f8b08a1ad8caa69e34f4794d54952dac7c5efcf2afe080985d6bd5b00770221
38   -00dea20ca3dbae1d15ec61bec57b4b8062e7d7c47614aba032c5a32f651f471cfd014104c30936d2
39   -456298a566aa76fefeab8a7cb7a91e8a936a11757c911b4c669f0434d12ab0936fc13986b156156f
40   -9b389ed244bbb580112be07dbe23949a4764dffb
41   --------END-TRANSACTION-3fX59xPj-------------------------------------------------
42   -EOS
43 10 end
44 11  
45 12 it "should parse txdp" do
46   - txdp = TxDP.parse(@txdp)
  13 + txt = fixtures_file("txdp-1.txt")
  14 + txdp = TxDP.parse(txt)
47 15 txdp.id.should == "3fX59xPj"
48 16 txdp.tx.size.should == 3
49 17 txdp.tx.first.hash.should ==
50 18 "2aa1938705066d0f9988923000ee75d5fc728b92b9739b71f94c139e5a729527"
51 19 txdp.inputs.size.should == 2
52   - txdp.serialize.should == @txdp.strip
53 20 end
  21 +
  22 + it "should parse unsigned txdp" do
  23 + txt = fixtures_file("txdp-2-unsigned.txt")
  24 + txdp = TxDP.parse(txt)
  25 + txdp.id.should == "7Q74Wkre"
  26 + txdp.tx.size.should == 2
  27 + txdp.inputs.size.should == 1
  28 + txdp.inputs.first[1].should == nil
  29 + end
  30 +
  31 + it "should parse signed txdp" do
  32 + txt = fixtures_file("txdp-2-signed.txt")
  33 + txdp = TxDP.parse(txt)
  34 + txdp.id.should == "7Q74Wkre"
  35 + txdp.tx.size.should == 2
  36 + txdp.inputs.size.should == 1
  37 + txdp.inputs.first[1].should ==[["mnheKkGdmw8d1fUV15XZbfmLR6AjQjVthy", "49304602210087bc1ff770c6cb3c7e47b9a3acb7dce678c16350f29acaa92e4ab231692256cf0221002da46fc1f39e132e726dea46a6e87e4278e85d36ccd393e39e931b89d55fc3a2014104955ec5646652d1b5bb14b2f867ef8879bcf224f1eab01072147fdfe0992440a234b36792937a23df736e8430613da6f0466bfc5505f2ad41b056131b7af13086"]]
  38 + end
  39 +
  40 + it "should serialize unsigned txdp" do
  41 + txt = fixtures_file("txdp-2-unsigned.txt")
  42 + txdp = TxDP.parse(txt)
  43 + txdp.serialize.should == txt.strip
  44 + end
  45 +
  46 + it "should serialize signed txdp" do
  47 + txt = fixtures_file("txdp-2-signed.txt")
  48 + txdp = TxDP.parse(txt)
  49 + txdp.serialize.should == txt.strip
  50 + end
  51 +
  52 + it "should create txdp from tx" do
  53 + tx1 = Bitcoin::P::Tx.from_json(fixtures_file("rawtx-05.json"))
  54 + tx2 = Bitcoin::P::Tx.from_json(fixtures_file("rawtx-04.json"))
  55 + sig = tx2.in[0].script_sig
  56 + tx2.in[0].script_sig_length = 0
  57 + tx2.in[0].script_sig = ""
  58 + txdp = TxDP.new([tx2, tx1])
  59 + txdp.id.should != nil
  60 + txdp.inputs.size.should == 1
  61 + txdp.inputs.first[0].should == 5e9
  62 + txt = txdp.serialize
  63 +
  64 + txt.should =~ /--BEGIN-TRANSACTION-#{txdp.id}--/
  65 + txt.should =~ /^_TXDIST_fabfb5da_#{txdp.id}_00cb$/
  66 + txt.should =~ /^_TXINPUT_00_50.00000000$/
  67 + txt.should =~ /--END-TRANSACTION-#{txdp.id}--/
  68 +
  69 + txdp.add_sig(0, tx1.out[0].value, "mh8YhPYEAYs3E7EVyKtB5xrcfMExkkdEMF", sig)
  70 + txt = txdp.serialize
  71 + txt.should =~ /^_SIG_mh8YhPYEAYs3E7EVyKtB5xrcfMExkkdEMF_00_0048$/
  72 + end
  73 +
54 74 end