block_io 2.0.0 → 3.0.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
- data/{.appveyor.yml → .appveyor.yml-disabled} +2 -2
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/LICENSE +1 -1
- data/README.md +14 -13
- data/block_io.gemspec +6 -6
- data/examples/basic.rb +29 -5
- data/examples/dtrust.rb +43 -24
- data/examples/sweeper.rb +21 -16
- data/lib/block_io.rb +3 -2
- 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 +143 -79
- data/lib/block_io/extended_bitcoinrb.rb +127 -0
- data/lib/block_io/helper.rb +137 -39
- data/lib/block_io/key.rb +11 -124
- data/lib/block_io/version.rb +1 -1
- 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 +1 -1
- data/spec/key_spec.rb +50 -19
- data/spec/larger_transaction_spec.rb +351 -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/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/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
- metadata +187 -45
- 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
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
e = ECDSA.normalize_digest(data, GROUP.bit_length)
|
|
50
|
+
s = point_field.mod(point_field.inverse(nonce) * (e + r * private_key))
|
|
51
|
+
|
|
52
|
+
# covert to low-s
|
|
53
|
+
s = GROUP.order - s if s > (GROUP.order / 2)
|
|
54
|
+
|
|
55
|
+
return nil if s.zero?
|
|
56
|
+
|
|
57
|
+
signature = ECDSA::Signature.new(r, s).to_der
|
|
58
|
+
|
|
59
|
+
# comment lines below lead to performance issues
|
|
60
|
+
# public_key = Bitcoin::Key.new(priv_key: privkey.bth, :key_type => Bitcoin::Key::TYPES[:compressed]).pubkey # get rid of the key_type warning
|
|
61
|
+
# raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
|
|
62
|
+
|
|
63
|
+
signature
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class Key
|
|
70
|
+
|
|
71
|
+
def initialize(priv_key: nil, pubkey: nil, key_type: nil, compressed: true, allow_hybrid: false)
|
|
72
|
+
# override so enforce compressed keys
|
|
73
|
+
|
|
74
|
+
raise "key_type must always be Bitcoin::KEY::TYPES[:compressed]" unless key_type == TYPES[:compressed]
|
|
75
|
+
puts "[Warning] Use key_type parameter instead of compressed. compressed parameter removed in the future." if key_type.nil? && !compressed.nil? && pubkey.nil?
|
|
76
|
+
if key_type
|
|
77
|
+
@key_type = key_type
|
|
78
|
+
compressed = @key_type != TYPES[:uncompressed]
|
|
79
|
+
else
|
|
80
|
+
@key_type = compressed ? TYPES[:compressed] : TYPES[:uncompressed]
|
|
81
|
+
end
|
|
82
|
+
@secp256k1_module = Bitcoin.secp_impl
|
|
83
|
+
@priv_key = priv_key
|
|
84
|
+
if @priv_key
|
|
85
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
|
|
86
|
+
end
|
|
87
|
+
if pubkey
|
|
88
|
+
@pubkey = pubkey
|
|
89
|
+
else
|
|
90
|
+
@pubkey = generate_pubkey(priv_key, compressed: compressed) if priv_key
|
|
91
|
+
end
|
|
92
|
+
raise ArgumentError, Errors::Messages::INVALID_PUBLIC_KEY unless fully_valid_pubkey?(allow_hybrid)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def public_key_hex
|
|
96
|
+
@pubkey
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def private_key_hex
|
|
100
|
+
@priv_key
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module Bech32
|
|
108
|
+
# override so we can parse non-Bitcoin Bech32 addresses
|
|
109
|
+
|
|
110
|
+
class SegwitAddr
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
def parse_addr(addr)
|
|
114
|
+
@hrp, data, spec = Bech32.decode(addr)
|
|
115
|
+
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)
|
|
116
|
+
@ver = data[0]
|
|
117
|
+
raise 'Invalid witness version' if @ver > 16
|
|
118
|
+
@prog = convert_bits(data[1..-1], 5, 8, false)
|
|
119
|
+
raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
|
|
120
|
+
raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
|
|
121
|
+
raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
127
|
+
|
data/lib/block_io/helper.rb
CHANGED
|
@@ -2,61 +2,165 @@ module BlockIo
|
|
|
2
2
|
|
|
3
3
|
class Helper
|
|
4
4
|
|
|
5
|
-
def self.
|
|
6
|
-
#
|
|
5
|
+
def self.allSignaturesPresent?(tx, inputs, signatures, input_address_data)
|
|
6
|
+
# returns true if transaction has all signatures present
|
|
7
|
+
|
|
8
|
+
all_signatures_present = false
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
inputs.each do |input|
|
|
11
|
+
# check if each input has its required signatures
|
|
12
|
+
|
|
13
|
+
spending_address = input['spending_address']
|
|
14
|
+
current_input_address_data = input_address_data.detect{|x| x['address'] == spending_address}
|
|
15
|
+
required_signatures = current_input_address_data['required_signatures']
|
|
16
|
+
public_keys = current_input_address_data['public_keys']
|
|
9
17
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
signatures_present = signatures.map{|x| x if x['input_index'] == input['input_index']}.compact.inject({}){|h,v| h[v['public_key']] = v['signature']; h}
|
|
19
|
+
|
|
20
|
+
# break the loop if all signatures are not present for this input
|
|
21
|
+
all_signatures_present = (signatures_present.keys.size >= required_signatures)
|
|
22
|
+
break unless all_signatures_present
|
|
23
|
+
|
|
24
|
+
end
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
inputs.each{|input| odata << input["data_to_sign"]; odata << input["signatures_needed"]; odata.push(*input["signers"])}
|
|
26
|
+
all_signatures_present
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.isSegwitAddressType?(address_type)
|
|
31
|
+
|
|
32
|
+
case address_type
|
|
33
|
+
when /^P2WPKH(-over-P2SH)?$/
|
|
34
|
+
true
|
|
35
|
+
when /^P2WSH(-over-P2SH)?$/
|
|
36
|
+
true
|
|
37
|
+
when /^WITNESS_V(\d)$/
|
|
38
|
+
true
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.finalizeTransaction(tx, inputs, signatures, input_address_data)
|
|
46
|
+
# append signatures to the transaction and return its hexadecimal representation
|
|
22
47
|
|
|
23
|
-
|
|
24
|
-
#
|
|
48
|
+
inputs.each do |input|
|
|
49
|
+
# for each input
|
|
50
|
+
|
|
51
|
+
signatures_present = signatures.map{|x| x if x['input_index'] == input['input_index']}.compact.inject({}){|h,v| h[v['public_key']] = v['signature']; h}
|
|
52
|
+
address_data = input_address_data.detect{|x| x['address'] == input['spending_address']} # contains public keys (ordered) and the address type
|
|
53
|
+
input_index = input['input_index']
|
|
54
|
+
is_segwit = isSegwitAddressType?(address_data['address_type'])
|
|
55
|
+
script_stack = (is_segwit ? tx.in[input_index].script_witness.stack : tx.in[input_index].script_sig)
|
|
25
56
|
|
|
26
|
-
if
|
|
27
|
-
#
|
|
57
|
+
if ['P2PKH', 'P2WPKH', 'P2WPKH-over-P2SH'].include?(address_data['address_type']) then
|
|
58
|
+
# P2PKH will use script_sig as script_stack
|
|
59
|
+
# P2WPKH input, or P2WPKH-over-P2SH input will use script_witness.stack as script_stack
|
|
28
60
|
|
|
29
|
-
|
|
30
|
-
|
|
61
|
+
current_public_key = address_data['public_keys'][0]
|
|
62
|
+
current_signature = signatures_present[current_public_key]
|
|
31
63
|
|
|
32
|
-
#
|
|
33
|
-
|
|
64
|
+
# no blank push necessary for P2PKH, P2WPKH, P2WPKH-over-P2SH
|
|
65
|
+
script_stack << ([current_signature].pack("H*") + [Bitcoin::SIGHASH_TYPE[:all]].pack('C'))
|
|
66
|
+
script_stack << [current_public_key].pack("H*")
|
|
34
67
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
# P2WPKH-over-P2SH required script_sig still
|
|
69
|
+
tx.in[input_index].script_sig << (
|
|
70
|
+
Bitcoin::Script.to_p2wpkh(
|
|
71
|
+
Bitcoin::Key.new(:pubkey => current_public_key, :key_type => Bitcoin::Key::TYPES[:compressed]).hash160 # hash160 of the compressed pubkey
|
|
72
|
+
).to_payload
|
|
73
|
+
) if address_data['address_type'] == "P2WPKH-over-P2SH"
|
|
74
|
+
|
|
75
|
+
elsif ['P2SH', 'WITNESS_V0', 'P2WSH-over-P2SH'].include?(address_data['address_type']) then
|
|
76
|
+
# P2SH will use script_sig as script_stack
|
|
77
|
+
# P2WSH or P2WSH-over-P2SH input will use script_witness.stack as script_stack
|
|
78
|
+
|
|
79
|
+
script = Bitcoin::Script.to_p2sh_multisig_script(address_data['required_signatures'], address_data['public_keys'])
|
|
80
|
+
|
|
81
|
+
script_stack << '' # blank push for scripthash always
|
|
82
|
+
|
|
83
|
+
signatures_added = 0
|
|
84
|
+
|
|
85
|
+
address_data['public_keys'].each do |public_key|
|
|
86
|
+
next unless signatures_present.key?(public_key)
|
|
87
|
+
|
|
88
|
+
# append signatures, no sighash needed, in correct order of public keys
|
|
89
|
+
current_signature = signatures_present[public_key]
|
|
90
|
+
script_stack << ([current_signature].pack("H*") + [Bitcoin::SIGHASH_TYPE[:all]].pack('C'))
|
|
91
|
+
|
|
92
|
+
signatures_added += 1
|
|
93
|
+
|
|
94
|
+
# required signatures added? break loop and move on
|
|
95
|
+
break if signatures_added == address_data['required_signatures']
|
|
43
96
|
end
|
|
97
|
+
|
|
98
|
+
script_stack << script.last.to_payload
|
|
99
|
+
|
|
100
|
+
# P2WSH-over-P2SH needs script_sig populated still
|
|
101
|
+
tx.in[input_index].script_sig << Bitcoin::Script.to_p2wsh(script.last).to_payload if address_data['address_type'] == "P2WSH-over-P2SH"
|
|
44
102
|
|
|
103
|
+
else
|
|
104
|
+
raise "Unrecognized input address: #{address_data['address_type']}"
|
|
45
105
|
end
|
|
46
|
-
|
|
106
|
+
|
|
47
107
|
end
|
|
48
108
|
|
|
49
|
-
|
|
109
|
+
tx.to_hex
|
|
110
|
+
|
|
50
111
|
end
|
|
112
|
+
|
|
113
|
+
def self.getSigHashForInput(tx, input_index, input_data, input_address_data)
|
|
114
|
+
# returns the sighash for the given input in bytes
|
|
115
|
+
|
|
116
|
+
address_type = input_address_data["address_type"]
|
|
117
|
+
input_value = (BigDecimal(input_data['input_value']) * BigDecimal(100000000)).to_i # in sats
|
|
118
|
+
sighash = nil
|
|
119
|
+
|
|
120
|
+
if address_type == "P2SH" then
|
|
121
|
+
# P2SH addresses
|
|
122
|
+
|
|
123
|
+
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data["required_signatures"], input_address_data["public_keys"])
|
|
124
|
+
sighash = tx.sighash_for_input(input_index, script.last)
|
|
125
|
+
|
|
126
|
+
elsif address_type == "P2WSH-over-P2SH" or address_type == "WITNESS_V0" then
|
|
127
|
+
# P2WSH-over-P2SH addresses
|
|
128
|
+
# WITNESS_V0 addresses
|
|
51
129
|
|
|
52
|
-
|
|
130
|
+
script = Bitcoin::Script.to_p2sh_multisig_script(input_address_data["required_signatures"], input_address_data["public_keys"])
|
|
131
|
+
sighash = tx.sighash_for_input(input_index, script.last, amount: input_value, sig_version: :witness_v0)
|
|
132
|
+
|
|
133
|
+
elsif address_type == "P2WPKH-over-P2SH" or address_type == "P2WPKH" then
|
|
134
|
+
# P2WPKH-over-P2SH addresses
|
|
135
|
+
# P2WPKH addresses
|
|
136
|
+
|
|
137
|
+
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys'].first, :key_type => Bitcoin::Key::TYPES[:compressed]) # compressed
|
|
138
|
+
script = Bitcoin::Script.to_p2wpkh(pub_key.hash160)
|
|
139
|
+
sighash = tx.sighash_for_input(input_index, script, amount: input_value, sig_version: :witness_v0)
|
|
140
|
+
|
|
141
|
+
elsif address_type == "P2PKH" then
|
|
142
|
+
# P2PKH addresses
|
|
143
|
+
|
|
144
|
+
pub_key = Bitcoin::Key.new(:pubkey => input_address_data['public_keys'].first, :key_type => Bitcoin::Key::TYPES[:compressed]) # compressed
|
|
145
|
+
script = Bitcoin::Script.to_p2pkh(pub_key.hash160)
|
|
146
|
+
sighash = tx.sighash_for_input(input_index, script)
|
|
147
|
+
|
|
148
|
+
else
|
|
149
|
+
raise "Unrecognize address type: #{address_type}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
sighash
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def self.extractKey(encrypted_data, b64_enc_key)
|
|
53
157
|
# passphrase is in plain text
|
|
54
158
|
# encrypted_data is in base64, as it was stored on Block.io
|
|
55
159
|
# returns the private key extracted from the given encrypted data
|
|
56
160
|
|
|
57
161
|
decrypted = self.decrypt(encrypted_data, b64_enc_key)
|
|
58
162
|
|
|
59
|
-
Key.from_passphrase(decrypted
|
|
163
|
+
Key.from_passphrase(decrypted)
|
|
60
164
|
|
|
61
165
|
end
|
|
62
166
|
|
|
@@ -90,12 +194,6 @@ module BlockIo
|
|
|
90
194
|
|
|
91
195
|
end
|
|
92
196
|
|
|
93
|
-
def self.low_r?(r)
|
|
94
|
-
# https://github.com/bitcoin/bitcoin/blob/v0.20.0/src/key.cpp#L207
|
|
95
|
-
h = r.scan(/../)
|
|
96
|
-
h[3].to_i(16) == 32 and h[4].to_i(16) < 0x80
|
|
97
|
-
end
|
|
98
|
-
|
|
99
197
|
# Decrypts a block of data (encrypted_data) given an encryption key
|
|
100
198
|
def self.decrypt(encrypted_data, b64_enc_key, iv = nil, cipher_type = "AES-256-ECB")
|
|
101
199
|
|
data/lib/block_io/key.rb
CHANGED
|
@@ -2,67 +2,18 @@ module BlockIo
|
|
|
2
2
|
|
|
3
3
|
class Key
|
|
4
4
|
|
|
5
|
-
def
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
@group = ECDSA::Group::Secp256k1
|
|
9
|
-
@private_key = (privkey.nil? ? (1 + SecureRandom.random_number(@group.order - 1)) : privkey.to_i(16))
|
|
10
|
-
@public_key = @group.generator.multiply_by_scalar(@private_key)
|
|
11
|
-
@compressed = compressed
|
|
12
|
-
@use_low_r = use_low_r
|
|
13
|
-
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def private_key
|
|
17
|
-
# returns private key in hex form
|
|
18
|
-
@private_key.to_s(16)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def public_key
|
|
22
|
-
# returns the compressed form of the public key to save network fees (shorter scripts)
|
|
23
|
-
# hex form
|
|
24
|
-
ECDSA::Format::PointOctetString.encode(@public_key, compression: @compressed).unpack("H*")[0]
|
|
5
|
+
def self.generate
|
|
6
|
+
# returns a new key
|
|
7
|
+
Bitcoin::Key.generate(Bitcoin::Key::TYPES[:compressed]) # compressed
|
|
25
8
|
end
|
|
26
|
-
|
|
27
|
-
def sign(data)
|
|
28
|
-
# sign the given hexadecimal string
|
|
29
|
-
|
|
30
|
-
counter = 0
|
|
31
|
-
signature = nil
|
|
32
|
-
|
|
33
|
-
loop do
|
|
34
|
-
|
|
35
|
-
# first this we get K, it's without extra entropy
|
|
36
|
-
# second time onwards, with extra entropy
|
|
37
|
-
nonce = Key.deterministicGenerateK([data].pack("H*"), @private_key, counter) # RFC6979
|
|
38
|
-
signature = ECDSA.sign(@group, @private_key, data.to_i(16), nonce)
|
|
39
|
-
|
|
40
|
-
r, s = signature.components
|
|
41
9
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
signature = ECDSA::Signature.new(r, s)
|
|
47
|
-
|
|
48
|
-
# DER encode this, and return it in hex form
|
|
49
|
-
signature = ECDSA::Format::SignatureDerString.encode(signature).unpack("H*")[0]
|
|
50
|
-
|
|
51
|
-
break if !@use_low_r or Helper.low_r?(signature)
|
|
52
|
-
|
|
53
|
-
counter += 1
|
|
54
|
-
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
signature
|
|
58
|
-
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def valid_signature?(signature, data)
|
|
62
|
-
ECDSA.valid_signature?(@public_key, [data].pack("H*"), ECDSA::Format::SignatureDerString.decode([signature].pack("H*")))
|
|
10
|
+
def self.from_private_key_hex(priv_key_hex)
|
|
11
|
+
# returns Bitcoin::Key (compressed)
|
|
12
|
+
# quirky behavior from bitcoinrb 0.7.0: use IntegerOctetString.encode on private key (integer) first
|
|
13
|
+
Bitcoin::Key.new(:priv_key => ECDSA::Format::IntegerOctetString.encode(priv_key_hex.to_i(16), 32).bth, :key_type => Bitcoin::Key::TYPES[:compressed])
|
|
63
14
|
end
|
|
64
15
|
|
|
65
|
-
def self.from_passphrase(passphrase
|
|
16
|
+
def self.from_passphrase(passphrase)
|
|
66
17
|
# ATTENTION: use BlockIo::Key.new to generate new private keys. Using passphrases is not recommended due to lack of / low entropy.
|
|
67
18
|
# create a private/public key pair from a given passphrase
|
|
68
19
|
# use a long, random passphrase. your security depends on the passphrase's entropy.
|
|
@@ -72,78 +23,14 @@ module BlockIo
|
|
|
72
23
|
hashed_key = Helper.sha256([passphrase].pack("H*")) # must pass bytes to sha256
|
|
73
24
|
|
|
74
25
|
# modding is for backward compatibility with legacy bitcoinjs
|
|
75
|
-
Key.
|
|
26
|
+
BlockIo::Key.from_private_key_hex((hashed_key.to_i(16) % ECDSA::Group::Secp256k1.order).to_s(16))
|
|
76
27
|
end
|
|
77
28
|
|
|
78
|
-
def self.from_wif(wif
|
|
29
|
+
def self.from_wif(wif)
|
|
79
30
|
# returns a new key extracted from the Wallet Import Format provided
|
|
80
|
-
# TODO check against checksum
|
|
81
|
-
|
|
82
|
-
hexkey = Helper.decode_base58(wif)
|
|
83
|
-
actual_key = hexkey[2...66]
|
|
84
31
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Key.new(actual_key, use_low_r, compressed)
|
|
88
|
-
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
def self.isPositive(i)
|
|
94
|
-
sig = "!+-"[i <=> 0]
|
|
95
|
-
sig.eql?("+")
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def self.deterministicGenerateK(data, privkey, extra_entropy = nil, group = ECDSA::Group::Secp256k1)
|
|
99
|
-
# returns a deterministic K -- RFC6979
|
|
100
|
-
|
|
101
|
-
hash = data.bytes.to_a
|
|
102
|
-
|
|
103
|
-
x = [privkey.to_s(16)].pack("H*").bytes.to_a
|
|
104
|
-
|
|
105
|
-
k = [0] * 32
|
|
106
|
-
v = [1] * 32
|
|
107
|
-
|
|
108
|
-
e = (extra_entropy.to_i <= 0 ? [] : [extra_entropy.to_s(16).rjust(64,"0").scan(/../).reverse.join].pack("H*").bytes.to_a)
|
|
109
|
-
|
|
110
|
-
# step D
|
|
111
|
-
k_data = [v, [0], x, hash, e]
|
|
112
|
-
k_data.flatten!
|
|
113
|
-
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), k_data.pack("C*")).bytes.to_a
|
|
114
|
-
|
|
115
|
-
# step E
|
|
116
|
-
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
|
117
|
-
|
|
118
|
-
# step F
|
|
119
|
-
k_data = [v, [1], x, hash, e]
|
|
120
|
-
k_data.flatten!
|
|
121
|
-
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), k_data.pack("C*")).bytes.to_a
|
|
122
|
-
|
|
123
|
-
# step G
|
|
124
|
-
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
|
125
|
-
|
|
126
|
-
# step H2b (Step H1/H2a ignored)
|
|
127
|
-
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
|
128
|
-
|
|
129
|
-
h2b = v.pack("C*").unpack("H*")[0]
|
|
130
|
-
tNum = h2b.to_i(16)
|
|
131
|
-
|
|
132
|
-
# step H3
|
|
133
|
-
while (!isPositive(tNum) or tNum >= group.order) do
|
|
134
|
-
# k = crypto.HmacSHA256(Buffer.concat([v, new Buffer([0])]), k)
|
|
135
|
-
k_data = [v, [0]]
|
|
136
|
-
k_data.flatten!
|
|
137
|
-
k = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), k_data.pack("C*")).bytes.to_a
|
|
138
|
-
|
|
139
|
-
# v = crypto.HmacSHA256(v, k)
|
|
140
|
-
v = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), k.pack("C*"), v.pack("C*")).bytes.to_a
|
|
141
|
-
|
|
142
|
-
# T = BigInteger.fromBuffer(v)
|
|
143
|
-
tNum = v.pack("C*").unpack("H*")[0].to_i(16)
|
|
144
|
-
end
|
|
32
|
+
Bitcoin::Key.from_wif(wif)
|
|
145
33
|
|
|
146
|
-
tNum
|
|
147
34
|
end
|
|
148
35
|
|
|
149
36
|
end
|