Commit d2f9facb0cd9c7e19e888246d5b56c3d12c262b9

Authored by Marius Hanne
1 parent 9cfffcdb6f

create multisig tx when all keys are in the same keystore

Showing 3 changed files with 71 additions and 49 deletions Side-by-side Diff

... ... @@ -2,6 +2,7 @@
2 2 $:.unshift( File.expand_path("../../lib", __FILE__) )
3 3  
4 4 require 'bitcoin'
  5 +require 'eventmachine'
5 6 require 'optparse'
6 7 require 'yaml'
7 8  
... ... @@ -65,7 +66,7 @@
65 66 exit puts optparse
66 67 end
67 68  
68   -
  69 +Bitcoin.network = options[:network]
69 70 backend, config = options[:keystore].split("::")
70 71 config = Hash[config.split(",").map{|c| c.split("=")}]
71 72 keystore = Bitcoin::Wallet.const_get("#{backend.capitalize}KeyStore").new(config)
72 73  
73 74  
... ... @@ -161,14 +162,14 @@
161 162  
162 163 when "send"
163 164 to = cmdopts[0].split(',').map do |pair|
164   - addr, value = pair.split(":")
  165 + type, *addrs, value = pair.split(":")
165 166 value = val_str(value)
166   - [addr, value]
  167 + [type.to_sym, *addrs, value]
167 168 end
168 169 fee = val_str(cmdopts[1]) || 0
169 170 value = val_str value
170 171  
171   - unless wallet.get_balance >= (to.map{|t|t[1]}.inject{|a,b|a+=b;a} + fee)
  172 + unless wallet.get_balance >= (to.map{|t|t[-1]}.inject{|a,b|a+=b;a} + fee)
172 173 puts "Insufficient funds."; exit
173 174 end
174 175  
... ... @@ -190,8 +191,12 @@
190 191 puts "outputs:"
191 192 tx.out.each do |txout|
192 193 total -= txout.value
193   - address = Bitcoin::Script.new(txout.pk_script).get_address
194   - puts " #{address} - #{str_val txout.value}"
  194 + script = Bitcoin::Script.new(txout.pk_script)
  195 + if script.is_hash160?
  196 + puts "#{str_val txout.value} #{script.get_address} (address)"
  197 + elsif script.is_multisig?
  198 + puts "#{str_val txout.value} #{script.get_addresses.join(' ')} (multisig)"
  199 + end
195 200 end
196 201 puts "Fee: #{str_val total}"
197 202  
... ... @@ -201,13 +206,18 @@
201 206 puts "Aborted."; exit
202 207 end
203 208  
204   - host, port = *options[:command].split(":")
205   - s = TCPSocket.new(host, port.to_i)
206   - s.puts "relay_tx " + tx.to_payload.unpack("H*")[0]
207   -
208   - res = s.readline
209   - puts JSON::pretty_generate(JSON::parse(res))
210   - puts "Transaction sent."
  209 + EM.run do
  210 + EM.connect(*options[:command].split(":")) do |conn|
  211 + conn.send_data(["relay_tx", tx.to_payload.unpack("H*")[0]].to_json)
  212 + def conn.receive_data(data)
  213 + (@buf ||= BufferedTokenizer.new("\x00")).extract(data).each do |packet|
  214 + res = JSON.load(packet)
  215 + puts "Transaction relayed: #{res[1]["hash"]}"
  216 + EM.stop
  217 + end
  218 + end
  219 + end
  220 + end
211 221  
212 222 else
213 223 puts "Unknown command."
lib/bitcoin/wallet/wallet.rb
... ... @@ -57,9 +57,9 @@
57 57 when :address
58 58 script = Bitcoin::Script.to_address_script(addrs[0])
59 59 when :multisig
60   - m, *pubkeys = addrs
61   - addrs = pubkeys.map{|p| Bitcoin.pubkey_to_address(p)}
62   - script = Bitcoin::Script.to_multisig_script(m, *pubkeys)
  60 + m, *addrs = addrs
  61 + addrs.map!{|a| keystore.key(a).pub rescue raise("public key for #{a} not known")}
  62 + script = Bitcoin::Script.to_multisig_script(m, *addrs)
63 63 else
64 64 raise "unknown script type: #{type}"
65 65 end
... ... @@ -83,14 +83,18 @@
83 83  
84 84 prev_outs.each_with_index do |prev_out, idx|
85 85 prev_tx = prev_out.get_tx
86   -
87   - key = @keystore.key(prev_out.get_address)
88   - sig_hash = tx.signature_hash_for_input(idx, prev_tx)
89   - sig = key.sign(sig_hash)
90   - script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key.pub].pack("H*"))
91   - tx.in[idx].script_sig_length = script_sig.bytesize
92   - tx.in[idx].script_sig = script_sig
93   - raise "Signature error" unless tx.verify_input_signature(idx, prev_tx)
  86 + pk_script = Bitcoin::Script.new(prev_out.pk_script)
  87 + if pk_script.is_pubkey? || pk_script.is_hash160?
  88 + key = @keystore.key(prev_out.get_address)
  89 + sig_hash = tx.signature_hash_for_input(idx, prev_tx)
  90 + sig = key.sign(sig_hash)
  91 + script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key.pub].pack("H*"))
  92 + tx.in[idx].script_sig_length = script_sig.bytesize
  93 + tx.in[idx].script_sig = script_sig
  94 + raise "Signature error" unless tx.verify_input_signature(idx, prev_tx)
  95 + elsif pk_script.is_multisig?
  96 + # TODO
  97 + end
94 98 end
95 99  
96 100 Bitcoin::Protocol::Tx.new(tx.to_payload)
spec/bitcoin/wallet/wallet_spec.rb
... ... @@ -6,6 +6,26 @@
6 6  
7 7 describe Bitcoin::Wallet::Wallet do
8 8  
  9 + class DummyKeyStore
  10 +
  11 + def initialize keys
  12 + @keys =keys
  13 + end
  14 +
  15 + def key(addr)
  16 + @keys.select{|k|k.addr==addr}.first
  17 + end
  18 +
  19 + def keys
  20 + @keys
  21 + end
  22 +
  23 + def new_key
  24 + @keys << Bitcoin::Key.generate
  25 + @keys[-1]
  26 + end
  27 + end
  28 +
9 29 def txout_mock(value, next_in)
10 30 txout = Mock.new
11 31 txout.expect(:value, value)
12 32  
13 33  
14 34  
15 35  
16 36  
... ... @@ -14,40 +34,33 @@
14 34  
15 35 before do
16 36 @storage = Mock.new
17   - @keystore = Mock.new
18 37 @key = Key.from_base58('5J2hn1E8KEXmQn5gqykzKotyCcHbKrVbe8fjegsfVXRdv6wwon8')
19 38 @addr = '1M89ZeWtmZmATzE3b6PHTBi8c7tGsg5xpo'
20 39 @key2 = Key.from_base58('5KK9Lw8gtNd4kcaXQJmkwcmNy8Y5rLGm49RqhcYAb7qRhWxaWMJ')
21 40 @addr2 = '134A4Bi8jN5V2KjkwmXUHjokDqdyqZ778J'
22   - @keystore.expect(:keys, [@key])
  41 + @key3 = Key.from_base58('5JFcJByQvwYnWjQ2RHTTu6LLGiBj9oPQYsHqKWuKLDVAvv4cQ7E')
  42 + @addr3 = '1EnrPVaRiRgrs1D7pujYZNN1N6iD9unZV6'
  43 + @keystore = DummyKeyStore.new([@key])
23 44 @selector = MiniTest::Mock.new
24 45 @wallet = Wallet.new(@storage, @keystore, @selector)
25 46 end
26 47  
27   - after do
28   - [@storage.verify, @selector.verify, @keystore.verify].all?.should == true
29   - end
30   -
31 48 it "should get total balance" do
32 49 @storage.expect(:get_txouts_for_address, [], [@addr])
33 50 @wallet.get_balance.should == 0
34 51  
35   - @storage.expect(:get_txouts_for_address, [txout_mock(5000, nil)],
36   - [@addr])
  52 + @storage.expect(:get_txouts_for_address, [txout_mock(5000, nil)], [@addr])
37 53 @wallet.get_balance.should == 5000
38 54  
39 55 @storage.expect(:get_txouts_for_address, [txout_mock(5000, true), txout_mock(1000, nil)],
40 56 [@addr])
41 57 @wallet.get_balance.should == 1000
42   - @storage.verify; @keystore.verify
  58 + @storage.verify
43 59 end
44 60  
45 61 it "should get all addrs" do
46 62 @wallet.addrs.should == [@addr]
47   -
48   - @keystore.expect(:keys, [@key, Key.generate])
49   - @wallet.addrs.size.should == 2
50   - @keystore.verify
  63 + @wallet.addrs.size.should == 1
51 64 end
52 65  
53 66 it "should list all addrs with balances" do
... ... @@ -63,8 +76,6 @@
63 76 @wallet.addrs.size.should == 1
64 77  
65 78 key = Key.generate
66   - @keystore.expect(:new_key, key)
67   - @keystore.expect(:keys, [@key, key])
68 79 a = @wallet.get_new_addr
69 80 @wallet.addrs.size.should == 2
70 81 @wallet.addrs[1].should == a
... ... @@ -87,7 +98,6 @@
87 98 txout.expect(:pk_script,
88 99 Script.to_address_script(@addr))
89 100 @storage.expect(:get_txouts_for_address, [txout], [@addr])
90   - @keystore.expect(:key, @key, [@addr])
91 101 selector = Mock.new
92 102 selector.expect(:select, [txout], [[txout]])
93 103 @selector.expect(:new, selector, [[txout]])
... ... @@ -95,7 +105,6 @@
95 105 end
96 106  
97 107  
98   -
99 108 it "should have hash" do
100 109 @tx.hash.size.should == 64
101 110 end
102 111  
... ... @@ -132,13 +141,9 @@
132 141 end
133 142  
134 143 it "should send change to new address" do
135   - key = Key.generate
136   - @keystore.expect(:new_key, key)
137   - @keystore.expect(:keys, [@key, key])
138 144 @tx = @wallet.tx([[:address, '1M2JjkX7KAgwMyyF5xc2sPSfE7mL1jqkE7', 1000]], 50, :new)
139 145 @wallet.addrs.size.should == 2
140   - @wallet.addrs.last.should == key.addr
141   - Script.new(@tx.out.last.pk_script).get_address.should == key.addr
  146 + Script.new(@tx.out.last.pk_script).get_address.should == @wallet.addrs.last
142 147 end
143 148  
144 149 it "should return nil if insufficient balance" do
... ... @@ -149,6 +154,7 @@
149 154 end
150 155  
151 156 describe "Bitcoin::Wallet::Wallet#tx (multisig)" do
  157 +
152 158 def txout_mock(value, next_in)
153 159 txout = Mock.new
154 160 txout.expect(:value, value)
155 161  
156 162  
... ... @@ -164,18 +170,20 @@
164 170 txout.expect(:get_address, @addr)
165 171 txout.expect(:pk_script, Script.to_address_script(@addr))
166 172 @storage.expect(:get_txouts_for_address, [txout], [@addr])
167   - @keystore.expect(:key, @key, [@addr])
  173 + @keystore = DummyKeyStore.new([@key, @key2, @key3])
  174 +
168 175 selector = Mock.new
169 176 selector.expect(:select, [txout], [1000])
170 177 @selector.expect(:new, selector, [[txout]])
171   - @tx = @wallet.tx([[:multisig, 1, @key.pub, @key2.pub, 1000]])
  178 + @wallet = Wallet.new(@storage, @keystore, @selector)
  179 + @tx = @wallet.tx([[:multisig, 1, @key2.addr, @key3.addr, 1000]])
172 180 end
173 181  
174 182 it "should have correct outputs" do
175 183 @tx.out.size.should == 2
176 184 @tx.out.first.value.should == 1000
177 185 s = Script.new(@tx.out.first.pk_script)
178   - s.get_addresses.should == [@addr, @addr2]
  186 + s.get_addresses.should == [@addr2, @addr3]
179 187 end
180 188  
181 189 end