coin-op 0.2.1 → 0.2.2
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.
- 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
|