coin-op 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -1
- data/lib/coin-op/bit.rb +232 -1
- data/lib/coin-op/bit/fee.rb +5 -5
- data/lib/coin-op/bit/input.rb +3 -3
- data/lib/coin-op/bit/multi_wallet.rb +33 -42
- data/lib/coin-op/bit/output.rb +6 -3
- data/lib/coin-op/bit/script.rb +42 -38
- data/lib/coin-op/bit/transaction.rb +25 -17
- data/lib/coin-op/crypto.rb +1 -1
- data/lib/coin-op/version.rb +2 -2
- metadata +2 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb3f0c14ebb0842dde25e82a66276a7f8696ee02
|
4
|
+
data.tar.gz: e9cb449657d9ee89a43c779da8ef4e5d25997ffa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dddf2781e3e9d3aaade199ab6cf30979138f796457fa8d8dfe81d0c656e4224f027571310fd5521980a6dda2baaf102ed50347bde2ebf98aa941aed79e110b9
|
7
|
+
data.tar.gz: 92a224650077948d9a84580349b379e2089b2dc5c4f9ed7ab0f3a916b2588b5c585f1e5a363dfaa89db39be507ccbb9b289476211a12497265162e9c871b82d2
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
�j� �C=��aYbc�X��
|
data/lib/coin-op/bit.rb
CHANGED
@@ -1,4 +1,61 @@
|
|
1
|
-
require
|
1
|
+
require 'bitcoin'
|
2
|
+
|
3
|
+
Bitcoin::NETWORKS[:dogecoin] = Bitcoin::NETWORKS[:litecoin].merge({
|
4
|
+
project: :dogecoin,
|
5
|
+
magic_head: "\xc0\xc0\xc0\xc0",
|
6
|
+
address_version: "1e",
|
7
|
+
p2sh_version: "16",
|
8
|
+
privkey_version: "9e",
|
9
|
+
default_port: 22556,
|
10
|
+
protocol_version: 70003,
|
11
|
+
max_money: 100_000_000_000 * Bitcoin::COIN,
|
12
|
+
min_tx_fee: Bitcoin::COIN,
|
13
|
+
min_relay_tx_fee: Bitcoin::COIN,
|
14
|
+
free_tx_bytes: 26_000,
|
15
|
+
dust: Bitcoin::COIN,
|
16
|
+
per_dust_fee: true,
|
17
|
+
coinbase_maturity: 30,
|
18
|
+
coinbase_maturity_new: 240,
|
19
|
+
reward_base: 500_000 * Bitcoin::COIN,
|
20
|
+
reward_halving: 100_000,
|
21
|
+
retarget_interval: 240,
|
22
|
+
retarget_time: 14400, # 4 hours
|
23
|
+
retarget_time_new: 60, # 1 minute
|
24
|
+
target_spacing: 60, # block interval
|
25
|
+
dns_seeds: [
|
26
|
+
"seed.dogechain.info",
|
27
|
+
"seed.dogecoin.com",
|
28
|
+
],
|
29
|
+
genesis_hash: "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691",
|
30
|
+
proof_of_work_limit: 0x1e0fffff,
|
31
|
+
alert_pubkeys: [],
|
32
|
+
known_nodes: [
|
33
|
+
"daemons.chain.so",
|
34
|
+
"bootstrap.chain.so",
|
35
|
+
],
|
36
|
+
checkpoints: {
|
37
|
+
0 => "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691",
|
38
|
+
42279 => "8444c3ef39a46222e87584ef956ad2c9ef401578bd8b51e8e4b9a86ec3134d3a",
|
39
|
+
42400 => "557bb7c17ed9e6d4a6f9361cfddf7c1fc0bdc394af7019167442b41f507252b4",
|
40
|
+
104679 => "35eb87ae90d44b98898fec8c39577b76cb1eb08e1261cfc10706c8ce9a1d01cf",
|
41
|
+
128370 => "3f9265c94cab7dc3bd6a2ad2fb26c8845cb41cff437e0a75ae006997b4974be6",
|
42
|
+
145000 => "cc47cae70d7c5c92828d3214a266331dde59087d4a39071fa76ddfff9b7bde72",
|
43
|
+
165393 => "7154efb4009e18c1c6a6a79fc6015f48502bcd0a1edd9c20e44cd7cbbe2eeef1",
|
44
|
+
186774 => "3c712c49b34a5f34d4b963750d6ba02b73e8a938d2ee415dcda141d89f5cb23a",
|
45
|
+
199992 => "3408ff829b7104eebaf61fd2ba2203ef2a43af38b95b353e992ef48f00ebb190",
|
46
|
+
225000 => "be148d9c5eab4a33392a6367198796784479720d06bfdd07bd547fe934eea15a",
|
47
|
+
250000 => "0e4bcfe8d970979f7e30e2809ab51908d435677998cf759169407824d4f36460",
|
48
|
+
270639 => "c587a36dd4f60725b9dd01d99694799bef111fc584d659f6756ab06d2a90d911",
|
49
|
+
299742 => "1cc89c0c8a58046bf0222fe131c099852bd9af25a80e07922918ef5fb39d6742",
|
50
|
+
323141 => "60c9f919f9b271add6ef5671e9538bad296d79f7fdc6487ba702bf2ba131d31d",
|
51
|
+
339202 => "8c29048df5ae9df38a67ea9470fdd404d281a3a5c6f33080cd5bf14aa496ab03"
|
52
|
+
},
|
53
|
+
auxpow_chain_id: 0x0062,
|
54
|
+
# Doge-specific hard-fork cutoffs
|
55
|
+
difficulty_change_block: 145000,
|
56
|
+
maturity_change_block: 145000,
|
57
|
+
auxpow_start_block: 371337
|
58
|
+
})
|
2
59
|
|
3
60
|
# bitcoin-ruby is not multi-network friendly. It's also a hassle
|
4
61
|
# to tell what network you're using if you don't already know.
|
@@ -7,12 +64,185 @@ Bitcoin::NETWORKS.each do |name, definition|
|
|
7
64
|
definition[:name] = name
|
8
65
|
end
|
9
66
|
|
67
|
+
module Bitcoin
|
68
|
+
module OpenSSL_EC
|
69
|
+
attach_function :d2i_ECDSA_SIG, [:pointer, :pointer, :long], :pointer
|
70
|
+
attach_function :i2d_ECDSA_SIG, [:pointer, :pointer], :int
|
71
|
+
attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void
|
72
|
+
attach_function :BN_rshift1, [:pointer, :pointer], :int
|
73
|
+
attach_function :BN_sub, [:pointer, :pointer, :pointer], :int
|
74
|
+
attach_function :BN_num_bits, [:pointer], :int
|
75
|
+
|
76
|
+
def self.BN_num_bytes(ptr); (BN_num_bits(ptr) + 7) / 8; end
|
77
|
+
|
78
|
+
# repack signature for OpenSSL 1.0.1k handling of DER signatures
|
79
|
+
# https://github.com/bitcoin/bitcoin/pull/5634/files
|
80
|
+
def self.repack_der_signature(signature)
|
81
|
+
init_ffi_ssl
|
82
|
+
|
83
|
+
return false if signature.empty?
|
84
|
+
|
85
|
+
# New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
|
86
|
+
norm_der = FFI::MemoryPointer.new(:pointer)
|
87
|
+
sig_ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, FFI::MemoryPointer.from_string(signature))
|
88
|
+
|
89
|
+
norm_sig = d2i_ECDSA_SIG(nil, sig_ptr, signature.bytesize)
|
90
|
+
|
91
|
+
derlen = i2d_ECDSA_SIG(norm_sig, norm_der)
|
92
|
+
ECDSA_SIG_free(norm_sig)
|
93
|
+
return false if derlen <= 0
|
94
|
+
|
95
|
+
ret = norm_der.read_pointer.read_string(derlen)
|
96
|
+
OPENSSL_free(norm_der.read_pointer)
|
97
|
+
|
98
|
+
ret
|
99
|
+
end
|
100
|
+
|
101
|
+
# Regenerate a DER-encoded signature such that the S-value complies with the BIP62
|
102
|
+
# specification.
|
103
|
+
#
|
104
|
+
def self.signature_to_low_s(signature)
|
105
|
+
init_ffi_ssl
|
106
|
+
|
107
|
+
temp = signature.unpack("C*")
|
108
|
+
length_r = temp[3]
|
109
|
+
length_s = temp[5+length_r]
|
110
|
+
sig = FFI::MemoryPointer.from_string(signature)
|
111
|
+
|
112
|
+
# Calculate the lower s value
|
113
|
+
s = BN_bin2bn(sig[6 + length_r], length_s, BN_new())
|
114
|
+
eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
|
115
|
+
group, order, halforder, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_new(), BN_CTX_new()
|
116
|
+
|
117
|
+
EC_GROUP_get_order(group, order, ctx)
|
118
|
+
BN_rshift1(halforder, order)
|
119
|
+
if BN_cmp(s, halforder) > 0
|
120
|
+
BN_sub(s, order, s)
|
121
|
+
end
|
122
|
+
|
123
|
+
BN_free(halforder)
|
124
|
+
BN_free(order)
|
125
|
+
BN_CTX_free(ctx)
|
126
|
+
|
127
|
+
buf = FFI::MemoryPointer.new(:uint8, BN_num_bytes(s))
|
128
|
+
BN_bn2bin(s, buf)
|
129
|
+
length_s = BN_num_bytes(s)
|
130
|
+
# p buf.read_string(length_s).unpack("H*")
|
131
|
+
|
132
|
+
# Re-encode the signature in DER format
|
133
|
+
sig = [0x30, 0, 0x02, length_r]
|
134
|
+
sig.concat(temp.slice(4, length_r))
|
135
|
+
sig << 0x02
|
136
|
+
sig << length_s
|
137
|
+
sig.concat(buf.read_string(length_s).unpack("C*"))
|
138
|
+
sig[1] = sig.size - 2
|
139
|
+
|
140
|
+
BN_free(s)
|
141
|
+
EC_KEY_free(eckey)
|
142
|
+
|
143
|
+
sig.pack("C*")
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
module Util
|
149
|
+
def verify_signature(hash, signature, public_key)
|
150
|
+
key = bitcoin_elliptic_curve
|
151
|
+
key.public_key = ::OpenSSL::PKey::EC::Point.from_hex(key.group, public_key)
|
152
|
+
signature = Bitcoin::OpenSSL_EC.repack_der_signature(signature)
|
153
|
+
if signature
|
154
|
+
key.dsa_verify_asn1(hash, signature)
|
155
|
+
else
|
156
|
+
false
|
157
|
+
end
|
158
|
+
rescue OpenSSL::PKey::ECError, OpenSSL::PKey::EC::Point::Error
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Key
|
164
|
+
# Sign +data+ with the key.
|
165
|
+
# key1 = Bitcoin::Key.generate
|
166
|
+
# sig = key1.sign("some data")
|
167
|
+
def sign(data)
|
168
|
+
sig = @key.dsa_sign_asn1(data)
|
169
|
+
if Script::is_low_der_signature?(sig)
|
170
|
+
sig
|
171
|
+
else
|
172
|
+
Bitcoin::OpenSSL_EC.signature_to_low_s(sig)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Verify signature +sig+ for +data+.
|
177
|
+
# key2 = Bitcoin::Key.new(nil, key1.pub)
|
178
|
+
# key2.verify("some data", sig)
|
179
|
+
def verify(data, sig)
|
180
|
+
regenerate_pubkey unless @key.public_key
|
181
|
+
sig = Bitcoin::OpenSSL_EC.repack_der_signature(sig)
|
182
|
+
if sig
|
183
|
+
@key.dsa_verify_asn1(data, sig)
|
184
|
+
else
|
185
|
+
false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Script
|
191
|
+
attr_reader :raw, :chunks, :debug, :stack
|
192
|
+
|
193
|
+
# Loosely correlates with IsLowDERSignature() from interpreter.cpp
|
194
|
+
def self.is_low_der_signature?(sig)
|
195
|
+
s = sig.unpack("C*")
|
196
|
+
|
197
|
+
length_r = s[3]
|
198
|
+
length_s = s[5+length_r]
|
199
|
+
s_val = s.slice(6 + length_r, length_s)
|
200
|
+
|
201
|
+
# If the S value is above the order of the curve divided by two, its
|
202
|
+
# complement modulo the order could have been used instead, which is
|
203
|
+
# one byte shorter when encoded correctly.
|
204
|
+
max_mod_half_order = [
|
205
|
+
0x7f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
206
|
+
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
|
207
|
+
0x5d,0x57,0x6e,0x73,0x57,0xa4,0x50,0x1d,
|
208
|
+
0xdf,0xe9,0x2f,0x46,0x68,0x1b,0x20,0xa0]
|
209
|
+
|
210
|
+
compare_big_endian(s_val, [0]) > 0 &&
|
211
|
+
compare_big_endian(s_val, max_mod_half_order) <= 0
|
212
|
+
end
|
213
|
+
|
214
|
+
# Compares two arrays of bytes
|
215
|
+
def self.compare_big_endian(c1, c2)
|
216
|
+
c1, c2 = c1.dup, c2.dup # Clone the arrays
|
217
|
+
|
218
|
+
while c1.size > c2.size
|
219
|
+
return 1 if c1.shift > 0
|
220
|
+
end
|
221
|
+
|
222
|
+
while c2.size > c1.size
|
223
|
+
return -1 if c2.shift > 0
|
224
|
+
end
|
225
|
+
|
226
|
+
c1.size.times{|idx| return c1[idx] - c2[idx] if c1[idx] != c2[idx] }
|
227
|
+
0
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
10
232
|
|
11
233
|
# BIP 32 Hierarchical Deterministic Wallets
|
12
234
|
require "money-tree"
|
13
235
|
|
14
236
|
# establish the namespace
|
15
237
|
module CoinOp
|
238
|
+
@bitcoin_mutex = Mutex.new
|
239
|
+
|
240
|
+
def self.syncbit(network, &block)
|
241
|
+
@bitcoin_mutex.synchronize do
|
242
|
+
Bitcoin.network = network
|
243
|
+
block.call
|
244
|
+
end
|
245
|
+
end
|
16
246
|
module Bit
|
17
247
|
end
|
18
248
|
end
|
@@ -30,3 +260,4 @@ require_relative "bit/fee"
|
|
30
260
|
# Augmented functionality
|
31
261
|
require_relative "bit/multi_wallet"
|
32
262
|
|
263
|
+
|
data/lib/coin-op/bit/fee.rb
CHANGED
@@ -12,7 +12,7 @@ module CoinOp::Bit
|
|
12
12
|
# that deviate from the customary single signature.
|
13
13
|
#
|
14
14
|
# Returns the estimated fee in satoshis.
|
15
|
-
def estimate(unspents, payees, tx_size=nil)
|
15
|
+
def estimate(unspents, payees, tx_size=nil, network:)
|
16
16
|
# https://en.bitcoin.it/wiki/Transaction_fees
|
17
17
|
|
18
18
|
# dupe because we'll need to add a change output
|
@@ -21,7 +21,7 @@ module CoinOp::Bit
|
|
21
21
|
unspent_total = unspents.inject(0) {|sum, output| sum += output.value}
|
22
22
|
payee_total = payees.inject(0) {|sum, payee| sum += payee.value}
|
23
23
|
nominal_change = unspent_total - payee_total
|
24
|
-
payees << Output.new(:
|
24
|
+
payees << Output.new(value: nominal_change, network: network)
|
25
25
|
|
26
26
|
tx_size ||= estimate_tx_size(unspents.size, payees.size)
|
27
27
|
min = payees.min_by {|payee| payee.value }
|
@@ -37,17 +37,17 @@ module CoinOp::Bit
|
|
37
37
|
if small && big_outputs && high_priority
|
38
38
|
0
|
39
39
|
else
|
40
|
-
fee_for_bytes(tx_size)
|
40
|
+
fee_for_bytes(tx_size, network: network)
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
44
44
|
|
45
|
-
def fee_for_bytes(bytes)
|
45
|
+
def fee_for_bytes(bytes, network:)
|
46
46
|
# https://en.bitcoin.it/wiki/Transaction_fees
|
47
47
|
# > the reference implementation will round up the transaction size to the
|
48
48
|
# > next thousand bytes and add a fee of 0.1 mBTC (0.0001 BTC) per thousand bytes
|
49
49
|
size = (bytes / 1000) + 1
|
50
|
-
Bitcoin.network[:min_tx_fee] * size
|
50
|
+
CoinOp.syncbit(network) { Bitcoin.network[:min_tx_fee] * size }
|
51
51
|
end
|
52
52
|
|
53
53
|
# From http://bitcoinfees.com. This estimation is only valid for
|
data/lib/coin-op/bit/input.rb
CHANGED
@@ -19,13 +19,13 @@ module CoinOp::Bit
|
|
19
19
|
# * script_sig_asm - the string form of the scriptSig for this input
|
20
20
|
#
|
21
21
|
def initialize(options={})
|
22
|
-
@transaction, @index, @output =
|
23
|
-
options.values_at :transaction, :index, :output
|
22
|
+
@transaction, @index, @output, @network =
|
23
|
+
options.values_at :transaction, :index, :output, :network
|
24
24
|
|
25
25
|
script_sig_asm = options[:script_sig_asm]
|
26
26
|
|
27
27
|
unless @output.is_a? Output
|
28
|
-
@output = Output.new(@output)
|
28
|
+
@output = Output.new(@output, network: @network)
|
29
29
|
end
|
30
30
|
|
31
31
|
@native = Bitcoin::Protocol::TxIn.new
|
@@ -1,27 +1,15 @@
|
|
1
|
-
require "money-tree"
|
2
|
-
require "bitcoin"
|
3
|
-
|
4
1
|
module CoinOp::Bit
|
5
2
|
|
6
3
|
class MultiWallet
|
7
4
|
include CoinOp::Encodings
|
8
5
|
|
9
|
-
|
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
|
6
|
+
def self.generate(names)
|
19
7
|
masters = {}
|
20
8
|
names.each do |name|
|
21
9
|
name = name.to_sym
|
22
|
-
masters[name] = MoneyTree::Master.new
|
10
|
+
masters[name] = MoneyTree::Master.new
|
23
11
|
end
|
24
|
-
self.new(:
|
12
|
+
self.new(private: masters)
|
25
13
|
end
|
26
14
|
|
27
15
|
attr_reader :trees
|
@@ -30,7 +18,6 @@ module CoinOp::Bit
|
|
30
18
|
@private_trees = {}
|
31
19
|
@public_trees = {}
|
32
20
|
@trees = {}
|
33
|
-
@network = NetworkMap[options.include? :network ? options[:network] : :testnet3]
|
34
21
|
|
35
22
|
# FIXME: we should allow this.
|
36
23
|
# if !private_trees
|
@@ -57,7 +44,7 @@ module CoinOp::Bit
|
|
57
44
|
when MoneyTree::Node
|
58
45
|
arg
|
59
46
|
when String
|
60
|
-
MoneyTree::Node.
|
47
|
+
MoneyTree::Node.from_bip32(arg)
|
61
48
|
else
|
62
49
|
raise "Unusable type: #{node.class}"
|
63
50
|
end
|
@@ -83,14 +70,14 @@ module CoinOp::Bit
|
|
83
70
|
names.each do |name|
|
84
71
|
name = name.to_sym
|
85
72
|
tree = @private_trees.delete(name)
|
86
|
-
|
87
|
-
@public_trees[name] = MoneyTree::Master.
|
73
|
+
serialized_priv = tree.to_bip32
|
74
|
+
@public_trees[name] = MoneyTree::Master.from_bip32(serialized_priv)
|
88
75
|
end
|
89
76
|
end
|
90
77
|
|
91
78
|
def import(addresses)
|
92
79
|
addresses.each do |name, address|
|
93
|
-
node = MoneyTree::Master.
|
80
|
+
node = MoneyTree::Master.from_bip32(address)
|
94
81
|
if node.private_key
|
95
82
|
@private_trees[name] = node
|
96
83
|
else
|
@@ -99,35 +86,34 @@ module CoinOp::Bit
|
|
99
86
|
end
|
100
87
|
end
|
101
88
|
|
102
|
-
def private_seed(name)
|
89
|
+
def private_seed(name, network:)
|
103
90
|
raise "No such node: '#{name}'" unless (node = @private_trees[name.to_sym])
|
104
|
-
node.
|
91
|
+
node.to_bip32(:private, network: network)
|
105
92
|
end
|
106
93
|
|
107
94
|
alias_method :private_address, :private_seed
|
108
95
|
|
109
|
-
def public_seed(name)
|
96
|
+
def public_seed(name, network:)
|
110
97
|
name = name.to_sym
|
111
98
|
if node = (@public_trees[name] || @private_trees[name])
|
112
|
-
node.
|
99
|
+
node.to_bip32(network: network)
|
113
100
|
else
|
114
101
|
raise "No such node: '#{name}'"
|
115
102
|
end
|
116
103
|
end
|
117
104
|
|
118
|
-
|
119
|
-
def private_seeds
|
105
|
+
def private_seeds(network:)
|
120
106
|
out = {}
|
121
107
|
@private_trees.each do |name, tree|
|
122
|
-
out[name] = self.private_address(name)
|
108
|
+
out[name] = self.private_address(name, network: network)
|
123
109
|
end
|
124
110
|
out
|
125
111
|
end
|
126
112
|
|
127
|
-
def public_seeds
|
113
|
+
def public_seeds(network:)
|
128
114
|
out = {}
|
129
115
|
@private_trees.each do |name, node|
|
130
|
-
out[name] = node.
|
116
|
+
out[name] = node.to_bip32(network: network)
|
131
117
|
end
|
132
118
|
out
|
133
119
|
end
|
@@ -141,7 +127,6 @@ module CoinOp::Bit
|
|
141
127
|
:path => path,
|
142
128
|
:private => {},
|
143
129
|
:public => {},
|
144
|
-
:network => @network
|
145
130
|
}
|
146
131
|
@private_trees.each do |name, node|
|
147
132
|
options[:private][name] = node.node_for_path(path)
|
@@ -211,7 +196,10 @@ module CoinOp::Bit
|
|
211
196
|
combined = {}
|
212
197
|
sig_dicts.each do |sig_dict|
|
213
198
|
sig_dict.each do |tree, signature|
|
214
|
-
|
199
|
+
decoded_sig = decode_base58(signature)
|
200
|
+
low_s_der_sig = Bitcoin::Script.is_low_der_signature?(decoded_sig) ?
|
201
|
+
decoded_sig : Bitcoin::OpenSSL_EC.signature_to_low_s(decoded_sig)
|
202
|
+
combined[tree] = Bitcoin::OpenSSL_EC.repack_der_signature(low_s_der_sig)
|
215
203
|
end
|
216
204
|
end
|
217
205
|
|
@@ -226,6 +214,13 @@ module CoinOp::Bit
|
|
226
214
|
class MultiNode
|
227
215
|
include CoinOp::Encodings
|
228
216
|
|
217
|
+
CODE_TO_NETWORK = {
|
218
|
+
0 => :bitcoin,
|
219
|
+
1 => :testnet3,
|
220
|
+
2 => :litecoin,
|
221
|
+
3 => :dogecoin
|
222
|
+
}
|
223
|
+
|
229
224
|
attr_reader :path, :private, :public, :keys, :public_keys
|
230
225
|
def initialize(options)
|
231
226
|
@path = options[:path]
|
@@ -234,7 +229,6 @@ module CoinOp::Bit
|
|
234
229
|
@public_keys = {}
|
235
230
|
@private = options[:private]
|
236
231
|
@public = options[:public]
|
237
|
-
@network = options[:network]
|
238
232
|
|
239
233
|
@private.each do |name, node|
|
240
234
|
key = Bitcoin::Key.new(node.private_key.to_hex, node.public_key.to_hex)
|
@@ -246,10 +240,14 @@ module CoinOp::Bit
|
|
246
240
|
end
|
247
241
|
end
|
248
242
|
|
243
|
+
def network
|
244
|
+
CODE_TO_NETWORK.fetch(@path.split('/')[2].to_i)
|
245
|
+
end
|
246
|
+
|
249
247
|
def script(m=2)
|
250
248
|
# m of n
|
251
249
|
keys = @public_keys.sort_by {|name, key| name }.map {|name, key| key.pub }
|
252
|
-
Script.new(:
|
250
|
+
Script.new(public_keys: keys, needed: m, network: network)
|
253
251
|
end
|
254
252
|
|
255
253
|
def address
|
@@ -259,21 +257,14 @@ module CoinOp::Bit
|
|
259
257
|
alias_method :p2sh_address, :address
|
260
258
|
|
261
259
|
def p2sh_script
|
262
|
-
Script.new(:address => self.script.p2sh_address, :
|
263
|
-
end
|
264
|
-
|
265
|
-
def sign(name, value)
|
266
|
-
raise "No such key: '#{name}'" unless (key = @keys[name.to_sym])
|
267
|
-
# \x01 means the hash type is SIGHASH_ALL
|
268
|
-
# https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29
|
269
|
-
key.sign(value) + "\x01"
|
260
|
+
Script.new(:address => self.script.p2sh_address, network: network)
|
270
261
|
end
|
271
262
|
|
272
263
|
def signatures(value, names:)
|
273
264
|
out = {}
|
274
265
|
@keys.each do |name, key|
|
275
266
|
next unless names.include?(name)
|
276
|
-
out[name] = base58(
|
267
|
+
out[name] = base58(key.sign(value))
|
277
268
|
end
|
278
269
|
out
|
279
270
|
end
|
data/lib/coin-op/bit/output.rb
CHANGED
@@ -20,7 +20,9 @@ module CoinOp::Bit
|
|
20
20
|
# or :address (a valid Bitcoin address)
|
21
21
|
# * :metadata (a Hash with arbitrary contents)
|
22
22
|
#
|
23
|
-
def initialize(options)
|
23
|
+
def initialize(options, network: nil)
|
24
|
+
network_name = options[:network] || network
|
25
|
+
raise(ArgumentError, 'Network cannot be nil!') unless network_name
|
24
26
|
if options[:transaction]
|
25
27
|
@transaction = options[:transaction]
|
26
28
|
elsif options[:transaction_hash]
|
@@ -36,9 +38,9 @@ module CoinOp::Bit
|
|
36
38
|
@metadata[:confirmations] ||= confirmations
|
37
39
|
|
38
40
|
if options[:script]
|
39
|
-
@script = Script.new(options[:script])
|
41
|
+
@script = Script.new(options[:script], network: network_name)
|
40
42
|
elsif @address
|
41
|
-
@script = Script.new(:address
|
43
|
+
@script = Script.new(address: @address, network: network_name)
|
42
44
|
end
|
43
45
|
|
44
46
|
|
@@ -95,3 +97,4 @@ module CoinOp::Bit
|
|
95
97
|
end
|
96
98
|
|
97
99
|
end
|
100
|
+
|
data/lib/coin-op/bit/script.rb
CHANGED
@@ -27,46 +27,42 @@ module CoinOp::Bit
|
|
27
27
|
# The name of the crypto-currency network may also be specified. It
|
28
28
|
# defaults to :testnet3. Names supplied in this manner must correspond
|
29
29
|
# to the names in the ::Bitcoin::NETWORKS Hash.
|
30
|
-
|
31
|
-
|
32
|
-
network_name = (options[:network] || :testnet3) rescue :testnet3
|
30
|
+
# TODO: PLEASE refactor this. should not accept either string or hash
|
31
|
+
def initialize(options, network: nil)
|
32
|
+
network_name = network || (options[:network] || :testnet3) rescue :testnet3
|
33
33
|
@network = Bitcoin::NETWORKS[network_name]
|
34
|
-
Bitcoin.network = network_name
|
35
34
|
|
36
35
|
# literals
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
if address = options[:address]
|
48
|
-
unless Bitcoin::valid_address?(address)
|
49
|
-
raise ArgumentError, "Invalid address: #{address}"
|
50
|
-
end
|
51
|
-
@blob = Bitcoin::Script.to_address_script(address)
|
52
|
-
elsif public_key = options[:public_key]
|
53
|
-
@blob = Bitcoin::Script.to_pubkey_script(public_key)
|
54
|
-
elsif (keys = options[:public_keys]) && (needed = options[:needed])
|
55
|
-
@blob = Bitcoin::Script.to_multisig_script(needed, *keys)
|
56
|
-
elsif signatures = options[:signatures]
|
57
|
-
@blob = Bitcoin::Script.to_multisig_script_sig(*signatures)
|
36
|
+
CoinOp.syncbit(network_name) do
|
37
|
+
if options.is_a? String
|
38
|
+
@blob = Bitcoin::Script.binary_from_string options
|
39
|
+
elsif string = options[:string]
|
40
|
+
@blob = Bitcoin::Script.binary_from_string string
|
41
|
+
elsif options[:blob]
|
42
|
+
@blob = options[:blob]
|
43
|
+
elsif options[:hex]
|
44
|
+
@blob = decode_hex(options[:hex])
|
45
|
+
# arguments for constructing
|
58
46
|
else
|
59
|
-
|
47
|
+
if address = options[:address]
|
48
|
+
unless Bitcoin::valid_address?(address)
|
49
|
+
raise ArgumentError, "Invalid address: #{address}"
|
50
|
+
end
|
51
|
+
@blob = Bitcoin::Script.to_address_script(address)
|
52
|
+
elsif public_key = options[:public_key]
|
53
|
+
@blob = Bitcoin::Script.to_pubkey_script(public_key)
|
54
|
+
elsif (keys = options[:public_keys]) && (needed = options[:needed])
|
55
|
+
@blob = Bitcoin::Script.to_multisig_script(needed, *keys)
|
56
|
+
elsif signatures = options[:signatures]
|
57
|
+
@blob = Bitcoin::Script.to_multisig_script_sig(*signatures)
|
58
|
+
else
|
59
|
+
raise ArgumentError
|
60
|
+
end
|
60
61
|
end
|
62
|
+
@native = Bitcoin::Script.new @blob
|
63
|
+
@hex = hex(@blob)
|
64
|
+
@string = @native.to_string
|
61
65
|
end
|
62
|
-
|
63
|
-
@hex = hex(@blob)
|
64
|
-
@native = Bitcoin::Script.new @blob
|
65
|
-
@string = @native.to_string
|
66
|
-
end
|
67
|
-
|
68
|
-
def address
|
69
|
-
@native.get_p2sh_address
|
70
66
|
end
|
71
67
|
|
72
68
|
def to_s
|
@@ -109,7 +105,9 @@ module CoinOp::Bit
|
|
109
105
|
end
|
110
106
|
|
111
107
|
def hash160
|
112
|
-
|
108
|
+
CoinOp.syncbit(@network[:name]) do
|
109
|
+
@native.get_hash160 || Bitcoin.hash160(@hex)
|
110
|
+
end
|
113
111
|
end
|
114
112
|
|
115
113
|
# Generate the script that uses a P2SH address.
|
@@ -117,19 +115,25 @@ module CoinOp::Bit
|
|
117
115
|
# can probably be removed, as I think it is equivalent to
|
118
116
|
# Script.new :address => some_p2sh_address
|
119
117
|
def p2sh_script
|
120
|
-
|
118
|
+
CoinOp.syncbit(@network[:name]) do |b|
|
119
|
+
self.class.new Bitcoin::Script.to_p2sh_script(self.hash160)
|
120
|
+
end
|
121
121
|
end
|
122
122
|
|
123
123
|
def p2sh_address
|
124
|
-
Bitcoin.
|
124
|
+
Bitcoin.encode_address(self.hash160, Bitcoin::NETWORKS[@network[:name]][:p2sh_version])
|
125
125
|
end
|
126
126
|
|
127
|
+
alias_method :address, :p2sh_address
|
128
|
+
|
127
129
|
# Generate a P2SH script_sig for the current script, using the
|
128
130
|
# supplied options, which will, in the case of a multisig input,
|
129
131
|
# be {:signatures => array_of_signatures}.
|
130
132
|
def p2sh_sig(options)
|
131
133
|
string = Script.new(options).to_s
|
132
|
-
|
134
|
+
CoinOp.syncbit(@network[:name]) do
|
135
|
+
Bitcoin::Script.binary_from_string("#{string} #{self.to_hex}")
|
136
|
+
end
|
133
137
|
end
|
134
138
|
|
135
139
|
end
|
@@ -12,26 +12,27 @@ module CoinOp::Bit
|
|
12
12
|
|
13
13
|
# Construct a Transaction from a data structure of nested Hashes
|
14
14
|
# and Arrays.
|
15
|
-
def self.data(data)
|
15
|
+
def self.data(data, network:)
|
16
16
|
version, lock_time, fee, inputs, outputs, confirmations =
|
17
17
|
data.values_at :version, :lock_time, :fee, :inputs, :outputs, :confirmations
|
18
18
|
|
19
19
|
transaction = self.new(
|
20
20
|
:fee => fee,
|
21
21
|
:version => version, :lock_time => lock_time,
|
22
|
-
:confirmations => confirmations
|
22
|
+
:confirmations => confirmations,
|
23
|
+
network: network
|
23
24
|
)
|
24
25
|
|
25
|
-
outputs.each do |
|
26
|
-
transaction.add_output
|
26
|
+
outputs.each do |output_hash|
|
27
|
+
transaction.add_output(Output.new(output_hash, network: network))
|
27
28
|
end
|
28
29
|
|
29
30
|
#FIXME: we're not handling sig_scripts for already signed inputs.
|
30
31
|
|
31
32
|
if inputs
|
32
33
|
# TODO: use #each instead of #each_with_index
|
33
|
-
inputs.each_with_index do |
|
34
|
-
transaction.add_input(
|
34
|
+
inputs.each_with_index do |input_hash, index|
|
35
|
+
transaction.add_input(input_hash, network: network)
|
35
36
|
|
36
37
|
## FIXME: verify that the supplied and computed sig_hashes match
|
37
38
|
#puts :sig_hashes_match => (data[:sig_hash] == input.sig_hash)
|
@@ -101,6 +102,7 @@ module CoinOp::Bit
|
|
101
102
|
@native = Bitcoin::Protocol::Tx.new
|
102
103
|
@inputs = []
|
103
104
|
@outputs = []
|
105
|
+
@network = options[:network]
|
104
106
|
@fee_override = options[:fee]
|
105
107
|
@confirmations = options[:confirmations]
|
106
108
|
end
|
@@ -141,7 +143,6 @@ module CoinOp::Bit
|
|
141
143
|
valid = true
|
142
144
|
@inputs.each_with_index do |input, index|
|
143
145
|
# TODO: confirm whether we need to mess with the block_timestamp arg
|
144
|
-
|
145
146
|
unless self.native.verify_input_signature(index, input.output.transaction.native)
|
146
147
|
valid = false
|
147
148
|
bad_inputs << index
|
@@ -157,14 +158,15 @@ module CoinOp::Bit
|
|
157
158
|
# * an instance of Output
|
158
159
|
# * a Hash describing an Output
|
159
160
|
#
|
160
|
-
def add_input(input)
|
161
|
+
def add_input(input, network:)
|
161
162
|
# TODO: allow specifying prev_tx and index with a Hash.
|
162
163
|
# Possibly stop using SparseInput.
|
163
164
|
|
164
165
|
unless input.is_a?(Input)
|
165
166
|
input = Input.new input.merge(
|
166
|
-
:
|
167
|
-
:
|
167
|
+
transaction: self,
|
168
|
+
index: @inputs.size,
|
169
|
+
network: network
|
168
170
|
)
|
169
171
|
end
|
170
172
|
|
@@ -178,7 +180,7 @@ module CoinOp::Bit
|
|
178
180
|
# Takes either an Output or a Hash describing an output.
|
179
181
|
def add_output(output)
|
180
182
|
unless output.is_a? Output
|
181
|
-
output = Output.new(output)
|
183
|
+
output = Output.new(output, network: @network)
|
182
184
|
end
|
183
185
|
|
184
186
|
index = @outputs.size
|
@@ -222,12 +224,13 @@ module CoinOp::Bit
|
|
222
224
|
# Typically used only by #to_json.
|
223
225
|
def to_hash
|
224
226
|
{
|
227
|
+
:confirmations => self.confirmations.nil? ? 0 : self.confirmations,
|
225
228
|
:version => self.version,
|
226
229
|
:lock_time => self.lock_time,
|
227
230
|
:hash => self.hex_hash,
|
228
231
|
:fee => self.fee,
|
229
232
|
:inputs => self.inputs,
|
230
|
-
:outputs => self.outputs
|
233
|
+
:outputs => self.outputs
|
231
234
|
}
|
232
235
|
end
|
233
236
|
|
@@ -235,10 +238,14 @@ module CoinOp::Bit
|
|
235
238
|
self.to_hash.to_json(*a)
|
236
239
|
end
|
237
240
|
|
238
|
-
# Compute the digest for a given input.
|
239
|
-
#
|
240
|
-
#
|
241
|
-
#
|
241
|
+
# Compute the digest for a given input. This is the value that is actually
|
242
|
+
# signed in a transaction.
|
243
|
+
# e.g. I want to spend UTXO0 - I provide the UTXO0 as an input.
|
244
|
+
# I provide as a script the script to which UTXO0 was paid.
|
245
|
+
# If UTX0 was paid to a P2SH address, the script here needs to be the correct
|
246
|
+
# m-of-n structure, which may well not be part of the input.output object
|
247
|
+
# - This works here because we default to 2-of-3 and have consistent ordering
|
248
|
+
# When we support multiple m-of-n's, this may become a prollem.
|
242
249
|
def sig_hash(input, script=nil)
|
243
250
|
# We only allow SIGHASH_ALL at this time
|
244
251
|
# https://en.bitcoin.it/wiki/OP_CHECKSIG#Hashtype_SIGHASH_ALL_.28default.29
|
@@ -286,7 +293,7 @@ module CoinOp::Bit
|
|
286
293
|
end
|
287
294
|
|
288
295
|
def fee_override
|
289
|
-
@fee_override || self.estimate_fee
|
296
|
+
@fee_override || self.estimate_fee(network: @network)
|
290
297
|
end
|
291
298
|
|
292
299
|
# Estimate the fee in satoshis for this transaction. Takes an optional
|
@@ -363,3 +370,4 @@ module CoinOp::Bit
|
|
363
370
|
end
|
364
371
|
|
365
372
|
end
|
373
|
+
|
data/lib/coin-op/crypto.rb
CHANGED
data/lib/coin-op/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module CoinOp
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.4.0"
|
3
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coin-op
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew King
|
@@ -32,7 +32,7 @@ cert_chain:
|
|
32
32
|
tdc4VS7IlSRxlZ3dBOgiigy9GXpJ+7F831AqjxL39EPwdr7RguTNz+pi//RKaT/U
|
33
33
|
IlpVB+Xfk0vQdP7iYfjGxDzUf0FACMjsR95waJmadKW1Iy6STw2hwPhYIQz1Hu1A
|
34
34
|
-----END CERTIFICATE-----
|
35
|
-
date: 2015-05-
|
35
|
+
date: 2015-05-19 00:00:00.000000000 Z
|
36
36
|
dependencies:
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: bitcoin-ruby
|
metadata.gz.sig
CHANGED
Binary file
|