coin-op 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dfeba45649380d25bdb450efa635c4abfeb97808
4
+ data.tar.gz: 9163ba6bb9a926d3e68ac56d52cfedf8a58f5896
5
+ SHA512:
6
+ metadata.gz: 5dc4eb2daff6a91ed33309ef74562ec21573c165708825bf616b03f3bf6df2d1ef20ee277d726889fb808b4f99325cee05916e77cd4099eb04ef91ad9969cc4d
7
+ data.tar.gz: 8186166830812cf399ad01542cc936e156589d42bef51be532e39a61d57324b99c2f72e47022f6799425703b15a2982db6ab270ea995f106113c71c214102c5a
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 BitVault, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
@@ -0,0 +1,28 @@
1
+ # Coin-Op
2
+
3
+ Install:
4
+
5
+ gem install coin-op
6
+
7
+
8
+ Use:
9
+
10
+ ```ruby
11
+ require "coin-op"
12
+
13
+ include CoinOp::Bit
14
+
15
+ ```
16
+
17
+ # Developers
18
+
19
+ Installing dependencies:
20
+
21
+ gem install starter
22
+ rake gem:deps
23
+
24
+ Running the tests:
25
+
26
+ rake test
27
+
28
+
@@ -0,0 +1,12 @@
1
+
2
+ # Establish the namespace.
3
+ module CoinOp
4
+
5
+ end
6
+
7
+ require_relative "coin-op/encodings"
8
+ require_relative "coin-op/crypto"
9
+ require_relative "coin-op/bit"
10
+ require_relative "coin-op/blockchain/mockchain"
11
+ require_relative "coin-op/blockchain/blockr"
12
+
@@ -0,0 +1,21 @@
1
+ require "bitcoin"
2
+ require "money-tree"
3
+
4
+ # establish the namespace
5
+ module CoinOp
6
+ module Bit
7
+ end
8
+ end
9
+
10
+ require_relative "encodings"
11
+
12
+ # Wrappers
13
+ require_relative "bit/script"
14
+ require_relative "bit/output"
15
+ require_relative "bit/input"
16
+ require_relative "bit/transaction"
17
+ require_relative "bit/spendable"
18
+
19
+ # Augmented functionality
20
+ require_relative "bit/multi_wallet"
21
+
@@ -0,0 +1,73 @@
1
+
2
+ module CoinOp::Bit
3
+
4
+ class SparseInput
5
+ include CoinOp::Encodings
6
+
7
+ def initialize(binary_hash, index)
8
+ @output = {
9
+ # the binary hash is the result of
10
+ # [tx.hash].pack("H*").reverse
11
+ :transaction_hash => hex(binary_hash.reverse),
12
+ :index => index,
13
+ }
14
+ end
15
+
16
+ def to_json(*a)
17
+ {
18
+ :output => @output,
19
+ }.to_json(*a)
20
+ end
21
+
22
+ end
23
+
24
+
25
+ class Input
26
+ include CoinOp::Encodings
27
+
28
+ attr_reader :native, :output, :binary_sig_hash,
29
+ :signatures, :sig_hash, :script_sig, :index
30
+
31
+ def initialize(options={})
32
+ @transaction, @index, @output =
33
+ options.values_at :transaction, :index, :output
34
+
35
+ unless @output.is_a? Output
36
+ @output = Output.new(@output)
37
+ end
38
+
39
+ @native = Bitcoin::Protocol::TxIn.new
40
+
41
+ @native.prev_out = decode_hex(@output.transaction_hash).reverse
42
+ @native.prev_out_index = @output.index
43
+
44
+ @signatures = []
45
+ end
46
+
47
+ def binary_sig_hash=(blob)
48
+ @binary_sig_hash = blob
49
+ @sig_hash = base58(blob)
50
+ end
51
+
52
+ def script_sig=(blob)
53
+ script = Script.new(:blob => blob)
54
+ @script_sig = script.to_s
55
+ @native.script_sig = blob
56
+ end
57
+
58
+
59
+ def to_json(*a)
60
+ {
61
+ :output => self.output,
62
+ :signatures => self.signatures.map {|b| base58(b) },
63
+ :sig_hash => self.sig_hash || "",
64
+ :script_sig => self.script_sig || ""
65
+ }.to_json(*a)
66
+ end
67
+
68
+ end
69
+
70
+
71
+ end
72
+
73
+
@@ -0,0 +1,261 @@
1
+ require "money-tree"
2
+ require "bitcoin"
3
+
4
+ module CoinOp::Bit
5
+
6
+ class MultiWallet
7
+ include CoinOp::Encodings
8
+
9
+ def self.generate(names, network=:bitcoin_testnet)
10
+ masters = {}
11
+ names.each do |name|
12
+ name = name.to_sym
13
+ masters[name] = MoneyTree::Master.new(:network => network)
14
+ end
15
+ self.new(:private => masters)
16
+ end
17
+
18
+ attr_reader :trees
19
+
20
+ def initialize(options)
21
+ # FIXME: must accept option for which network to use.
22
+ @private_trees = {}
23
+ @public_trees = {}
24
+ @trees = {}
25
+ private_trees = options[:private]
26
+
27
+ if !private_trees
28
+ raise "Must supply :private"
29
+ end
30
+
31
+ private_trees.each do |name, arg|
32
+ name = name.to_sym
33
+ @private_trees[name] = @trees[name] = self.get_node(arg)
34
+ end
35
+
36
+ if public_trees = options[:public]
37
+ public_trees.each do |name, arg|
38
+ name = name.to_sym
39
+ @public_trees[name] = @trees[name] = self.get_node(arg)
40
+ end
41
+ end
42
+ end
43
+
44
+ def drop(*names)
45
+ names = names.map(&:to_sym)
46
+ options = {:private => {}, :public => {}}
47
+ @private_trees.each do |name, node|
48
+ unless names.include?(name.to_sym)
49
+ options[:private][name] = node
50
+ end
51
+ end
52
+ @public_trees.each do |name, node|
53
+ unless names.include?(name.to_sym)
54
+ options[:private][name] = node
55
+ end
56
+ end
57
+ self.class.new options
58
+ end
59
+
60
+ def drop_private(*names)
61
+ names.each do |name|
62
+ name = name.to_sym
63
+ tree = @private_trees.delete(name)
64
+ address = tree.to_serialized_address
65
+ @public_trees[name] = MoneyTree::Master.from_serialized_address(address)
66
+ end
67
+ end
68
+
69
+ def import(addresses)
70
+ addresses.each do |name, address|
71
+ node = MoneyTree::Master.from_serialized_address(address)
72
+ if node.private_key
73
+ @private_trees[name] = node
74
+ else
75
+ @public_trees[name] = node
76
+ end
77
+ end
78
+ end
79
+
80
+ def private_seed(name)
81
+ raise "No such node: '#{name}'" unless (node = @private_trees[name.to_sym])
82
+ node.to_serialized_address(:private)
83
+ end
84
+
85
+ alias_method :private_address, :private_seed
86
+
87
+ def public_seed(name)
88
+ name = name.to_sym
89
+ if node = (@public_trees[name] || @private_trees[name])
90
+ node.to_serialized_address
91
+ else
92
+ raise "No such node: '#{name}'"
93
+ end
94
+ end
95
+
96
+ alias_method :public_address, :public_seed
97
+
98
+ def private_addresses
99
+ out = {}
100
+ @private_trees.each do |name, tree|
101
+ out[name] = self.private_address(name)
102
+ end
103
+ out
104
+ end
105
+
106
+ def public_addresses
107
+ out = {}
108
+ @private_trees.each do |name, node|
109
+ out[name] = node.to_serialized_address
110
+ end
111
+ out
112
+ end
113
+
114
+ def get_node(arg)
115
+ case arg
116
+ when MoneyTree::Node
117
+ arg
118
+ when String
119
+ MoneyTree::Node.from_serialized_address(arg)
120
+ else
121
+ raise "Unusable type: #{node.class}"
122
+ end
123
+ end
124
+
125
+ def path(path)
126
+ options = {
127
+ :path => path,
128
+ :private => {},
129
+ :public => {}
130
+ }
131
+ @private_trees.each do |name, node|
132
+ options[:private][name] = node.node_for_path(path)
133
+ end
134
+ @public_trees.each do |name, node|
135
+ options[:public][name] = node.node_for_path(path)
136
+ end
137
+
138
+ MultiNode.new(options)
139
+ end
140
+
141
+ def valid_output?(output)
142
+ if path = output.metadata.wallet_path
143
+ node = self.path(path)
144
+ node.p2sh_script.to_s == output.script.to_s
145
+ else
146
+ true
147
+ end
148
+ end
149
+
150
+ # Takes a Transaction ready to be signed.
151
+ #
152
+ # Returns an Array of signature dictionaries.
153
+ def signatures(transaction)
154
+ transaction.inputs.map do |input|
155
+ path = input.output.metadata[:wallet_path]
156
+ node = self.path(path)
157
+ sig_hash = transaction.sig_hash(input, node.script)
158
+ node.signatures(sig_hash)
159
+ end
160
+ end
161
+
162
+
163
+ # Takes a Transaction and any number of Arrays of signature dictionaries.
164
+ # Each sig_dict in an Array corresponds to the Input with the same index.
165
+ #
166
+ # Uses the combined signatures from all the signers to generate and set
167
+ # the script_sig for each Input.
168
+ #
169
+ # Returns the transaction.
170
+ def authorize(transaction, *signers)
171
+ transaction.set_script_sigs *signers do |input, *sig_dicts|
172
+ node = self.path(input.output.metadata[:wallet_path])
173
+ signatures = combine_signatures(*sig_dicts)
174
+ node.script_sig(signatures)
175
+ end
176
+ transaction
177
+ end
178
+
179
+ # Takes any number of "signature dictionaries", which are Hashes where
180
+ # the keys are tree names, and the values are base58-encoded signatures
181
+ # for a single input.
182
+ #
183
+ # Returns an Array of the signatures in binary, sorted by their tree names.
184
+ def combine_signatures(*sig_dicts)
185
+ combined = {}
186
+ sig_dicts.each do |sig_dict|
187
+ sig_dict.each do |tree, signature|
188
+ combined[tree] = decode_base58(signature)
189
+ end
190
+ end
191
+
192
+ # Order of signatures is important for validation, so we always
193
+ # sort public keys and signatures by the name of the tree
194
+ # they belong to.
195
+ combined.sort_by { |tree, value| tree }.map { |tree, sig| sig }
196
+ end
197
+
198
+ end
199
+
200
+ class MultiNode
201
+ include CoinOp::Encodings
202
+
203
+ attr_reader :path, :keys, :public_keys
204
+ def initialize(options)
205
+ # m of n
206
+ # TODO: take @m from the options
207
+ @m = 2
208
+ @path = options[:path]
209
+
210
+ @keys = {}
211
+ @public_keys = {}
212
+ @private = options[:private]
213
+ @public = options[:public]
214
+
215
+ @private.each do |name, node|
216
+ key = Bitcoin::Key.new(node.private_key.to_hex, node.public_key.to_hex)
217
+ @keys[name] = key
218
+ @public_keys[name] = key
219
+ end
220
+ @public.each do |name, node|
221
+ @public_keys[name] = Bitcoin::Key.new(nil, node.public_key.to_hex)
222
+ end
223
+ end
224
+
225
+ def script
226
+ keys = @public_keys.sort_by {|name, key| name }.map {|name, key| key.pub }
227
+ Script.new(:public_keys => keys, :needed => @m)
228
+ end
229
+
230
+ def p2sh_address
231
+ self.script.p2sh_address
232
+ end
233
+
234
+ def p2sh_script
235
+ Script.new(:address => self.script.p2sh_address)
236
+ end
237
+
238
+ def sign(name, value)
239
+ raise "No such key: '#{name}'" unless (key = @keys[name.to_sym])
240
+ # \x01 means the hash type is SIGHASH_ALL
241
+ # https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29
242
+ key.sign(value) + "\x01"
243
+ end
244
+
245
+ def signatures(value)
246
+ out = {}
247
+ @keys.each do |name, key|
248
+ out[name] = base58(self.sign(name, value))
249
+ end
250
+ out
251
+ end
252
+
253
+ def script_sig(signatures)
254
+ self.script.p2sh_sig(:signatures => signatures)
255
+ end
256
+
257
+ end
258
+
259
+
260
+ end
261
+
@@ -0,0 +1,79 @@
1
+
2
+ module CoinOp::Bit
3
+
4
+ class Output
5
+ include CoinOp::Encodings
6
+
7
+ attr_accessor :metadata
8
+ attr_reader :native, :transaction, :index, :value, :script, :address
9
+
10
+ # Takes a Hash with required keys:
11
+ #
12
+ # * either :transaction (an instance of Transaction)
13
+ # or :transaction_hash (the hex-encoded hash of a Bitcoin transaction)
14
+ # * :index
15
+ # * :script
16
+ #
17
+ # optional keys:
18
+ #
19
+ # * :value
20
+ # * :metadata
21
+ #
22
+ def initialize(options)
23
+ if options[:transaction]
24
+ @transaction = options[:transaction]
25
+ elsif options[:transaction_hash]
26
+ @transaction_hash = options[:transaction_hash]
27
+ end
28
+
29
+ # FIXME: be aware of string bitcoin values versus
30
+ # integer satoshi values
31
+ @index, @value, @address = options.values_at :index, :value, :address
32
+ @metadata = options[:metadata] || {}
33
+
34
+ if options[:script]
35
+ @script = Script.new(options[:script])
36
+ else
37
+ raise ArgumentError, "No script supplied"
38
+ end
39
+
40
+ @native = Bitcoin::Protocol::TxOut.from_hash(
41
+ "value" => @value.to_s,
42
+ "scriptPubKey" => @script.to_s
43
+ )
44
+ end
45
+
46
+ def set_transaction(transaction, index)
47
+ @transaction_hash = nil
48
+ @transaction, @index = transaction, index
49
+ end
50
+
51
+ def transaction_hash
52
+ if @transaction
53
+ @transaction.hex_hash
54
+ elsif @transaction_hash
55
+ @transaction_hash
56
+ else
57
+ ""
58
+ end
59
+ end
60
+
61
+
62
+ def to_hash
63
+ {
64
+ :transaction_hash => self.transaction_hash,
65
+ :index => self.index,
66
+ :value => self.value,
67
+ :script => self.script,
68
+ :address => self.address,
69
+ :metadata => self.metadata
70
+ }
71
+ end
72
+
73
+ def to_json(*a)
74
+ self.to_hash.to_json(*a)
75
+ end
76
+
77
+ end
78
+
79
+ end