coin-op 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +41 -1
- data/lib/coin-op.rb +0 -1
- data/lib/coin-op/bit.rb +11 -0
- data/lib/coin-op/bit/fee.rb +84 -0
- data/lib/coin-op/bit/input.rb +50 -21
- data/lib/coin-op/bit/multi_wallet.rb +30 -15
- data/lib/coin-op/bit/output.rb +28 -10
- data/lib/coin-op/bit/script.rb +42 -0
- data/lib/coin-op/bit/spendable.rb +27 -41
- data/lib/coin-op/bit/transaction.rb +177 -47
- data/lib/coin-op/blockchain/mockchain.rb +2 -0
- data/lib/coin-op/crypto.rb +23 -3
- data/lib/coin-op/encodings.rb +2 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0549717040829929865680910e3d10be98a3a605
|
4
|
+
data.tar.gz: bab2d7e727c99896a8689b9819662c7f7a9c4b89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20025659e8398d0ae10ed292335dcc21b835641339c9c9b864b768640a8b553b00945dd017058826078a2790a70ba5481d228532bc037abd829fac4d789705da
|
7
|
+
data.tar.gz: 33fbc0697dd2d619c9d9dfdfe259338de649fae5497563295afb7ed9ca2d15f2c19f1e9677a79b542cb02c2787fdb9ee0c0c91b3fab82f171b0abe1023d21e3b
|
data/README.md
CHANGED
@@ -5,13 +5,53 @@ Install:
|
|
5
5
|
gem install coin-op
|
6
6
|
|
7
7
|
|
8
|
-
|
8
|
+
Basic usage:
|
9
9
|
|
10
10
|
```ruby
|
11
11
|
require "coin-op"
|
12
12
|
|
13
13
|
include CoinOp::Bit
|
14
14
|
|
15
|
+
transaction = Transaction.from_data(
|
16
|
+
# Override the minimum suggested fee
|
17
|
+
:fee => 20_000,
|
18
|
+
:inputs => [
|
19
|
+
{
|
20
|
+
:output => {
|
21
|
+
:transaction_hash => "2f47a8d7537fd981670b6142f86e1961991577506a825cdfb4c6ab3666db4fc1",
|
22
|
+
:index => 0,
|
23
|
+
:value => 2_000_000
|
24
|
+
}
|
25
|
+
},
|
26
|
+
{
|
27
|
+
:output => {
|
28
|
+
:transaction_hash => "fe4d26f6536c17c451e7d9fd7bca3e981a1c9f4542ee49f3bdcb71050c8ef243",
|
29
|
+
:index => 0,
|
30
|
+
:value => 2_600_000
|
31
|
+
}
|
32
|
+
}
|
33
|
+
],
|
34
|
+
:outputs => [
|
35
|
+
{
|
36
|
+
:value => 3_000_000,
|
37
|
+
:address => "2N9c7acEJNHkDaQvRShMxJcBu5Lw535AvwR"
|
38
|
+
}
|
39
|
+
]
|
40
|
+
)
|
41
|
+
|
42
|
+
transaction.add_change(change_address)
|
43
|
+
|
44
|
+
# Set the script_sigs manually
|
45
|
+
transaction.inputs[0].script_sig = "foo"
|
46
|
+
transaction.inputs[1].script_sig = "bar"
|
47
|
+
|
48
|
+
# Or use an iterating helper method. First argument is an array of
|
49
|
+
# items corresponding to the inputs. The block yields to you each
|
50
|
+
# input, along with the corresponding element from your array.
|
51
|
+
transaction.set_script_sigs *keypairs do |input, keypair|
|
52
|
+
sig_for(keypair, input)
|
53
|
+
end
|
54
|
+
|
15
55
|
```
|
16
56
|
|
17
57
|
# Developers
|
data/lib/coin-op.rb
CHANGED
data/lib/coin-op/bit.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
require "bitcoin"
|
2
|
+
|
3
|
+
# bitcoin-ruby is not multi-network friendly. It's also a hassle
|
4
|
+
# to tell what network you're using if you don't already know.
|
5
|
+
# This makes it a bit easier.
|
6
|
+
Bitcoin::NETWORKS.each do |name, definition|
|
7
|
+
definition[:name] = name
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
# BIP 32 Hierarchical Deterministic Wallets
|
2
12
|
require "money-tree"
|
3
13
|
|
4
14
|
# establish the namespace
|
@@ -15,6 +25,7 @@ require_relative "bit/output"
|
|
15
25
|
require_relative "bit/input"
|
16
26
|
require_relative "bit/transaction"
|
17
27
|
require_relative "bit/spendable"
|
28
|
+
require_relative "bit/fee"
|
18
29
|
|
19
30
|
# Augmented functionality
|
20
31
|
require_relative "bit/multi_wallet"
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
module CoinOp::Bit
|
3
|
+
|
4
|
+
module Fee
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Given an array of unspent Outputs and an array of Outputs for a
|
9
|
+
# Transaction, estimate the fee required for the transaction to be
|
10
|
+
# included in a block. Optionally takes an Integer specifying the
|
11
|
+
# transaction size in bytes, which is necessary when using unspents
|
12
|
+
# that deviate from the customary single signature.
|
13
|
+
#
|
14
|
+
# Returns the estimated fee in satoshis.
|
15
|
+
def estimate(unspents, payees, tx_size=nil)
|
16
|
+
# https://en.bitcoin.it/wiki/Transaction_fees
|
17
|
+
|
18
|
+
# dupe because we'll need to add a change output
|
19
|
+
payees = payees.dup
|
20
|
+
|
21
|
+
unspent_total = unspents.inject(0) {|sum, output| sum += output.value}
|
22
|
+
payee_total = payees.inject(0) {|sum, payee| sum += payee.value}
|
23
|
+
nominal_change = unspent_total - payee_total
|
24
|
+
payees << Output.new(:value => nominal_change)
|
25
|
+
|
26
|
+
tx_size ||= estimate_tx_size(unspents.size, payees.size)
|
27
|
+
min = payees.min_by {|payee| payee.value }
|
28
|
+
|
29
|
+
small = tx_size < 1000
|
30
|
+
big_outputs = min.value > 1_000_000
|
31
|
+
|
32
|
+
p = priority :size => tx_size, :unspents => (unspents.map do |output|
|
33
|
+
{:value => output.value, :age => output.confirmations}
|
34
|
+
end)
|
35
|
+
high_priority = p > PRIORITY_THRESHOLD
|
36
|
+
|
37
|
+
if small && big_outputs && high_priority
|
38
|
+
0
|
39
|
+
else
|
40
|
+
fee_for_bytes(tx_size)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def fee_for_bytes(bytes)
|
46
|
+
# https://en.bitcoin.it/wiki/Transaction_fees
|
47
|
+
# > the reference implementation will round up the transaction size to the
|
48
|
+
# > next thousand bytes and add a fee of 0.1 mBTC (0.0001 BTC) per thousand bytes
|
49
|
+
size = (bytes / 1000) + 1
|
50
|
+
Bitcoin.network[:min_tx_fee] * size
|
51
|
+
end
|
52
|
+
|
53
|
+
# From http://bitcoinfees.com. This estimation is only valid for
|
54
|
+
# transactions with all inputs using the common "public key hash" method
|
55
|
+
# for authorization.
|
56
|
+
def estimate_tx_size(num_inputs, num_outputs)
|
57
|
+
# From http://bitcoinfees.com.
|
58
|
+
(148 * num_inputs) + (34 * num_outputs) + 10
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# https://en.bitcoin.it/wiki/Transaction_fees#Including_in_Blocks
|
63
|
+
#
|
64
|
+
# https://en.bitcoin.it/wiki/Transaction_fees#Technical_info
|
65
|
+
# > Transactions need to have a priority above 57,600,000 to avoid the
|
66
|
+
# > enforced limit.... This threshold is written in the code as
|
67
|
+
# > COIN * 144 / 250, suggesting that the threshold represents a one day
|
68
|
+
# > old, 1 btc coin (144 is the expected number of blocks per day) and a
|
69
|
+
# > transaction size of 250 bytes.
|
70
|
+
PRIORITY_THRESHOLD = 57_600_000
|
71
|
+
|
72
|
+
def priority(params)
|
73
|
+
tx_size, unspents = params.values_at :size, :unspents
|
74
|
+
sum = unspents.inject(0) do |sum, output|
|
75
|
+
age = output[:age] || 0
|
76
|
+
sum += (output[:value] * age)
|
77
|
+
sum
|
78
|
+
end
|
79
|
+
sum / tx_size
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
data/lib/coin-op/bit/input.rb
CHANGED
@@ -1,55 +1,60 @@
|
|
1
1
|
|
2
2
|
module CoinOp::Bit
|
3
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
4
|
class Input
|
5
|
+
|
26
6
|
include CoinOp::Encodings
|
27
7
|
|
28
8
|
attr_reader :native, :output, :binary_sig_hash,
|
29
9
|
:signatures, :sig_hash, :script_sig, :index
|
30
10
|
|
11
|
+
# Takes a Hash containing these fields:
|
12
|
+
#
|
13
|
+
# * :transaction - a Transaction instance
|
14
|
+
# * :index - the index of the input within the transaction
|
15
|
+
# * :output - a value representing this input's unspent output
|
16
|
+
#
|
17
|
+
# Optionally:
|
18
|
+
#
|
19
|
+
# * script_sig_asm - the string form of the scriptSig for this input
|
20
|
+
#
|
31
21
|
def initialize(options={})
|
32
22
|
@transaction, @index, @output =
|
33
23
|
options.values_at :transaction, :index, :output
|
34
24
|
|
25
|
+
script_sig_asm = options[:script_sig_asm]
|
26
|
+
|
35
27
|
unless @output.is_a? Output
|
36
28
|
@output = Output.new(@output)
|
37
29
|
end
|
38
30
|
|
39
31
|
@native = Bitcoin::Protocol::TxIn.new
|
40
32
|
|
33
|
+
# TODO: the reverse is cargo-culted from a function in bitcoin-ruby
|
34
|
+
# that doesn't document the reason. Find the explanation in the bitcoin
|
35
|
+
# wiki or in the reference client source and document here.
|
41
36
|
@native.prev_out = decode_hex(@output.transaction_hash).reverse
|
42
37
|
@native.prev_out_index = @output.index
|
43
38
|
|
39
|
+
if script_sig_asm
|
40
|
+
self.script_sig = Bitcoin::Script.binary_from_string(script_sig_asm)
|
41
|
+
end
|
44
42
|
@signatures = []
|
45
43
|
end
|
46
44
|
|
45
|
+
# Set the sig_hash (the digest used in signing) for this input using a
|
46
|
+
# string of bytes.
|
47
47
|
def binary_sig_hash=(blob)
|
48
|
+
# This is only a setter because of the initial choice to do things
|
49
|
+
# eagerly. Can become an attr_accessor when we move to lazy eval.
|
48
50
|
@binary_sig_hash = blob
|
49
51
|
@sig_hash = hex(blob)
|
50
52
|
end
|
51
53
|
|
54
|
+
# Set the scriptSig for this input using a string of bytes.
|
52
55
|
def script_sig=(blob)
|
56
|
+
# This is only a setter because of the initial choice to do things
|
57
|
+
# eagerly. Can become an attr_accessor when we move to lazy eval.
|
53
58
|
script = Script.new(:blob => blob)
|
54
59
|
@script_sig = script.to_s
|
55
60
|
@native.script_sig = blob
|
@@ -67,6 +72,30 @@ module CoinOp::Bit
|
|
67
72
|
|
68
73
|
end
|
69
74
|
|
75
|
+
# Used in Transaction.from_native or other situations where we do
|
76
|
+
# not have full information about the unspent output being used
|
77
|
+
# for an input.
|
78
|
+
class SparseInput
|
79
|
+
include CoinOp::Encodings
|
80
|
+
|
81
|
+
def initialize(binary_hash, index)
|
82
|
+
@output = {
|
83
|
+
# the binary hash is the result of
|
84
|
+
# [tx.hash].pack("H*").reverse
|
85
|
+
:transaction_hash => hex(binary_hash.reverse),
|
86
|
+
:index => index,
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_json(*a)
|
91
|
+
{
|
92
|
+
:output => @output,
|
93
|
+
}.to_json(*a)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
|
70
99
|
|
71
100
|
end
|
72
101
|
|
@@ -6,32 +6,42 @@ module CoinOp::Bit
|
|
6
6
|
class MultiWallet
|
7
7
|
include CoinOp::Encodings
|
8
8
|
|
9
|
-
|
9
|
+
NetworkMap = {
|
10
|
+
:testnet3 => :bitcoin_testnet,
|
11
|
+
:bitcoin_testnet => :bitcoin_testnet,
|
12
|
+
:bitcoin => :bitcoin
|
13
|
+
}
|
14
|
+
|
15
|
+
def self.generate(names, network_name=:testnet3)
|
16
|
+
unless network = NetworkMap[network_name]
|
17
|
+
raise ArgumentError, "Unknown network #{network_name}"
|
18
|
+
end
|
10
19
|
masters = {}
|
11
20
|
names.each do |name|
|
12
21
|
name = name.to_sym
|
13
22
|
masters[name] = MoneyTree::Master.new(:network => network)
|
14
23
|
end
|
15
|
-
self.new(:private => masters)
|
24
|
+
self.new(:private => masters, :network => network)
|
16
25
|
end
|
17
26
|
|
18
27
|
attr_reader :trees
|
19
28
|
|
20
29
|
def initialize(options)
|
21
|
-
# FIXME: must accept option for which network to use.
|
22
30
|
@private_trees = {}
|
23
31
|
@public_trees = {}
|
24
32
|
@trees = {}
|
25
|
-
|
33
|
+
@network = NetworkMap[options.include? :network ? options[:network] : :testnet3]
|
26
34
|
|
27
35
|
# FIXME: we should allow this.
|
28
|
-
if !private_trees
|
29
|
-
|
30
|
-
end
|
36
|
+
# if !private_trees
|
37
|
+
# raise "Must supply :private"
|
38
|
+
# end
|
31
39
|
|
32
|
-
private_trees
|
33
|
-
|
34
|
-
|
40
|
+
if private_trees = options[:private]
|
41
|
+
private_trees.each do |name, arg|
|
42
|
+
name = name.to_sym
|
43
|
+
@private_trees[name] = @trees[name] = self.get_node(arg)
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
47
|
if public_trees = options[:public]
|
@@ -130,7 +140,8 @@ module CoinOp::Bit
|
|
130
140
|
options = {
|
131
141
|
:path => path,
|
132
142
|
:private => {},
|
133
|
-
:public => {}
|
143
|
+
:public => {},
|
144
|
+
:network => @network
|
134
145
|
}
|
135
146
|
@private_trees.each do |name, node|
|
136
147
|
options[:private][name] = node.node_for_path(path)
|
@@ -142,6 +153,10 @@ module CoinOp::Bit
|
|
142
153
|
MultiNode.new(options)
|
143
154
|
end
|
144
155
|
|
156
|
+
def address(path)
|
157
|
+
path(path).address
|
158
|
+
end
|
159
|
+
|
145
160
|
def valid_output?(output)
|
146
161
|
if path = output.metadata.wallet_path
|
147
162
|
node = self.path(path)
|
@@ -219,6 +234,7 @@ module CoinOp::Bit
|
|
219
234
|
@public_keys = {}
|
220
235
|
@private = options[:private]
|
221
236
|
@public = options[:public]
|
237
|
+
@network = options[:network]
|
222
238
|
|
223
239
|
@private.each do |name, node|
|
224
240
|
key = Bitcoin::Key.new(node.private_key.to_hex, node.public_key.to_hex)
|
@@ -231,9 +247,9 @@ module CoinOp::Bit
|
|
231
247
|
end
|
232
248
|
|
233
249
|
def script(m=2)
|
234
|
-
# m of n
|
250
|
+
# m of n
|
235
251
|
keys = @public_keys.sort_by {|name, key| name }.map {|name, key| key.pub }
|
236
|
-
Script.new(:public_keys => keys, :needed => m)
|
252
|
+
Script.new(:public_keys => keys, :needed => m, :network => @network)
|
237
253
|
end
|
238
254
|
|
239
255
|
def address
|
@@ -243,7 +259,7 @@ module CoinOp::Bit
|
|
243
259
|
alias_method :p2sh_address, :address
|
244
260
|
|
245
261
|
def p2sh_script
|
246
|
-
Script.new(:address => self.script.p2sh_address)
|
262
|
+
Script.new(:address => self.script.p2sh_address, :network => @network)
|
247
263
|
end
|
248
264
|
|
249
265
|
def sign(name, value)
|
@@ -269,4 +285,3 @@ module CoinOp::Bit
|
|
269
285
|
|
270
286
|
|
271
287
|
end
|
272
|
-
|
data/lib/coin-op/bit/output.rb
CHANGED
@@ -5,20 +5,21 @@ module CoinOp::Bit
|
|
5
5
|
include CoinOp::Encodings
|
6
6
|
|
7
7
|
attr_accessor :metadata
|
8
|
-
attr_reader :native, :transaction, :index, :value, :script
|
8
|
+
attr_reader :native, :transaction, :index, :value, :script
|
9
9
|
|
10
10
|
# Takes a Hash with required keys:
|
11
11
|
#
|
12
|
-
# * either
|
13
|
-
# or
|
12
|
+
# * either :transaction (instance of Transaction)
|
13
|
+
# or :transaction_hash (hex-encoded hash of a Bitcoin transaction)
|
14
14
|
# * :index
|
15
|
-
# * :
|
15
|
+
# * :value
|
16
16
|
#
|
17
17
|
# optional keys:
|
18
18
|
#
|
19
|
-
# * :value
|
20
|
-
#
|
21
|
-
#
|
19
|
+
# * either :script (a value usable in Script.new)
|
20
|
+
# or :address (a valid Bitcoin address)
|
21
|
+
# * :metadata (a Hash with arbitrary contents)
|
22
|
+
#
|
22
23
|
def initialize(options)
|
23
24
|
if options[:transaction]
|
24
25
|
@transaction = options[:transaction]
|
@@ -28,26 +29,43 @@ module CoinOp::Bit
|
|
28
29
|
|
29
30
|
# FIXME: be aware of string bitcoin values versus
|
30
31
|
# integer satoshi values
|
31
|
-
@index, @value, @address
|
32
|
+
@index, @value, @address, confirmations =
|
33
|
+
options.values_at :index, :value, :address, :confirmations
|
34
|
+
|
32
35
|
@metadata = options[:metadata] || {}
|
36
|
+
@metadata[:confirmations] ||= confirmations
|
33
37
|
|
34
38
|
if options[:script]
|
35
39
|
@script = Script.new(options[:script])
|
36
|
-
|
37
|
-
|
40
|
+
elsif @address
|
41
|
+
@script = Script.new(:address => @address)
|
38
42
|
end
|
39
43
|
|
44
|
+
|
40
45
|
@native = Bitcoin::Protocol::TxOut.from_hash(
|
41
46
|
"value" => @value.to_s,
|
42
47
|
"scriptPubKey" => @script.to_s
|
43
48
|
)
|
44
49
|
end
|
45
50
|
|
51
|
+
# The bitcoin address generated from the associated Script.
|
52
|
+
def address
|
53
|
+
if @script
|
54
|
+
@script.address
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def confirmations
|
59
|
+
@metadata[:confirmations]
|
60
|
+
end
|
61
|
+
|
62
|
+
# DEPRECATED
|
46
63
|
def set_transaction(transaction, index)
|
47
64
|
@transaction_hash = nil
|
48
65
|
@transaction, @index = transaction, index
|
49
66
|
end
|
50
67
|
|
68
|
+
# Returns the transaction hash for this output.
|
51
69
|
def transaction_hash
|
52
70
|
if @transaction
|
53
71
|
@transaction.hex_hash
|
data/lib/coin-op/bit/script.rb
CHANGED
@@ -1,12 +1,37 @@
|
|
1
1
|
|
2
2
|
module CoinOp::Bit
|
3
3
|
|
4
|
+
# A wrapper class to make it easier to read and write Bitcoin scripts.
|
5
|
+
# Provides a sane #to_json method.
|
4
6
|
class Script
|
5
7
|
include CoinOp::Encodings
|
6
8
|
|
9
|
+
# Accessor for the "native" ::Bitcoin::Script script instance
|
7
10
|
attr_reader :native
|
8
11
|
|
12
|
+
# Takes either a String or a Hash as its argument.
|
13
|
+
#
|
14
|
+
# A String argument will be parsed as a human readable script.
|
15
|
+
#
|
16
|
+
# A Hash argument specifies a script using one of several possible
|
17
|
+
# keys:
|
18
|
+
#
|
19
|
+
# * :string
|
20
|
+
# * :blob
|
21
|
+
# * :hex
|
22
|
+
# * :address
|
23
|
+
# * :public_key
|
24
|
+
# * :public_keys, :needed
|
25
|
+
# * :signatures
|
26
|
+
#
|
27
|
+
# The name of the crypto-currency network may also be specified. It
|
28
|
+
# defaults to :testnet3. Names supplied in this manner must correspond
|
29
|
+
# to the names in the ::Bitcoin::NETWORKS Hash.
|
9
30
|
def initialize(options)
|
31
|
+
# Doing the rescue in case the input argument is a String.
|
32
|
+
network_name = (options[:network] || :testnet3) rescue :testnet3
|
33
|
+
@network = Bitcoin::NETWORKS[network_name]
|
34
|
+
|
10
35
|
# literals
|
11
36
|
if options.is_a? String
|
12
37
|
@blob = Bitcoin::Script.binary_from_string options
|
@@ -19,6 +44,9 @@ module CoinOp::Bit
|
|
19
44
|
# arguments for constructing
|
20
45
|
else
|
21
46
|
if address = options[:address]
|
47
|
+
unless Bitcoin::valid_address?(address)
|
48
|
+
raise ArgumentError, "Invalid address: #{address}"
|
49
|
+
end
|
22
50
|
@blob = Bitcoin::Script.to_address_script(address)
|
23
51
|
elsif public_key = options[:public_key]
|
24
52
|
@blob = Bitcoin::Script.to_pubkey_script(public_key)
|
@@ -36,6 +64,10 @@ module CoinOp::Bit
|
|
36
64
|
@string = @native.to_string
|
37
65
|
end
|
38
66
|
|
67
|
+
def address
|
68
|
+
@native.get_p2sh_address
|
69
|
+
end
|
70
|
+
|
39
71
|
def to_s
|
40
72
|
@string
|
41
73
|
end
|
@@ -53,8 +85,11 @@ module CoinOp::Bit
|
|
53
85
|
def type
|
54
86
|
case self.native.type
|
55
87
|
when :hash160
|
88
|
+
# Pay to address, because an "address" is really just the hash
|
89
|
+
# of a public key.
|
56
90
|
:pubkey_hash
|
57
91
|
when :p2sh
|
92
|
+
# Pay to Script Hash
|
58
93
|
:script_hash
|
59
94
|
else
|
60
95
|
self.native.type
|
@@ -76,6 +111,10 @@ module CoinOp::Bit
|
|
76
111
|
Bitcoin.hash160(@hex)
|
77
112
|
end
|
78
113
|
|
114
|
+
# Generate the script that uses a P2SH address.
|
115
|
+
# Used for an Output's scriptPubKey value. Not much used, and
|
116
|
+
# can probably be removed, as I think it is equivalent to
|
117
|
+
# Script.new :address => some_p2sh_address
|
79
118
|
def p2sh_script
|
80
119
|
self.class.new Bitcoin::Script.to_p2sh_script(self.hash160)
|
81
120
|
end
|
@@ -84,6 +123,9 @@ module CoinOp::Bit
|
|
84
123
|
Bitcoin.hash160_to_p2sh_address(self.hash160)
|
85
124
|
end
|
86
125
|
|
126
|
+
# Generate a P2SH script_sig for the current script, using the
|
127
|
+
# supplied options, which will, in the case of a multisig input,
|
128
|
+
# be {:signatures => array_of_signatures}.
|
87
129
|
def p2sh_sig(options)
|
88
130
|
string = Script.new(options).to_s
|
89
131
|
Bitcoin::Script.binary_from_string("#{string} #{self.to_hex}")
|
@@ -1,9 +1,21 @@
|
|
1
1
|
module CoinOp::Bit
|
2
|
+
|
3
|
+
# A mixin to provide simple transaction preparation. Not currently
|
4
|
+
# used in any production code, so needs vetting.
|
5
|
+
#
|
6
|
+
# Requires the including class to define these methods:
|
7
|
+
#
|
8
|
+
# * network
|
9
|
+
# * balance
|
10
|
+
# * select_unspent
|
11
|
+
# * authorize
|
12
|
+
#
|
2
13
|
module Spendable
|
3
14
|
|
4
15
|
class InsufficientFunds < RuntimeError
|
5
16
|
end
|
6
17
|
|
18
|
+
# Return the network name (must be one of the keys from Bitcoin.network)
|
7
19
|
def network
|
8
20
|
raise "implement #network in your class"
|
9
21
|
end
|
@@ -12,42 +24,24 @@ module CoinOp::Bit
|
|
12
24
|
raise "implement #balance in your class"
|
13
25
|
end
|
14
26
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def select_unspent
|
27
|
+
# Takes a value in satoshis.
|
28
|
+
# Returns an array of spendable Outputs
|
29
|
+
def select_unspent(value)
|
20
30
|
raise "implement #select_unspent in your class"
|
21
31
|
end
|
22
32
|
|
23
|
-
|
33
|
+
# Authorize the supplied transaction by setting its inputs' script_sigs
|
34
|
+
# to whatever values are appropriate.
|
35
|
+
def authorize(transaction)
|
24
36
|
raise "implement #authorize in your class"
|
25
37
|
end
|
26
38
|
|
27
|
-
def
|
28
|
-
# FIXME: use the return value of #network as the arg, once this ticket
|
29
|
-
# is resolved: # https://github.com/BitVault/bitvault/issues/251
|
30
|
-
@blockchain ||= BitVaultAPI::Blockchain::Blockr.new(:test)
|
31
|
-
end
|
32
|
-
|
33
|
-
def lock(outputs)
|
34
|
-
# no op
|
35
|
-
# Mixing classes may wish to lock down these selected outputs
|
36
|
-
# so that concurrent payments or transfers cannot use them.
|
37
|
-
#
|
38
|
-
# When do we release unspents (if a user abandons a transaction)?
|
39
|
-
end
|
40
|
-
|
41
|
-
def unlock(outputs)
|
42
|
-
end
|
43
|
-
|
44
|
-
def create_transaction(outputs, change_address)
|
39
|
+
def create_transaction(outputs, change_address, fee_amount=nil)
|
45
40
|
|
46
|
-
transaction = CoinOp::Bit::Transaction.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
41
|
+
transaction = CoinOp::Bit::Transaction.from_data(
|
42
|
+
:fee => fee_amount,
|
43
|
+
:outputs => outputs
|
44
|
+
)
|
51
45
|
|
52
46
|
if self.balance < transaction.output_value
|
53
47
|
raise InsufficientFunds
|
@@ -56,29 +50,20 @@ module CoinOp::Bit
|
|
56
50
|
unspent = self.select_unspent(transaction.output_value)
|
57
51
|
|
58
52
|
unspent.each do |output|
|
59
|
-
transaction.add_input output
|
53
|
+
transaction.add_input :output => output
|
60
54
|
end
|
61
55
|
|
62
56
|
input_amount = unspent.inject(0) {|sum, output| sum += output.value }
|
63
|
-
fee = transaction.suggested_fee
|
64
57
|
|
65
58
|
# FIXME: there's likely another unspent output we can add, but the present
|
66
59
|
# implementation of all this can't easily help us. Possibly stop
|
67
60
|
# using select_unspent(value) and start using a while loop that shifts
|
68
61
|
# outputs off the array. Then we can start the process over.
|
69
|
-
|
62
|
+
unless transaction.funded?
|
70
63
|
raise InsufficientFunds
|
71
64
|
end
|
72
65
|
|
73
|
-
|
74
|
-
|
75
|
-
transaction.add_output(
|
76
|
-
:value => change,
|
77
|
-
:script => {
|
78
|
-
:address => change_address
|
79
|
-
},
|
80
|
-
:address => change_address,
|
81
|
-
)
|
66
|
+
transaction.add_change change_address
|
82
67
|
|
83
68
|
self.authorize(transaction)
|
84
69
|
transaction
|
@@ -86,3 +71,4 @@ module CoinOp::Bit
|
|
86
71
|
|
87
72
|
end
|
88
73
|
end
|
74
|
+
|
@@ -1,15 +1,57 @@
|
|
1
|
-
|
2
1
|
module CoinOp::Bit
|
3
2
|
|
4
3
|
class Transaction
|
5
4
|
include CoinOp::Encodings
|
6
5
|
|
6
|
+
# Deprecated. Easier to use Transaction.from_data
|
7
7
|
def self.build(&block)
|
8
8
|
transaction = self.new
|
9
9
|
yield transaction
|
10
10
|
transaction
|
11
11
|
end
|
12
12
|
|
13
|
+
# Construct a Transaction from a data structure of nested Hashes
|
14
|
+
# and Arrays.
|
15
|
+
def self.data(data)
|
16
|
+
version, lock_time, fee, inputs, outputs, confirmations =
|
17
|
+
data.values_at :version, :lock_time, :fee, :inputs, :outputs, :confirmations
|
18
|
+
|
19
|
+
transaction = self.new(
|
20
|
+
:fee => fee,
|
21
|
+
:version => version, :lock_time => lock_time,
|
22
|
+
:confirmations => confirmations
|
23
|
+
)
|
24
|
+
|
25
|
+
outputs.each do |data|
|
26
|
+
transaction.add_output Output.new(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
#FIXME: we're not handling sig_scripts for already signed inputs.
|
30
|
+
|
31
|
+
if inputs
|
32
|
+
# TODO: use #each instead of #each_with_index
|
33
|
+
inputs.each_with_index do |data, index|
|
34
|
+
transaction.add_input(data)
|
35
|
+
|
36
|
+
## FIXME: verify that the supplied and computed sig_hashes match
|
37
|
+
#puts :sig_hashes_match => (data[:sig_hash] == input.sig_hash)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
transaction
|
42
|
+
end
|
43
|
+
|
44
|
+
# Construct a Transaction from raw bytes.
|
45
|
+
def self.raw(raw_tx)
|
46
|
+
self.native ::Bitcoin::Protocol::Tx.new(raw_tx)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a Transaction from a hex representation of the raw bytes.
|
50
|
+
def self.hex(hex)
|
51
|
+
self.from_bytes CoinOp::Encodings.decode_hex(hex)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Construct a transaction from an instance of ::Bitcoin::Protocol::Tx
|
13
55
|
def self.native(tx)
|
14
56
|
transaction = self.new()
|
15
57
|
# TODO: reconsider use of instance_eval
|
@@ -33,49 +75,39 @@ module CoinOp::Bit
|
|
33
75
|
|
34
76
|
report = transaction.validate_syntax
|
35
77
|
unless report[:valid] == true
|
36
|
-
raise "Invalid syntax: #{report[:
|
78
|
+
raise "Invalid syntax: #{report[:error].to_json}"
|
37
79
|
end
|
38
80
|
transaction
|
39
81
|
end
|
40
82
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
83
|
+
# Preparation for interface change, where the from_foo methods become
|
84
|
+
# preferred, and the terser method names are deprecated.
|
85
|
+
#
|
86
|
+
# This nasty little construct allows us to work on the class's metaclass.
|
87
|
+
class << self
|
88
|
+
alias_method :from_data, :data
|
89
|
+
alias_method :from_hex, :hex
|
90
|
+
alias_method :from_bytes, :raw
|
91
|
+
alias_method :from_native, :native
|
47
92
|
end
|
48
93
|
|
49
|
-
def self.data(hash)
|
50
|
-
version, lock_time, tx_hash, inputs, outputs =
|
51
|
-
hash.values_at :version, :lock_time, :tx_hash, :inputs, :outputs
|
52
|
-
|
53
|
-
transaction = self.new
|
54
|
-
|
55
|
-
outputs.each do |data|
|
56
|
-
transaction.add_output Output.new(data)
|
57
|
-
end
|
58
|
-
|
59
|
-
#FIXME: we're not handling sig_scripts for already signed inputs.
|
60
|
-
|
61
|
-
inputs.each_with_index do |data, index|
|
62
|
-
transaction.add_input data[:output]
|
63
|
-
|
64
|
-
## FIXME: verify that the supplied and computed sig_hashes match
|
65
|
-
#puts :sig_hashes_match => (data[:sig_hash] == input.sig_hash)
|
66
|
-
end
|
67
|
-
|
68
|
-
transaction
|
69
|
-
end
|
70
94
|
|
71
|
-
attr_reader :native, :inputs, :outputs
|
95
|
+
attr_reader :native, :inputs, :outputs, :confirmations
|
72
96
|
|
73
|
-
|
74
|
-
|
97
|
+
# A new Transaction contains no inputs or outputs; these can be added with
|
98
|
+
# #add_input and #add_output.
|
99
|
+
# FIXME: version and locktime options are ignored here.
|
100
|
+
def initialize(options={})
|
101
|
+
@native = Bitcoin::Protocol::Tx.new
|
75
102
|
@inputs = []
|
76
103
|
@outputs = []
|
104
|
+
@fee_override = options[:fee]
|
105
|
+
@confirmations = options[:confirmations]
|
77
106
|
end
|
78
107
|
|
108
|
+
# Update the "native" bitcoin-ruby instances for the transaction and
|
109
|
+
# all its inputs. Will be removed when we rework the wrapper classes
|
110
|
+
# to be lazy, rather than eager.
|
79
111
|
def update_native
|
80
112
|
yield @native if block_given?
|
81
113
|
@native = Bitcoin::Protocol::Tx.new(@native.to_payload)
|
@@ -91,6 +123,11 @@ module CoinOp::Bit
|
|
91
123
|
end
|
92
124
|
end
|
93
125
|
|
126
|
+
# Monkeypatch to remove a test that fails because bitcoin-ruby thinks a
|
127
|
+
# transaction doesn't have valid syntax when it contains a coinbase input.
|
128
|
+
Bitcoin::Validation::Tx::RULES[:syntax].delete(:inputs)
|
129
|
+
|
130
|
+
# Validate that the transaction is plausibly signable.
|
94
131
|
def validate_syntax
|
95
132
|
update_native
|
96
133
|
validator = Bitcoin::Validation::Tx.new(@native, nil)
|
@@ -98,6 +135,7 @@ module CoinOp::Bit
|
|
98
135
|
{:valid => valid, :error => validator.error}
|
99
136
|
end
|
100
137
|
|
138
|
+
# Verify that the script_sigs for all inputs are valid.
|
101
139
|
def validate_script_sigs
|
102
140
|
bad_inputs = []
|
103
141
|
valid = true
|
@@ -119,16 +157,14 @@ module CoinOp::Bit
|
|
119
157
|
# * an instance of Output
|
120
158
|
# * a Hash describing an Output
|
121
159
|
#
|
122
|
-
def add_input(
|
160
|
+
def add_input(input)
|
123
161
|
# TODO: allow specifying prev_tx and index with a Hash.
|
124
162
|
# Possibly stop using SparseInput.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
input = Input.new(
|
163
|
+
|
164
|
+
unless input.is_a?(Input)
|
165
|
+
input = Input.new input.merge(
|
129
166
|
:transaction => self,
|
130
167
|
:index => @inputs.size,
|
131
|
-
:output => arg
|
132
168
|
)
|
133
169
|
end
|
134
170
|
|
@@ -139,12 +175,15 @@ module CoinOp::Bit
|
|
139
175
|
input
|
140
176
|
end
|
141
177
|
|
178
|
+
# Takes either an Output or a Hash describing an output.
|
142
179
|
def add_output(output)
|
143
180
|
unless output.is_a? Output
|
144
181
|
output = Output.new(output)
|
145
182
|
end
|
146
183
|
|
147
184
|
index = @outputs.size
|
185
|
+
# TODO: stop using set_transaction and just pass self to Output.new
|
186
|
+
# Then remove output.set_transaction
|
148
187
|
output.set_transaction self, index
|
149
188
|
@outputs << output
|
150
189
|
self.update_native do |native|
|
@@ -152,11 +191,13 @@ module CoinOp::Bit
|
|
152
191
|
end
|
153
192
|
end
|
154
193
|
|
194
|
+
# Returns the transaction hash as a string of bytes.
|
155
195
|
def binary_hash
|
156
196
|
update_native
|
157
197
|
@native.binary_hash
|
158
198
|
end
|
159
199
|
|
200
|
+
# Returns the transaction hash encoded as hex
|
160
201
|
def hex_hash
|
161
202
|
update_native
|
162
203
|
@native.hash
|
@@ -170,27 +211,36 @@ module CoinOp::Bit
|
|
170
211
|
@native.lock_time
|
171
212
|
end
|
172
213
|
|
214
|
+
# Returns the transaction payload encoded as hex. This value can
|
215
|
+
# be used by other bitcoin tools for publishing to the network.
|
173
216
|
def to_hex
|
174
217
|
payload = self.native.to_payload
|
175
218
|
CoinOp::Encodings.hex(payload)
|
176
219
|
end
|
177
220
|
|
178
|
-
|
179
|
-
|
180
|
-
end
|
181
|
-
|
221
|
+
# Returns a custom data structure representing the full transaction.
|
222
|
+
# Typically used only by #to_json.
|
182
223
|
def to_hash
|
183
224
|
{
|
184
225
|
:version => self.version,
|
185
226
|
:lock_time => self.lock_time,
|
186
227
|
:hash => self.hex_hash,
|
228
|
+
:fee => self.fee,
|
187
229
|
:inputs => self.inputs,
|
188
230
|
:outputs => self.outputs,
|
189
231
|
}
|
190
232
|
end
|
191
233
|
|
234
|
+
def to_json(*a)
|
235
|
+
self.to_hash.to_json(*a)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Compute the digest for a given input. In most cases, you need to provide
|
239
|
+
# the script. Which script to supply can be confusing, especially in the
|
240
|
+
# case of P2SH outputs.
|
241
|
+
# TODO: explain the above more clearly.
|
192
242
|
def sig_hash(input, script=nil)
|
193
|
-
#
|
243
|
+
# We only allow SIGHASH_ALL at this time
|
194
244
|
# https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29
|
195
245
|
|
196
246
|
prev_out = input.output
|
@@ -199,6 +249,28 @@ module CoinOp::Bit
|
|
199
249
|
@native.signature_hash_for_input(input.index, nil, script.to_blob)
|
200
250
|
end
|
201
251
|
|
252
|
+
# A convenience method for authorizing inputs in a generic manner.
|
253
|
+
# Rather than iterating over the inputs manually, the user can
|
254
|
+
# provide this method with an array of values and a block that
|
255
|
+
# knows what to do with the values.
|
256
|
+
#
|
257
|
+
# For example, if you happen to have the script sigs precomputed
|
258
|
+
# for some strange reason, you could do this:
|
259
|
+
#
|
260
|
+
# tx.set_script_sigs sig_array do |input, sig|
|
261
|
+
# sig
|
262
|
+
# end
|
263
|
+
#
|
264
|
+
# More realistically, if you have an array of the keypairs corresponding
|
265
|
+
# to the inputs:
|
266
|
+
#
|
267
|
+
# tx.set_script_sigs keys do |input, key|
|
268
|
+
# sig_hash = tx.sig_hash(input)
|
269
|
+
# key.sign(sig_hash)
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# Each element of the array may be an array, which allows for easy handling
|
273
|
+
# of multisig situations.
|
202
274
|
def set_script_sigs(*input_args, &block)
|
203
275
|
# No sense trying to authorize when the transaction isn't usable.
|
204
276
|
report = validate_syntax
|
@@ -207,19 +279,32 @@ module CoinOp::Bit
|
|
207
279
|
end
|
208
280
|
|
209
281
|
# Array#zip here allows us to iterate over the inputs in lockstep with any
|
210
|
-
# number of sets of
|
282
|
+
# number of sets of values.
|
211
283
|
self.inputs.zip(*input_args) do |input, *input_arg|
|
212
284
|
input.script_sig = yield input, *input_arg
|
213
285
|
end
|
214
286
|
end
|
215
287
|
|
288
|
+
def fee_override
|
289
|
+
@fee_override || self.estimate_fee
|
290
|
+
end
|
216
291
|
|
217
|
-
|
218
|
-
|
292
|
+
# Estimate the fee in satoshis for this transaction. Takes an optional
|
293
|
+
# tx_size argument because it is impossible to determine programmatically
|
294
|
+
# the size of the scripts used to create P2SH outputs.
|
295
|
+
# Rough testing of the size of a 2of3 multisig p2sh input: 297
|
296
|
+
def estimate_fee(tx_size=nil)
|
297
|
+
unspents = inputs.map(&:output)
|
298
|
+
Fee.estimate(unspents, outputs, tx_size)
|
219
299
|
end
|
220
300
|
|
301
|
+
# Returns the transaction fee computed from the actual input and output
|
302
|
+
# values, as opposed to the requested override fee or the estimated fee.
|
303
|
+
def fee
|
304
|
+
input_value - output_value rescue nil
|
305
|
+
end
|
221
306
|
|
222
|
-
# Total value
|
307
|
+
# Total value of all outputs.
|
223
308
|
def output_value
|
224
309
|
total = 0
|
225
310
|
@outputs.each do |output|
|
@@ -229,6 +314,51 @@ module CoinOp::Bit
|
|
229
314
|
total
|
230
315
|
end
|
231
316
|
|
317
|
+
# Are the currently selected inputs sufficient to cover the current
|
318
|
+
# outputs and the desired fee?
|
319
|
+
def funded?
|
320
|
+
input_value >= (output_value + fee_override)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Total value of all inputs.
|
324
|
+
def input_value
|
325
|
+
inputs.inject(0) { |sum, input| sum += input.output.value }
|
326
|
+
end
|
327
|
+
|
328
|
+
# Takes a set of Bitcoin addresses and returns the net change in value
|
329
|
+
# expressed in this transaction.
|
330
|
+
def value_for(addresses)
|
331
|
+
output_value_for(addresses) - input_value_for(addresses)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Takes a set of Bitcoin addresses and returns the value expressed in
|
335
|
+
# the inputs for this transaction.
|
336
|
+
def input_value_for(addresses)
|
337
|
+
own = inputs.select { |input| addresses.include?(input.output.address) }
|
338
|
+
own.inject(0) { |sum, input| input.output.value }
|
339
|
+
end
|
340
|
+
|
341
|
+
# Takes a set of Bitcoin addresses and returns the value expressed in
|
342
|
+
# the outputs for this transaction.
|
343
|
+
def output_value_for(addresses)
|
344
|
+
own = outputs.select { |output| addresses.include?(output.address) }
|
345
|
+
own.inject(0) { |sum, output| output.value }
|
346
|
+
end
|
347
|
+
|
348
|
+
# Returns the value that should be assigned to a change output.
|
349
|
+
def change_value
|
350
|
+
input_value - (output_value + fee_override)
|
351
|
+
end
|
352
|
+
|
353
|
+
# Add an output to receive change for this transaction.
|
354
|
+
# Takes a bitcoin address and optional metadata Hash.
|
355
|
+
def add_change(address, metadata={})
|
356
|
+
add_output(
|
357
|
+
:value => change_value,
|
358
|
+
:address => address,
|
359
|
+
:metadata => {:memo => "change"}.merge(metadata)
|
360
|
+
)
|
361
|
+
end
|
232
362
|
|
233
363
|
end
|
234
364
|
|
data/lib/coin-op/crypto.rb
CHANGED
@@ -1,19 +1,39 @@
|
|
1
|
+
# Ruby bindings for libsodium, a port of DJB's NaCl crypto library
|
1
2
|
require "rbnacl"
|
2
3
|
require "openssl"
|
3
4
|
|
4
5
|
module CoinOp
|
5
6
|
module Crypto
|
6
7
|
|
8
|
+
# A wrapper for NaCl's Secret Box, taking a user-supplied passphrase
|
9
|
+
# and deriving a secret key, rather than using a (far more secure)
|
10
|
+
# randomly generated secret key.
|
11
|
+
#
|
12
|
+
# NaCl Secret Box provides a high level interface for authenticated
|
13
|
+
# symmetric encryption. When creating the box, you must supply a key.
|
14
|
+
# When using the box to encrypt, you must supply a random nonce. Nonces
|
15
|
+
# must never be re-used.
|
16
|
+
#
|
17
|
+
# Secret Box decryption requires the ciphertext and the nonce used to
|
18
|
+
# create it.
|
19
|
+
#
|
20
|
+
# The PassphraseBox class takes a passphrase, rather than a randomly
|
21
|
+
# generated key. It uses PBKDF2 to generate a key that, while not random,
|
22
|
+
# is somewhat resistant to brute force attacks. Great care should still
|
23
|
+
# be taken to avoid passphrases that are subject to dictionary attacks.
|
7
24
|
class PassphraseBox
|
8
|
-
|
25
|
+
|
26
|
+
# Both class and instance methods need encoding help, so we supply
|
27
|
+
# them to both scopes using extend and include, respectively.
|
9
28
|
extend CoinOp::Encodings
|
29
|
+
include CoinOp::Encodings
|
10
30
|
|
11
31
|
# PBKDF2 work factor
|
12
32
|
ITERATIONS = 100_000
|
13
33
|
|
14
34
|
# Given passphrase and plaintext as strings, returns a Hash
|
15
35
|
# containing the ciphertext and other values needed for later
|
16
|
-
# decryption.
|
36
|
+
# decryption. Binary values are encoded as hexadecimal strings.
|
17
37
|
def self.encrypt(passphrase, plaintext)
|
18
38
|
box = self.new(passphrase)
|
19
39
|
box.encrypt(plaintext)
|
@@ -53,8 +73,8 @@ module CoinOp
|
|
53
73
|
nonce = RbNaCl::Random.random_bytes(RbNaCl::SecretBox.nonce_bytes)
|
54
74
|
ciphertext = @box.encrypt(nonce, plaintext)
|
55
75
|
{
|
56
|
-
:salt => hex(@salt),
|
57
76
|
:iterations => @iterations,
|
77
|
+
:salt => hex(@salt),
|
58
78
|
:nonce => hex(nonce),
|
59
79
|
:ciphertext => hex(ciphertext)
|
60
80
|
}
|
data/lib/coin-op/encodings.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coin-op
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew King
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bitcoin-ruby
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.
|
19
|
+
version: 0.0.6
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0.
|
26
|
+
version: 0.0.6
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: money-tree
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- README.md
|
108
108
|
- lib/coin-op.rb
|
109
109
|
- lib/coin-op/bit.rb
|
110
|
+
- lib/coin-op/bit/fee.rb
|
110
111
|
- lib/coin-op/bit/input.rb
|
111
112
|
- lib/coin-op/bit/multi_wallet.rb
|
112
113
|
- lib/coin-op/bit/output.rb
|
@@ -137,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
138
|
version: '0'
|
138
139
|
requirements: []
|
139
140
|
rubyforge_project:
|
140
|
-
rubygems_version: 2.2.
|
141
|
+
rubygems_version: 2.2.2
|
141
142
|
signing_key:
|
142
143
|
specification_version: 4
|
143
144
|
summary: Crypto currency classes in Ruby
|