coin-op 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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