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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/{.appveyor.yml → .appveyor.yml-disabled} +2 -2
  3. data/.gitignore +1 -0
  4. data/.travis.yml +1 -1
  5. data/LICENSE +1 -1
  6. data/README.md +14 -13
  7. data/block_io.gemspec +6 -6
  8. data/examples/basic.rb +29 -5
  9. data/examples/dtrust.rb +43 -24
  10. data/examples/sweeper.rb +21 -16
  11. data/lib/block_io.rb +3 -2
  12. data/lib/block_io/api_exception.rb +11 -0
  13. data/lib/block_io/chainparams/BTC.yml +8 -0
  14. data/lib/block_io/chainparams/BTCTEST.yml +8 -0
  15. data/lib/block_io/chainparams/DOGE.yml +8 -0
  16. data/lib/block_io/chainparams/DOGETEST.yml +8 -0
  17. data/lib/block_io/chainparams/LTC.yml +8 -0
  18. data/lib/block_io/chainparams/LTCTEST.yml +8 -0
  19. data/lib/block_io/client.rb +143 -79
  20. data/lib/block_io/extended_bitcoinrb.rb +127 -0
  21. data/lib/block_io/helper.rb +137 -39
  22. data/lib/block_io/key.rb +11 -124
  23. data/lib/block_io/version.rb +1 -1
  24. data/spec/client_misc_spec.rb +76 -0
  25. data/spec/client_spec.rb +23 -178
  26. data/spec/dtrust_spec.rb +167 -0
  27. data/spec/helper_spec.rb +1 -1
  28. data/spec/key_spec.rb +50 -19
  29. data/spec/larger_transaction_spec.rb +351 -0
  30. data/spec/sweep_spec.rb +115 -0
  31. data/spec/test-cases/.gitignore +2 -0
  32. data/spec/test-cases/LICENSE +21 -0
  33. data/spec/test-cases/README.md +2 -0
  34. data/spec/test-cases/json/create_and_sign_transaction_response.json +61 -0
  35. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1261 -0
  36. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1266 -0
  37. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1271 -0
  38. data/spec/test-cases/json/create_and_sign_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +3816 -0
  39. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_3of5_195inputs.json +2931 -0
  40. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2SH_4of5_195inputs.json +5 -0
  41. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_251inputs.json +3771 -0
  42. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_252inputs.json +3786 -0
  43. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_3of5_253inputs.json +3801 -0
  44. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_251inputs.json +5 -0
  45. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_252inputs.json +5 -0
  46. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_P2WSH-over-P2SH_4of5_253inputs.json +5 -0
  47. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_251inputs.json +3771 -0
  48. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_252inputs.json +3786 -0
  49. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_3of5_253inputs.json +3801 -0
  50. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_251inputs.json +5 -0
  51. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_252inputs.json +5 -0
  52. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_WITNESS_V0_4of5_253inputs.json +5 -0
  53. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_3_of_5_keys.json +21 -0
  54. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2sh_4_of_5_keys.json +5 -0
  55. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_3_of_5_keys.json +21 -0
  56. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_p2wsh_over_p2sh_4_of_5_keys.json +5 -0
  57. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3_of_5_keys.json +36 -0
  58. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_251outputs.json +591 -0
  59. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_252outputs.json +576 -0
  60. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_3of5_253outputs.json +531 -0
  61. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4_of_5_keys.json +5 -0
  62. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_251outputs.json +5 -0
  63. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_252outputs.json +5 -0
  64. data/spec/test-cases/json/create_and_sign_transaction_response_dtrust_witness_v0_4of5_253outputs.json +5 -0
  65. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2pkh.json +5 -0
  66. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh.json +5 -0
  67. data/spec/test-cases/json/create_and_sign_transaction_response_sweep_p2wpkh_over_p2sh.json +5 -0
  68. data/spec/test-cases/json/create_and_sign_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +21 -0
  69. data/spec/test-cases/json/get_balance_response.json +8 -0
  70. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_3of5_195inputs.json +1397 -0
  71. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2SH_4of5_195inputs.json +1397 -0
  72. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_251inputs.json +1795 -0
  73. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_252inputs.json +1802 -0
  74. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_3of5_253inputs.json +1809 -0
  75. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_251inputs.json +1789 -0
  76. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_252inputs.json +1802 -0
  77. data/spec/test-cases/json/prepare_dtrust_transaction_response_P2WSH-over-P2SH_4of5_253inputs.json +1809 -0
  78. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_251inputs.json +1795 -0
  79. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_252inputs.json +1802 -0
  80. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_3of5_253inputs.json +1809 -0
  81. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_251inputs.json +1795 -0
  82. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_252inputs.json +1802 -0
  83. data/spec/test-cases/json/prepare_dtrust_transaction_response_WITNESS_V0_4of5_253inputs.json +1809 -0
  84. data/spec/test-cases/json/prepare_dtrust_transaction_response_p2sh.json +45 -0
  85. data/spec/test-cases/json/prepare_dtrust_transaction_response_p2wsh_over_p2sh.json +45 -0
  86. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0.json +52 -0
  87. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_251outputs.json +1805 -0
  88. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_252outputs.json +1804 -0
  89. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_3of5_253outputs.json +1789 -0
  90. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_251outputs.json +1805 -0
  91. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_252outputs.json +1804 -0
  92. data/spec/test-cases/json/prepare_dtrust_transaction_response_witness_v0_4of5_253outputs.json +1789 -0
  93. data/spec/test-cases/json/prepare_sweep_transaction_response_p2pkh.json +35 -0
  94. data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh.json +35 -0
  95. data/spec/test-cases/json/prepare_sweep_transaction_response_p2wpkh_over_p2sh.json +35 -0
  96. data/spec/test-cases/json/prepare_transaction_response.json +164 -0
  97. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_251inputs.json +1796 -0
  98. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_252inputs.json +1803 -0
  99. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_253inputs.json +1810 -0
  100. data/spec/test-cases/json/prepare_transaction_response_P2WSH-over-P2SH_1of2_762inputs.json +5367 -0
  101. data/spec/test-cases/json/prepare_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +76 -0
  102. data/spec/test-cases/json/summarize_prepared_transaction_response_with_blockio_fee_and_expected_unsigned_txid.json +6 -0
  103. metadata +187 -45
  104. data/examples/max_withdrawal.rb +0 -29
  105. data/lib/block_io/constants.rb +0 -10
  106. data/spec/data/sign_and_finalize_dtrust_withdrawal_request.json +0 -1
  107. data/spec/data/sign_and_finalize_sweep_request.json +0 -1
  108. data/spec/data/sign_and_finalize_withdrawal_request.json +0 -4
  109. data/spec/data/sweep_from_address_response.json +0 -1
  110. data/spec/data/withdraw_from_dtrust_address_response.json +0 -1
  111. data/spec/data/withdraw_response.json +0 -1227
  112. data/spec/rfc6979_spec.rb +0 -59
  113. 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
+
@@ -2,61 +2,165 @@ module BlockIo
2
2
 
3
3
  class Helper
4
4
 
5
- def self.signData(inputs, keys)
6
- # sign the given data with the given keys
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
- raise Exception.new("Keys object must be a hash or array containing the appropriate keys.") unless keys.size >= 1
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
- signatures_added = false
11
-
12
- # create a dictionary of keys we have
13
- # saves the next loop from being O(n^3)
14
- hkeys = (keys.is_a?(Hash) ? keys : keys.inject({}){|h,v| h[v.public_key] = v; h})
15
- odata = []
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
- # saves the next loop from being O(n^2)
18
- inputs.each{|input| odata << input["data_to_sign"]; odata << input["signatures_needed"]; odata.push(*input["signers"])}
26
+ all_signatures_present
19
27
 
20
- data_to_sign = nil
21
- signatures_needed = nil
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
- while !(cdata = odata.shift).nil? do
24
- # O(n)
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 cdata.is_a?(String) then
27
- # this is data to sign
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
- # make a copy of this
30
- data_to_sign = '' << cdata
61
+ current_public_key = address_data['public_keys'][0]
62
+ current_signature = signatures_present[current_public_key]
31
63
 
32
- # number of signatures needed
33
- signatures_needed = 0 + odata.shift
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
- else
36
- # add signatures if necessary
37
- # dTrust required signatures may be lower than number of keys provided
38
-
39
- if hkeys.key?(cdata["signer_public_key"]) and signatures_needed > 0 and cdata["signed_data"].nil? then
40
- cdata["signed_data"] = hkeys[cdata["signer_public_key"]].sign(data_to_sign)
41
- signatures_needed -= 1
42
- signatures_added ||= true
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
- signatures_added
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
- def self.extractKey(encrypted_data, b64_enc_key, use_low_r = true)
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, use_low_r)
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 initialize(privkey = nil, use_low_r = true, compressed = true)
6
- # the privkey must be in hex if at all provided
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
- # BIP0062 -- use lower S values only
43
- over_two = @group.order >> 1 # half of what it was
44
- s = @group.order - s if (s > over_two)
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, use_low_r = true)
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.new((hashed_key.to_i(16) % ECDSA::Group::Secp256k1.order).to_s(16), use_low_r)
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, use_low_r = true)
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
- compressed = hexkey[2..hexkey.length].length-8 > 64 and hexkey[2..hexkey.length][64...66] == "01"
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