block_io 2.0.0 → 3.0.3
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/{.appveyor.yml → .appveyor.yml-disabled} +2 -2
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/LICENSE +1 -1
- data/README.md +18 -14
- data/block_io.gemspec +7 -7
- data/examples/basic.rb +29 -5
- data/examples/dtrust.rb +43 -24
- data/examples/sweeper.rb +21 -16
- data/lib/block_io/api_exception.rb +11 -0
- data/lib/block_io/chainparams/BTC.yml +8 -0
- data/lib/block_io/chainparams/BTCTEST.yml +8 -0
- data/lib/block_io/chainparams/DOGE.yml +8 -0
- data/lib/block_io/chainparams/DOGETEST.yml +8 -0
- data/lib/block_io/chainparams/LTC.yml +8 -0
- data/lib/block_io/chainparams/LTCTEST.yml +8 -0
- data/lib/block_io/client.rb +145 -80
- data/lib/block_io/extended_bitcoinrb.rb +132 -0
- data/lib/block_io/helper.rb +211 -53
- data/lib/block_io/key.rb +11 -124
- data/lib/block_io/version.rb +1 -1
- data/lib/block_io.rb +3 -2
- data/spec/client_misc_spec.rb +76 -0
- data/spec/client_spec.rb +23 -178
- data/spec/dtrust_spec.rb +167 -0
- data/spec/helper_spec.rb +117 -7
- data/spec/key_spec.rb +50 -19
- data/spec/larger_transaction_spec.rb +371 -0
- data/spec/sweep_spec.rb +115 -0
- data/spec/test-cases/.gitignore +2 -0
- data/spec/test-cases/LICENSE +21 -0
- data/spec/test-cases/README.md +2 -0
- data/spec/test-cases/json/create_and_sign_transaction_response.json +61 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1261 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1266 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1271 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +3816 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json +2931 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json +3771 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json +3786 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json +3801 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json +36 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json +591 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json +576 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json +531 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json +5 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +21 -0
- data/spec/test-cases/json/create_and_sign_transaction_response_witness_v1_output.json +11 -0
- data/spec/test-cases/json/get_balance_response.json +8 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json +1397 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json +1795 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json +1802 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json +1809 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json +45 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json +52 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json +1805 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json +1804 -0
- data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json +1789 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2pkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json +35 -0
- data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json +35 -0
- data/spec/test-cases/json/prepare_transaction_response.json +164 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1796 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1803 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1810 -0
- data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +5367 -0
- data/spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +76 -0
- data/spec/test-cases/json/prepare_transaction_response_witness_v1_output.json +64 -0
- data/spec/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
- metadata +203 -57
- data/examples/max_withdrawal.rb +0 -29
- data/lib/block_io/constants.rb +0 -10
- data/spec/data/sign_and_finalize_dtrust_withdrawal_request.json +0 -1
- data/spec/data/sign_and_finalize_sweep_request.json +0 -1
- data/spec/data/sign_and_finalize_withdrawal_request.json +0 -4
- data/spec/data/sweep_from_address_response.json +0 -1
- data/spec/data/withdraw_from_dtrust_address_response.json +0 -1
- data/spec/data/withdraw_response.json +0 -1227
- data/spec/rfc6979_spec.rb +0 -59
- data/spec/withdraw_spec.rb +0 -90
data/lib/block_io/client.rb
CHANGED
|
@@ -16,20 +16,12 @@ module BlockIo
|
|
|
16
16
|
raise "Must provide an API Key." unless args.key?(:api_key) and args[:api_key].to_s.size > 0
|
|
17
17
|
|
|
18
18
|
@api_key = args[:api_key]
|
|
19
|
-
@
|
|
19
|
+
@pin = args[:pin]
|
|
20
20
|
@version = args[:version] || 2
|
|
21
21
|
@hostname = args[:hostname] || "block.io"
|
|
22
22
|
@proxy = args[:proxy] || {}
|
|
23
|
-
@keys =
|
|
24
|
-
@use_low_r = args[:use_low_r]
|
|
25
|
-
@raise_exception_on_error = args[:raise_exception_on_error] || false
|
|
23
|
+
@keys = {}
|
|
26
24
|
|
|
27
|
-
raise Exception.new("Keys must be provided as an array.") unless @keys.is_a?(Array)
|
|
28
|
-
raise Exception.new("Keys must be BlockIo::Key objects.") unless @keys.all?{|key| key.is_a?(BlockIo::Key)}
|
|
29
|
-
|
|
30
|
-
# make a hash of the keys we've been given
|
|
31
|
-
@keys = @keys.inject({}){|h,v| h[v.public_key] = v; h}
|
|
32
|
-
|
|
33
25
|
raise Exception.new("Must specify hostname, port, username, password if using a proxy.") if @proxy.keys.size > 0 and [:hostname, :port, :username, :password].any?{|x| !@proxy.key?(x)}
|
|
34
26
|
|
|
35
27
|
@conn = ConnectionPool.new(:size => args[:pool_size] || 5) { http = HTTP.headers(:accept => "application/json", :user_agent => "gem:block_io:#{VERSION}");
|
|
@@ -50,109 +42,176 @@ module BlockIo
|
|
|
50
42
|
raise Exception.new("Parameter keys must be symbols. For instance: :label => 'default' instead of 'label' => 'default'") unless args[0].nil? or args[0].keys.all?{|x| x.is_a?(Symbol)}
|
|
51
43
|
raise Exception.new("Cannot pass PINs to any calls. PINs can only be set when initiating this library.") if !args[0].nil? and args[0].key?(:pin)
|
|
52
44
|
raise Exception.new("Do not specify API Keys here. Initiate a new BlockIo object instead if you need to use another API Key.") if !args[0].nil? and args[0].key?(:api_key)
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
elsif BlockIo::SWEEP_METHODS.key?(method_name) then
|
|
45
|
+
|
|
46
|
+
if method_name.eql?("prepare_sweep_transaction") then
|
|
47
|
+
# we need to ensure @network is set before we allow this
|
|
48
|
+
# we need to send only the public key, not the given private key
|
|
58
49
|
# we're sweeping from an address
|
|
59
|
-
|
|
60
|
-
elsif BlockIo::FINALIZE_SIGNATURE_METHODS.key?(method_name) then
|
|
61
|
-
# we're finalize the transaction signatures
|
|
62
|
-
finalize_signature(args[0], method_name)
|
|
50
|
+
internal_prepare_sweep_transaction(args[0], method_name)
|
|
63
51
|
else
|
|
64
52
|
api_call({:method_name => method_name, :params => args[0] || {}})
|
|
65
53
|
end
|
|
66
54
|
|
|
67
55
|
end
|
|
68
56
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
def summarize_prepared_transaction(data)
|
|
58
|
+
# takes the response from prepare_transaction/prepare_dtrust_transaction/prepare_sweep_transaction
|
|
59
|
+
# returns the network fee being paid, the blockio fee being paid, amounts being sent
|
|
72
60
|
|
|
73
|
-
|
|
61
|
+
input_sum = data['data']['inputs'].map{|input| BigDecimal(input['input_value'])}.inject(:+)
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
output_values = [BigDecimal(0)]
|
|
64
|
+
blockio_fees = [BigDecimal(0)]
|
|
65
|
+
change_amounts = [BigDecimal(0)]
|
|
77
66
|
|
|
78
|
-
|
|
67
|
+
data['data']['outputs'].each do |output|
|
|
68
|
+
if output['output_category'] == 'blockio-fee' then
|
|
69
|
+
blockio_fees << BigDecimal(output['output_value'])
|
|
70
|
+
elsif output['output_category'] == 'change' then
|
|
71
|
+
change_amounts << BigDecimal(output['output_value'])
|
|
72
|
+
else
|
|
73
|
+
# user-specified
|
|
74
|
+
output_values << BigDecimal(output['output_value'])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
output_sum = output_values.inject(:+)
|
|
79
|
+
blockio_fee = blockio_fees.inject(:+)
|
|
80
|
+
change_amount = change_amounts.inject(:+)
|
|
81
|
+
|
|
82
|
+
network_fee = input_sum - output_sum - blockio_fee - change_amount
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
'network' => data['data']['network'],
|
|
86
|
+
'network_fee' => '%0.8f' % network_fee,
|
|
87
|
+
"blockio_fee" => '%0.8f' % blockio_fee,
|
|
88
|
+
"total_amount_to_send" => '%0.8f' % output_sum
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def create_and_sign_transaction(data, keys = [])
|
|
94
|
+
# takes data from prepare_transaction, prepare_dtrust_transaction, prepare_sweep_transaction
|
|
95
|
+
# creates the transaction given the inputs and outputs from data
|
|
96
|
+
# signs the transaction using keys (if not provided, decrypts the key using the PIN)
|
|
97
|
+
|
|
98
|
+
set_network(data['data']['network']) if data['data'].key?('network')
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
100
|
+
raise "Data must be contain one or more inputs" unless data['data']['inputs'].size > 0
|
|
101
|
+
raise "Data must contain one or more outputs" unless data['data']['outputs'].size > 0
|
|
102
|
+
raise "Data must contain information about addresses" unless data['data']['input_address_data'].size > 0 # TODO make stricter
|
|
82
103
|
|
|
83
|
-
|
|
104
|
+
private_keys = keys.map{|x| Key.from_private_key_hex(x)}
|
|
84
105
|
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
# TODO debug all of this
|
|
107
|
+
|
|
108
|
+
inputs = data['data']['inputs']
|
|
109
|
+
outputs = data['data']['outputs']
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
@keys[key.public_key] = key
|
|
111
|
+
tx = Bitcoin::Tx.new
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
# populate the inputs
|
|
114
|
+
inputs.each do |input|
|
|
115
|
+
tx.in << Bitcoin::TxIn.new(:out_point => Bitcoin::OutPoint.from_txid(input['previous_txid'], input['previous_output_index']))
|
|
116
|
+
end
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# we just need reference_id and inputs
|
|
98
|
-
response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
|
|
99
|
-
|
|
100
|
-
# let's sign all the inputs we can
|
|
101
|
-
signatures_added = (@keys.size == 0 ? false : Helper.signData(response["data"]["inputs"], @keys))
|
|
102
|
-
|
|
103
|
-
# the response object is now signed, let's stringify it and finalize this withdrawal
|
|
104
|
-
response = finalize_signature({:signature_data => response["data"]}, "sign_and_finalize_withdrawal") if signatures_added
|
|
105
|
-
|
|
106
|
-
# if we provided all the required signatures, this transaction went through
|
|
107
|
-
# otherwise Block.io responded with data asking for more signatures and recorded the signature we provided above
|
|
108
|
-
# the latter will be the case for dTrust addresses
|
|
109
|
-
end
|
|
110
|
-
|
|
118
|
+
# populate the outputs
|
|
119
|
+
outputs.each do |output|
|
|
120
|
+
tx.out << Bitcoin::TxOut.new(:value => (BigDecimal(output['output_value']) * BigDecimal(100000000)).to_i, :script_pubkey => Bitcoin::Script.parse_from_addr(output['receiving_address']))
|
|
111
121
|
end
|
|
112
122
|
|
|
113
|
-
response
|
|
114
123
|
|
|
115
|
-
|
|
124
|
+
# some protection against misbehaving machines and/or code
|
|
125
|
+
raise Exception.new("Expected unsigned transaction ID mismatch. Please report this error to support@block.io.") unless (data['data']['expected_unsigned_txid'].nil? or
|
|
126
|
+
data['data']['expected_unsigned_txid'] == tx.txid)
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
# extract key
|
|
129
|
+
encrypted_key = data['data']['user_key']
|
|
119
130
|
|
|
120
|
-
|
|
131
|
+
if !encrypted_key.nil? and !@keys.key?(encrypted_key['public_key']) then
|
|
132
|
+
# decrypt the key with PIN
|
|
121
133
|
|
|
122
|
-
|
|
123
|
-
sanitized_args = args.merge({:public_key => key.public_key})
|
|
124
|
-
sanitized_args.delete(:private_key)
|
|
125
|
-
|
|
126
|
-
response = api_call({:method_name => method_name, :params => sanitized_args})
|
|
127
|
-
|
|
128
|
-
if response["data"].key?("reference_id") then
|
|
129
|
-
# Block.io's asking us to provide client-side signatures
|
|
134
|
+
raise Exception.new("PIN not set and no keys provided. Cannot sign transaction.") unless !@pin.nil? or @keys.size > 0
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
response["data"] = {"reference_id" => response["data"]["reference_id"], "inputs" => response["data"]["inputs"]}
|
|
133
|
-
|
|
134
|
-
# let's sign all the inputs we can
|
|
135
|
-
signatures_added = Helper.signData(response["data"]["inputs"], [key])
|
|
136
|
+
key = Helper.dynamicExtractKey(encrypted_key, @pin)
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
response = finalize_signature({:signature_data => response["data"]}, "sign_and_finalize_sweep") if signatures_added
|
|
138
|
+
raise Exception.new("Public key mismatch for requested signer and ourselves. Invalid Secret PIN detected.") unless key.public_key_hex.eql?(encrypted_key["public_key"])
|
|
139
139
|
|
|
140
|
-
#
|
|
140
|
+
# store this key for later use
|
|
141
|
+
@keys[key.public_key_hex] = key
|
|
142
|
+
|
|
141
143
|
end
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
# store the provided keys, if any, for later use
|
|
146
|
+
private_keys.each{|key| @keys[key.public_key_hex] = key}
|
|
147
|
+
|
|
148
|
+
signatures = []
|
|
149
|
+
|
|
150
|
+
if @keys.size > 0 then
|
|
151
|
+
# try to sign whatever we can here and give the user the data back
|
|
152
|
+
# Block.io will check to see if all signatures are present, or return an error otherwise saying insufficient signatures provided
|
|
153
|
+
|
|
154
|
+
i = 0
|
|
155
|
+
while i < inputs.size do
|
|
156
|
+
input = inputs[i]
|
|
157
|
+
|
|
158
|
+
input_address_data = data['data']['input_address_data'].detect{|d| d['address'] == input['spending_address']}
|
|
159
|
+
sighash_for_input = Helper.getSigHashForInput(tx, i, input, input_address_data) # in bytes
|
|
160
|
+
|
|
161
|
+
input_address_data['public_keys'].each do |signer_public_key|
|
|
162
|
+
# sign what we can and append signatures to the signatures object
|
|
163
|
+
|
|
164
|
+
next unless @keys.key?(signer_public_key)
|
|
165
|
+
|
|
166
|
+
signature = @keys[signer_public_key].sign(sighash_for_input).unpack("H*")[0] # in hex
|
|
167
|
+
signatures << {"input_index" => i, "public_key" => signer_public_key, "signature" => signature}
|
|
168
|
+
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
i += 1 # go to next input
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# if we have everything we need for this transaction, just finalize the transaction
|
|
177
|
+
if Helper.allSignaturesPresent?(tx, inputs, signatures, data['data']['input_address_data']) then
|
|
178
|
+
Helper.finalizeTransaction(tx, inputs, signatures, data['data']['input_address_data'])
|
|
179
|
+
signatures = [] # no signatures left to append
|
|
180
|
+
end
|
|
144
181
|
|
|
182
|
+
# reset keys
|
|
183
|
+
@keys = {}
|
|
184
|
+
|
|
185
|
+
# the response for submitting the transaction
|
|
186
|
+
{"tx_type" => data['data']['tx_type'], "tx_hex" => tx.to_hex, "signatures" => (signatures.size == 0 ? nil : signatures)}
|
|
187
|
+
|
|
145
188
|
end
|
|
146
189
|
|
|
147
|
-
|
|
190
|
+
private
|
|
191
|
+
|
|
192
|
+
def internal_prepare_sweep_transaction(args = {}, method_name = "prepare_sweep_transaction")
|
|
148
193
|
|
|
149
|
-
|
|
194
|
+
# set the network first if not already known
|
|
195
|
+
api_call({:method_name => "get_balance", :params => {}}) if @network.nil?
|
|
150
196
|
|
|
151
|
-
|
|
197
|
+
raise Exception.new("No private_key provided.") unless args.key?(:private_key) and (args[:private_key] || "").size > 0
|
|
152
198
|
|
|
153
|
-
|
|
199
|
+
# ensure the private key never goes to Block.io
|
|
200
|
+
key = Key.from_wif(args[:private_key])
|
|
201
|
+
sanitized_args = args.merge({:public_key => key.public_key_hex})
|
|
202
|
+
sanitized_args.delete(:private_key)
|
|
203
|
+
|
|
204
|
+
@keys[key.public_key_hex] = key # store this in our set of keys for later use
|
|
205
|
+
|
|
206
|
+
api_call({:method_name => method_name, :params => sanitized_args})
|
|
154
207
|
|
|
155
208
|
end
|
|
209
|
+
|
|
210
|
+
def set_network(network)
|
|
211
|
+
# load the chain_params for this network
|
|
212
|
+
@network ||= network
|
|
213
|
+
Bitcoin.chain_params = @network unless @network.to_s.size == 0
|
|
214
|
+
end
|
|
156
215
|
|
|
157
216
|
def api_call(args)
|
|
158
217
|
|
|
@@ -166,9 +225,15 @@ module BlockIo
|
|
|
166
225
|
body = {"status" => "fail", "data" => {"error_message" => "Unknown error occurred. Please report this to support@block.io. Status #{response.code}."}}
|
|
167
226
|
end
|
|
168
227
|
|
|
169
|
-
|
|
228
|
+
if !body["status"].eql?("success") then
|
|
229
|
+
# raise an exception on error for easy handling
|
|
230
|
+
# user can extract raw response using e.raw_data
|
|
231
|
+
e = APIException.new("#{body["data"]["error_message"]}")
|
|
232
|
+
e.set_raw_data(body)
|
|
233
|
+
raise e
|
|
234
|
+
end
|
|
170
235
|
|
|
171
|
-
|
|
236
|
+
set_network(body['data']['network']) if body['data'].key?('network')
|
|
172
237
|
|
|
173
238
|
body
|
|
174
239
|
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Bitcoin
|
|
4
|
+
|
|
5
|
+
# set network chain params
|
|
6
|
+
def self.chain_params=(name)
|
|
7
|
+
raise "chain params for #{name} is not defined." unless %i(BTC DOGE LTC BTCTEST DOGETEST LTCTEST).include?(name.to_sym)
|
|
8
|
+
@current_chain = nil
|
|
9
|
+
@chain_param = name.to_sym
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# current network chain params.
|
|
13
|
+
def self.chain_params
|
|
14
|
+
return @current_chain if @current_chain
|
|
15
|
+
return (@current_chain = Bitcoin::ChainParams.get(@chain_param.to_s))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class ChainParams
|
|
19
|
+
def self.get(network)
|
|
20
|
+
init(network)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.init(name)
|
|
24
|
+
i = YAML.load(File.open("#{__dir__}/chainparams/#{name}.yml"))
|
|
25
|
+
i.dust_relay_fee ||= Bitcoin::DUST_RELAY_TX_FEE
|
|
26
|
+
i
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module Secp256k1
|
|
31
|
+
|
|
32
|
+
module Ruby
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
def sign_ecdsa(data, privkey, extra_entropy)
|
|
37
|
+
privkey = privkey.htb
|
|
38
|
+
private_key = ECDSA::Format::IntegerOctetString.decode(privkey)
|
|
39
|
+
extra_entropy ||= ''
|
|
40
|
+
nonce = RFC6979.generate_rfc6979_nonce(privkey + data, extra_entropy)
|
|
41
|
+
|
|
42
|
+
# port form ecdsa gem.
|
|
43
|
+
r_point = GROUP.new_point(nonce)
|
|
44
|
+
|
|
45
|
+
point_field = ECDSA::PrimeField.new(GROUP.order)
|
|
46
|
+
r = point_field.mod(r_point.x)
|
|
47
|
+
return nil if r.zero?
|
|
48
|
+
|
|
49
|
+
rec = r_point.y & 1
|
|
50
|
+
|
|
51
|
+
e = ECDSA.normalize_digest(data, GROUP.bit_length)
|
|
52
|
+
s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
|
|
53
|
+
|
|
54
|
+
# covert to low-s
|
|
55
|
+
if s > (GROUP.order / 2)
|
|
56
|
+
s = GROUP.order - s
|
|
57
|
+
rec = r_point.y & 1
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
return nil if s.zero?
|
|
61
|
+
|
|
62
|
+
signature = ECDSA::Signature.new(r, s).to_der
|
|
63
|
+
|
|
64
|
+
# comment lines below lead to performance issues
|
|
65
|
+
# public_key = Bitcoin::Key.new(priv_key: privkey.bth, :key_type => Bitcoin::Key::TYPES[:compressed]).pubkey # get rid of the key_type warning
|
|
66
|
+
# raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
|
|
67
|
+
|
|
68
|
+
[signature, rec]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Key
|
|
75
|
+
|
|
76
|
+
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
|
77
|
+
# override so enforce compressed keys
|
|
78
|
+
|
|
79
|
+
raise "key_type must always be Bitcoin::KEY::TYPES[:compressed]" unless key_type == TYPES[:compressed]
|
|
80
|
+
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
|
81
|
+
if key_type
|
|
82
|
+
@key_type = key_type
|
|
83
|
+
compressed = @key_type != TYPES[:uncompressed]
|
|
84
|
+
else
|
|
85
|
+
@key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
|
|
86
|
+
end
|
|
87
|
+
@secp256k1_module = Bitcoin.secp_impl
|
|
88
|
+
@priv_key = priv_key
|
|
89
|
+
if @priv_key
|
|
90
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
|
|
91
|
+
end
|
|
92
|
+
if pubkey
|
|
93
|
+
@pubkey = pubkey
|
|
94
|
+
else
|
|
95
|
+
@pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
|
|
96
|
+
end
|
|
97
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def public_key_hex
|
|
101
|
+
@pubkey
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def private_key_hex
|
|
105
|
+
@priv_key
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module Bech32
|
|
113
|
+
# override so we can parse non-Bitcoin Bech32 addresses
|
|
114
|
+
|
|
115
|
+
class SegwitAddr
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
def parse_addr(addr)
|
|
119
|
+
@hrp, data, spec = Bech32.decode(addr)
|
|
120
|
+
raise 'Invalid address.' if hrp.nil? || data[0].nil? || !['bc', 'ltc', 'tb', 'tltc', 'doge', 'tdge'].include?(hrp) # HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
|
|
121
|
+
@ver = data[0]
|
|
122
|
+
raise 'Invalid witness version' if @ver > 16
|
|
123
|
+
@prog = convert_bits(data[1..-1], 5, 8, false)
|
|
124
|
+
raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
|
|
125
|
+
raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
|
|
126
|
+
raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
|