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