bitcoin-ruby 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +2 -7
  4. data/COPYING +1 -1
  5. data/Gemfile +2 -6
  6. data/Gemfile.lock +34 -0
  7. data/README.rdoc +16 -68
  8. data/Rakefile +3 -6
  9. data/bin/bitcoin_shell +0 -1
  10. data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
  11. data/lib/bitcoin.rb +350 -296
  12. data/lib/bitcoin/builder.rb +3 -1
  13. data/lib/bitcoin/connection.rb +2 -1
  14. data/lib/bitcoin/contracthash.rb +76 -0
  15. data/lib/bitcoin/dogecoin.rb +97 -0
  16. data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
  17. data/lib/bitcoin/ffi/openssl.rb +98 -2
  18. data/lib/bitcoin/ffi/secp256k1.rb +144 -0
  19. data/lib/bitcoin/key.rb +12 -2
  20. data/lib/bitcoin/logger.rb +3 -12
  21. data/lib/bitcoin/protocol/block.rb +3 -9
  22. data/lib/bitcoin/protocol/parser.rb +6 -2
  23. data/lib/bitcoin/protocol/tx.rb +44 -13
  24. data/lib/bitcoin/protocol/txin.rb +4 -2
  25. data/lib/bitcoin/protocol/txout.rb +2 -2
  26. data/lib/bitcoin/script.rb +212 -37
  27. data/lib/bitcoin/trezor/mnemonic.rb +130 -0
  28. data/lib/bitcoin/version.rb +1 -1
  29. data/spec/bitcoin/bitcoin_spec.rb +32 -3
  30. data/spec/bitcoin/builder_spec.rb +18 -0
  31. data/spec/bitcoin/contracthash_spec.rb +45 -0
  32. data/spec/bitcoin/dogecoin_spec.rb +176 -0
  33. data/spec/bitcoin/ffi_openssl.rb +45 -0
  34. data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
  35. data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
  36. data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
  37. data/spec/bitcoin/fixtures/coinbase.json +24 -0
  38. data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
  39. data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
  40. data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
  41. data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
  42. data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
  43. data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
  44. data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
  45. data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
  46. data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
  47. data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
  48. data/spec/bitcoin/protocol/block_spec.rb +0 -22
  49. data/spec/bitcoin/protocol/tx_spec.rb +145 -2
  50. data/spec/bitcoin/script/script_spec.rb +282 -0
  51. data/spec/bitcoin/secp256k1_spec.rb +48 -0
  52. data/spec/bitcoin/spec_helper.rb +0 -51
  53. data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
  54. metadata +48 -98
  55. data/bin/bitcoin_dns_seed +0 -130
  56. data/bin/bitcoin_gui +0 -80
  57. data/bin/bitcoin_node +0 -153
  58. data/bin/bitcoin_node_cli +0 -81
  59. data/bin/bitcoin_wallet +0 -402
  60. data/doc/CONFIG.rdoc +0 -66
  61. data/doc/EXAMPLES.rdoc +0 -13
  62. data/doc/NAMECOIN.rdoc +0 -34
  63. data/doc/NODE.rdoc +0 -225
  64. data/doc/STORAGE.rdoc +0 -33
  65. data/doc/WALLET.rdoc +0 -102
  66. data/examples/balance.rb +0 -66
  67. data/examples/forwarder.rb +0 -73
  68. data/examples/index_nhash.rb +0 -24
  69. data/examples/reindex_p2sh_addrs.rb +0 -44
  70. data/examples/relay_tx.rb +0 -22
  71. data/examples/verify_tx.rb +0 -57
  72. data/lib/bitcoin/config.rb +0 -58
  73. data/lib/bitcoin/gui/addr_view.rb +0 -44
  74. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  75. data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
  76. data/lib/bitcoin/gui/conn_view.rb +0 -38
  77. data/lib/bitcoin/gui/connection.rb +0 -70
  78. data/lib/bitcoin/gui/em_gtk.rb +0 -30
  79. data/lib/bitcoin/gui/gui.builder +0 -1643
  80. data/lib/bitcoin/gui/gui.rb +0 -292
  81. data/lib/bitcoin/gui/helpers.rb +0 -115
  82. data/lib/bitcoin/gui/tree_view.rb +0 -84
  83. data/lib/bitcoin/gui/tx_view.rb +0 -69
  84. data/lib/bitcoin/namecoin.rb +0 -280
  85. data/lib/bitcoin/network/command_client.rb +0 -104
  86. data/lib/bitcoin/network/command_handler.rb +0 -570
  87. data/lib/bitcoin/network/connection_handler.rb +0 -387
  88. data/lib/bitcoin/network/node.rb +0 -565
  89. data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
  90. data/lib/bitcoin/storage/models.rb +0 -171
  91. data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
  92. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
  93. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
  94. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
  95. data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
  96. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
  97. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
  98. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
  99. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
  100. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
  101. data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
  102. data/lib/bitcoin/storage/storage.rb +0 -517
  103. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
  104. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
  105. data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
  106. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
  107. data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
  108. data/lib/bitcoin/validation.rb +0 -400
  109. data/lib/bitcoin/wallet/coinselector.rb +0 -33
  110. data/lib/bitcoin/wallet/keygenerator.rb +0 -77
  111. data/lib/bitcoin/wallet/keystore.rb +0 -207
  112. data/lib/bitcoin/wallet/txdp.rb +0 -118
  113. data/lib/bitcoin/wallet/wallet.rb +0 -281
  114. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  115. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
  116. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  117. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
  118. data/spec/bitcoin/namecoin_spec.rb +0 -182
  119. data/spec/bitcoin/node/command_api_spec.rb +0 -663
  120. data/spec/bitcoin/storage/models_spec.rb +0 -104
  121. data/spec/bitcoin/storage/reorg_spec.rb +0 -236
  122. data/spec/bitcoin/storage/storage_spec.rb +0 -387
  123. data/spec/bitcoin/storage/validation_spec.rb +0 -300
  124. data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
  125. data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
  126. data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
  127. data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
  128. data/spec/bitcoin/wallet/wallet_spec.rb +0 -238
@@ -432,11 +432,13 @@ module Bitcoin
432
432
  # o.value 12345
433
433
  # o.script {|s| s.recipient address }
434
434
  # end
435
+ #
436
+ # t.output {|o| o.to "deadbeef", OP_RETURN }
435
437
  class TxOutBuilder
436
438
  attr_reader :txout
437
439
 
438
440
  def initialize
439
- @txout = P::TxOut.new
441
+ @txout = P::TxOut.new(0)
440
442
  end
441
443
 
442
444
  # Set output value (in base units / "satoshis")
@@ -3,6 +3,7 @@
3
3
  require 'socket'
4
4
  require 'eventmachine'
5
5
  require 'bitcoin'
6
+ require 'resolv'
6
7
 
7
8
  module Bitcoin
8
9
 
@@ -107,7 +108,7 @@ module Bitcoin
107
108
  def self.connect_random_from_dns(connections)
108
109
  seeds = Bitcoin.network[:dns_seeds]
109
110
  if seeds.any?
110
- host = `nslookup #{seeds.sample}`.scan(/Address\: (.+)$/).flatten.sample
111
+ host = Resolv::DNS.new.getaddresses(seeds.sample).map {|a| a.to_s}.sample
111
112
  connect(host, Bitcoin::network[:default_port], connections)
112
113
  else
113
114
  raise "No DNS seeds available. Provide IP, configure seeds, or use different network."
@@ -0,0 +1,76 @@
1
+ #
2
+ # Ruby port of https://github.com/Blockstream/contracthashtool
3
+ #
4
+
5
+ module Bitcoin
6
+ module ContractHash
7
+
8
+ HMAC_DIGEST = OpenSSL::Digest.new("SHA256")
9
+ EC_GROUP = OpenSSL::PKey::EC::Group.new("secp256k1")
10
+
11
+ def self.hmac(pubkey, data)
12
+ OpenSSL::HMAC.hexdigest(HMAC_DIGEST, pubkey, data)
13
+ end
14
+
15
+ # generate a contract address
16
+ def self.generate(redeem_script_hex, payee_address_or_ascii, nonce_hex=nil)
17
+ redeem_script = Bitcoin::Script.new([redeem_script_hex].pack("H*"))
18
+ raise "only multisig redeem scripts are currently supported" unless redeem_script.is_multisig?
19
+ nonce_hex, data = compute_data(payee_address_or_ascii, nonce_hex)
20
+
21
+ derived_keys = []
22
+ redeem_script.get_multisig_pubkeys.each do |pubkey|
23
+ tweak = hmac(pubkey, data).to_i(16)
24
+ raise "order exceeded, pick a new nonce" if tweak >= EC_GROUP.order.to_i
25
+ tweak = OpenSSL::BN.new(tweak.to_s)
26
+
27
+ key = Bitcoin::Key.new(nil, pubkey.unpack("H*")[0])
28
+ key = key.instance_variable_get(:@key)
29
+ point = EC_GROUP.generator.mul(tweak).ec_add(key.public_key).to_bn.to_i
30
+ raise "infinity" if point == 1/0.0
31
+
32
+ key = Bitcoin::Key.new(nil, point.to_s(16))
33
+ key.instance_eval{ @pubkey_compressed = true }
34
+ derived_keys << key.pub
35
+ end
36
+
37
+ m = redeem_script.get_signatures_required
38
+ p2sh_script, redeem_script = Bitcoin::Script.to_p2sh_multisig_script(m, *derived_keys)
39
+
40
+ [ nonce_hex, redeem_script.unpack("H*")[0], Bitcoin::Script.new(p2sh_script).get_p2sh_address ]
41
+ end
42
+
43
+ # claim a contract
44
+ def self.claim(private_key_wif, payee_address_or_ascii, nonce_hex)
45
+ key = Bitcoin::Key.from_base58(private_key_wif)
46
+ data = compute_data(payee_address_or_ascii, nonce_hex)[1]
47
+
48
+ pubkey = [key.pub].pack("H*")
49
+ tweak = hmac(pubkey, data).to_i(16)
50
+ raise "order exceeded, verify parameters" if tweak >= EC_GROUP.order.to_i
51
+
52
+ derived_key = (tweak + key.priv.to_i(16)) % EC_GROUP.order.to_i
53
+ raise "zero" if derived_key == 0
54
+
55
+ Bitcoin::Key.new(derived_key.to_s(16))
56
+ end
57
+
58
+ # compute HMAC data
59
+ def self.compute_data(address_or_ascii, nonce_hex)
60
+ nonce = nonce_hex ? [nonce_hex].pack("H32") : SecureRandom.random_bytes(16)
61
+ if Bitcoin.valid_address?(address_or_ascii)
62
+ address_type = case Bitcoin.address_type(address_or_ascii)
63
+ when :hash160; 'P2PH'
64
+ when :p2sh; 'P2SH'
65
+ else
66
+ raise "unsupported address type #{address_type}"
67
+ end
68
+ contract_bytes = [ Bitcoin.hash160_from_address(address_or_ascii) ].pack("H*")
69
+ else
70
+ address_type = "TEXT"
71
+ contract_bytes = address_or_ascii
72
+ end
73
+ [ nonce.unpack("H*")[0], address_type + nonce + contract_bytes ]
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # This module includes (almost) everything necessary to add dogecoin support
4
+ # to bitcoin-ruby. When switching to a :dogecoin network, it will load its
5
+ # functionality into the Script class.
6
+ # The only things not included here should be parsing the AuxPow, which is
7
+ # done in Protocol::Block directly, and passing the txout to #store_doge from
8
+ # the storage backend.
9
+ module Bitcoin::Dogecoin
10
+
11
+ def self.load
12
+ Bitcoin::Util.class_eval { include Util }
13
+ end
14
+
15
+ # fixed reward past the 600k block
16
+ POST_600K_REWARD = 10000 * Bitcoin::COIN
17
+
18
+ # Dogecoin-specific Script methods for parsing and creating of dogecoin scripts,
19
+ # as well as methods to extract address, doge_hash, doge and value.
20
+ module Util
21
+
22
+ def self.included(base)
23
+ base.constants.each {|c| const_set(c, base.const_get(c)) unless constants.include?(c) }
24
+ base.class_eval do
25
+
26
+ def block_creation_reward(block_height)
27
+ if block_height < Bitcoin.network[:difficulty_change_block]
28
+ # Dogecoin early rewards were random, using part of the hash of the
29
+ # previous block as the seed for the Mersenne Twister algorithm.
30
+ # Given we don't have previous block hash available, and this value is
31
+ # functionally a maximum (not exact value), I'm using the maximum the random
32
+ # reward generator can produce and calling it good enough.
33
+ Bitcoin.network[:reward_base] / (2 ** (block_height / Bitcoin.network[:reward_halving].to_f).floor) * 2
34
+ elsif block_height < 600000
35
+ Bitcoin.network[:reward_base] / (2 ** (block_height / Bitcoin.network[:reward_halving].to_f).floor)
36
+ else
37
+ POST_600K_REWARD
38
+ end
39
+ end
40
+
41
+ def block_new_target(prev_height, prev_block_time, prev_block_bits, last_retarget_time)
42
+ new_difficulty_protocol = (prev_height + 1) >= Bitcoin.network[:difficulty_change_block]
43
+
44
+ # target interval for block interval in seconds
45
+ retarget_time = Bitcoin.network[:retarget_time]
46
+
47
+ if new_difficulty_protocol
48
+ # what is the ideal interval between the blocks
49
+ retarget_time = Bitcoin.network[:retarget_time_new]
50
+ end
51
+
52
+ # actual time elapsed since last retarget
53
+ actual_time = prev_block_time - last_retarget_time
54
+
55
+ if new_difficulty_protocol
56
+ # DigiShield implementation - thanks to RealSolid & WDC for this code
57
+ # We round always towards zero to match the C++ version
58
+ if actual_time < retarget_time
59
+ actual_time = retarget_time + ((actual_time - retarget_time) / 8.0).ceil
60
+ else
61
+ actual_time = retarget_time + ((actual_time - retarget_time) / 8.0).floor
62
+ end
63
+ # amplitude filter - thanks to daft27 for this code
64
+ min = retarget_time - (retarget_time/4)
65
+ max = retarget_time + (retarget_time/2)
66
+ elsif prev_height+1 > 10000
67
+ min = retarget_time / 4
68
+ max = retarget_time * 4
69
+ elsif prev_height+1 > 5000
70
+ min = retarget_time / 8
71
+ max = retarget_time * 4
72
+ else
73
+ min = retarget_time / 16
74
+ max = retarget_time * 4
75
+ end
76
+
77
+ actual_time = min if actual_time < min
78
+ actual_time = max if actual_time > max
79
+
80
+ # It could be a bit confusing: we are adjusting difficulty of the previous block, while logically
81
+ # we should use difficulty of the previous 2016th block ("first")
82
+
83
+ prev_target = decode_compact_bits(prev_block_bits).to_i(16)
84
+
85
+ new_target = prev_target * actual_time / retarget_time
86
+ if new_target < Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
87
+ encode_compact_bits(new_target.to_s(16))
88
+ else
89
+ Bitcoin.network[:proof_of_work_limit]
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'ffi'
4
+
5
+ # Wraps bitcoinconsensus.so (https://github.com/bitcoin/bitcoin)
6
+ # commit: 90c71548c795787b008bc337cb9332f75d1bccdb
7
+
8
+ module Bitcoin
9
+ module BitcoinConsensus
10
+ extend FFI::Library
11
+
12
+ SCRIPT_VERIFY_NONE = 0
13
+ SCRIPT_VERIFY_P2SH = (1 << 0)
14
+ SCRIPT_VERIFY_STRICTENC = (1 << 1)
15
+ SCRIPT_VERIFY_DERSIG = (1 << 2)
16
+ SCRIPT_VERIFY_LOW_S = (1 << 3)
17
+ SCRIPT_VERIFY_NULLDUMMY = (1 << 4)
18
+ SCRIPT_VERIFY_SIGPUSHONLY = (1 << 5)
19
+ SCRIPT_VERIFY_MINIMALDATA = (1 << 6)
20
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = (1 << 7)
21
+ SCRIPT_VERIFY_CLEANSTACK = (1 << 8)
22
+
23
+ ERR_CODES = { 0 => :ok, 1 => :tx_index, 2 => :tx_size_mismatch, 3 => :tx_deserialize }
24
+
25
+ def self.ffi_load_functions(file)
26
+ class_eval <<-RUBY
27
+ ffi_lib [ %[#{file}] ]
28
+ attach_function :bitcoinconsensus_version, [], :uint
29
+
30
+ # int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
31
+ # const unsigned char *txTo , unsigned int txToLen,
32
+ # unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
33
+ attach_function :bitcoinconsensus_verify_script, [:pointer, :uint, :pointer, :uint, :uint, :uint, :pointer], :int
34
+ RUBY
35
+ end
36
+
37
+ def self.lib_available?
38
+ @__lib_path ||= [ ENV['BITCOINCONSENSUS_LIB_PATH'], 'vendor/bitcoin/src/.libs/libbitcoinconsensus.so' ].find{|f| File.exists?(f.to_s) }
39
+ end
40
+
41
+ def self.init
42
+ return if @bitcoin_consensus
43
+ lib_path = lib_available?
44
+ ffi_load_functions(lib_path)
45
+ @bitcoin_consensus = true
46
+ end
47
+
48
+ # api version
49
+ def self.version
50
+ init
51
+ bitcoinconsensus_version
52
+ end
53
+
54
+ def self.verify_script(input_index, script_pubkey, tx_payload, script_flags)
55
+ init
56
+
57
+ scriptPubKey = FFI::MemoryPointer.new(:uchar, script_pubkey.bytesize).put_bytes(0, script_pubkey)
58
+ txTo = FFI::MemoryPointer.new(:uchar, tx_payload.bytesize).put_bytes(0, tx_payload)
59
+ error_ret = FFI::MemoryPointer.new(:uint)
60
+
61
+ ret = bitcoinconsensus_verify_script(scriptPubKey, scriptPubKey.size, txTo, txTo.size, input_index, script_flags, error_ret)
62
+
63
+ case ret
64
+ when 0
65
+ false
66
+ when 1
67
+ (ERR_CODES[error_ret.read_int] == :ok) ? true : false
68
+ else
69
+ raise "error invalid result"
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -3,7 +3,7 @@
3
3
  # autoload when you need to re-generate a public_key from only its private_key.
4
4
  # ported from: https://github.com/sipa/bitcoin/blob/2d40fe4da9ea82af4b652b691a4185431d6e47a8/key.h
5
5
 
6
- Bitcoin.require_dependency :ffi, exit: false, message: "Skipping FFI needed for OpenSSL_EC methods."
6
+ require 'ffi'
7
7
 
8
8
  module Bitcoin
9
9
  module OpenSSL_EC
@@ -27,7 +27,7 @@ module OpenSSL_EC
27
27
  attach_function :BN_CTX_new, [], :pointer
28
28
  attach_function :BN_add, [:pointer, :pointer, :pointer], :int
29
29
  attach_function :BN_bin2bn, [:pointer, :int, :pointer], :pointer
30
- attach_function :BN_bn2bin, [:pointer, :pointer], :void
30
+ attach_function :BN_bn2bin, [:pointer, :pointer], :int
31
31
  attach_function :BN_cmp, [:pointer, :pointer], :int
32
32
  attach_function :BN_copy, [:pointer, :pointer], :pointer
33
33
  attach_function :BN_dup, [:pointer], :pointer
@@ -38,7 +38,9 @@ module OpenSSL_EC
38
38
  attach_function :BN_mul_word, [:pointer, :int], :int
39
39
  attach_function :BN_new, [], :pointer
40
40
  attach_function :BN_rshift, [:pointer, :pointer, :int], :int
41
+ attach_function :BN_rshift1, [:pointer, :pointer], :int
41
42
  attach_function :BN_set_word, [:pointer, :int], :int
43
+ attach_function :BN_sub, [:pointer, :pointer, :pointer], :int
42
44
  attach_function :EC_GROUP_get_curve_GFp, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
43
45
  attach_function :EC_GROUP_get_degree, [:pointer], :int
44
46
  attach_function :EC_GROUP_get_order, [:pointer, :pointer, :pointer], :int
@@ -61,6 +63,13 @@ module OpenSSL_EC
61
63
  attach_function :ECDSA_do_sign, [:pointer, :uint, :pointer], :pointer
62
64
  attach_function :BN_num_bits, [:pointer], :int
63
65
  attach_function :ECDSA_SIG_free, [:pointer], :void
66
+ attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
67
+ attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string
68
+ attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer
69
+ attach_function :ECDSA_SIG_new, [], :pointer
70
+ attach_function :d2i_ECDSA_SIG, [:pointer, :pointer, :long], :pointer
71
+ attach_function :i2d_ECDSA_SIG, [:pointer, :pointer], :int
72
+ attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void
64
73
 
65
74
  def self.BN_num_bytes(ptr); (BN_num_bits(ptr) + 7) / 8; end
66
75
 
@@ -223,6 +232,50 @@ module OpenSSL_EC
223
232
  pub_hex
224
233
  end
225
234
 
235
+ # Regenerate a DER-encoded signature such that the S-value complies with the BIP62
236
+ # specification.
237
+ #
238
+ def self.signature_to_low_s(signature)
239
+ init_ffi_ssl
240
+
241
+ buf = FFI::MemoryPointer.new(:uint8, 34)
242
+ temp = signature.unpack("C*")
243
+ length_r = temp[3]
244
+ length_s = temp[5+length_r]
245
+ sig = FFI::MemoryPointer.from_string(signature)
246
+
247
+ # Calculate the lower s value
248
+ s = BN_bin2bn(sig[6 + length_r], length_s, BN_new())
249
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
250
+ group, order, halforder, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_new(), BN_CTX_new()
251
+
252
+ EC_GROUP_get_order(group, order, ctx)
253
+ BN_rshift1(halforder, order)
254
+ if BN_cmp(s, halforder) > 0
255
+ BN_sub(s, order, s)
256
+ end
257
+
258
+ BN_free(halforder)
259
+ BN_free(order)
260
+ BN_CTX_free(ctx)
261
+
262
+ length_s = BN_bn2bin(s, buf)
263
+ # p buf.read_string(length_s).unpack("H*")
264
+
265
+ # Re-encode the signature in DER format
266
+ sig = [0x30, 0, 0x02, length_r]
267
+ sig.concat(temp.slice(4, length_r))
268
+ sig << 0x02
269
+ sig << length_s
270
+ sig.concat(buf.read_string(length_s).unpack("C*"))
271
+ sig[1] = sig.size - 2
272
+
273
+ BN_free(s)
274
+ EC_KEY_free(eckey)
275
+
276
+ sig.pack("C*")
277
+ end
278
+
226
279
  def self.sign_compact(hash, private_key, public_key_hex = nil, pubkey_compressed = nil)
227
280
  private_key = [private_key].pack("H*") if private_key.bytesize >= 64
228
281
  private_key_hex = private_key.unpack("H*")[0]
@@ -279,6 +332,49 @@ module OpenSSL_EC
279
332
  pubkey = recover_public_key_from_signature(hash, signature, version-27, compressed)
280
333
  end
281
334
 
335
+ # lifted from https://github.com/GemHQ/money-tree
336
+ def self.ec_add(point_0, point_1)
337
+ init_ffi_ssl
338
+
339
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
340
+ group = EC_KEY_get0_group(eckey)
341
+
342
+ point_0_hex = point_0.to_bn.to_s(16)
343
+ point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil)
344
+ point_1_hex = point_1.to_bn.to_s(16)
345
+ point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil)
346
+
347
+ sum_point = EC_POINT_new(group)
348
+ success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil)
349
+ hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil)
350
+ EC_KEY_free(eckey)
351
+ EC_POINT_free(sum_point)
352
+ hex
353
+ end
354
+
355
+ # repack signature for OpenSSL 1.0.1k handling of DER signatures
356
+ # https://github.com/bitcoin/bitcoin/pull/5634/files
357
+ def self.repack_der_signature(signature)
358
+ init_ffi_ssl
359
+
360
+ return false if signature.empty?
361
+
362
+ # New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first.
363
+ norm_der = FFI::MemoryPointer.new(:pointer)
364
+ sig_ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, FFI::MemoryPointer.from_string(signature))
365
+
366
+ norm_sig = d2i_ECDSA_SIG(nil, sig_ptr, signature.bytesize)
367
+
368
+ derlen = i2d_ECDSA_SIG(norm_sig, norm_der)
369
+ ECDSA_SIG_free(norm_sig)
370
+ return false if derlen <= 0
371
+
372
+ ret = norm_der.read_pointer.read_string(derlen)
373
+ OPENSSL_free(norm_der.read_pointer)
374
+
375
+ ret
376
+ end
377
+
282
378
  def self.init_ffi_ssl
283
379
  return if @ssl_loaded
284
380
  SSL_library_init()
@@ -0,0 +1,144 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Wraps libsecp256k1 (https://github.com/bitcoin/secp256k1)
4
+ # commit: 50cc6ab0625efda6dddf1dc86c1e2671f069b0d8
5
+
6
+ require 'ffi'
7
+
8
+ module Bitcoin
9
+ module Secp256k1
10
+ extend FFI::Library
11
+
12
+ SECP256K1_START_VERIFY = (1 << 0)
13
+ SECP256K1_START_SIGN = (1 << 1)
14
+
15
+ def self.ffi_load_functions(file)
16
+ class_eval <<-RUBY
17
+ ffi_lib [ %[#{file}] ]
18
+
19
+ attach_function :secp256k1_start, [:int], :void
20
+ attach_function :secp256k1_stop, [], :void
21
+ attach_function :secp256k1_ec_seckey_verify, [:pointer], :int
22
+ attach_function :secp256k1_ec_pubkey_verify, [:pointer, :int], :int
23
+ attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer, :int], :int
24
+
25
+ # int secp256k1_ecdsa_sign(const unsigned char *msg32, unsigned char *sig, int *siglen, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata)
26
+ attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
27
+
28
+ # int secp256k1_ecdsa_verify(const unsigned char *msg32, const unsigned char *sig, int siglen, const unsigned char *pubkey, int pubkeylen)
29
+ attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :int, :pointer, :int], :int
30
+
31
+ # int secp256k1_ecdsa_sign_compact(const unsigned char *msg32, unsigned char *sig64, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata, int *recid)
32
+ attach_function :secp256k1_ecdsa_sign_compact, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
33
+
34
+ # int secp256k1_ecdsa_recover_compact(const unsigned char *msg32, const unsigned char *sig64, unsigned char *pubkey, int *pubkeylen, int compressed, int recid)
35
+ attach_function :secp256k1_ecdsa_recover_compact, [:pointer, :pointer, :pointer, :pointer, :int, :int], :int
36
+ RUBY
37
+ end
38
+
39
+ def self.init
40
+ return if @secp256k1_started
41
+
42
+ lib_path = [ ENV['SECP256K1_LIB_PATH'], 'vendor/secp256k1/.libs/libsecp256k1.so' ].find{|f| File.exists?(f.to_s) }
43
+ lib_path = 'libsecp256k1.so' unless lib_path
44
+ ffi_load_functions(lib_path)
45
+
46
+ secp256k1_start(SECP256K1_START_VERIFY | SECP256K1_START_SIGN)
47
+ @secp256k1_started = true
48
+ end
49
+
50
+ def self.generate_key_pair(compressed=true)
51
+ init
52
+
53
+ while true do
54
+ priv_key = SecureRandom.random_bytes(32)
55
+ priv_key_buf = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key)
56
+ break if secp256k1_ec_seckey_verify(priv_key_buf)
57
+ end
58
+
59
+ pub_key_buf = FFI::MemoryPointer.new(:uchar, 65)
60
+ pub_key_size = FFI::MemoryPointer.new(:int)
61
+ result = secp256k1_ec_pubkey_create(pub_key_buf, pub_key_size, priv_key_buf, compressed ? 1 : 0)
62
+ raise "error creating pubkey" unless result
63
+
64
+ [ priv_key, pub_key_buf.read_string(pub_key_size.read_int) ]
65
+ end
66
+
67
+ def self.sign(data, priv_key)
68
+ init
69
+
70
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
71
+ seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
72
+ raise "priv_key invalid" unless secp256k1_ec_seckey_verify(seckey)
73
+
74
+ sig, siglen = FFI::MemoryPointer.new(:uchar, 72), FFI::MemoryPointer.new(:int).write_int(72)
75
+
76
+ while true do
77
+ break if secp256k1_ecdsa_sign(msg32, sig, siglen, seckey, nil, nil)
78
+ end
79
+
80
+ sig.read_string(siglen.read_int)
81
+ end
82
+
83
+ def self.verify(data, signature, pub_key)
84
+ init
85
+
86
+ data_buf = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
87
+ sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
88
+ pub_buf = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key)
89
+
90
+ result = secp256k1_ecdsa_verify(data_buf, sig_buf, sig_buf.size, pub_buf, pub_buf.size)
91
+
92
+ case result
93
+ when 0; false
94
+ when 1; true
95
+ when -1; raise "error invalid pubkey"
96
+ when -2; raise "error invalid signature"
97
+ else ; raise "error invalid result"
98
+ end
99
+ end
100
+
101
+ def self.sign_compact(message, priv_key, compressed=true)
102
+ init
103
+
104
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
105
+ sig64 = FFI::MemoryPointer.new(:uchar, 64)
106
+ rec_id = FFI::MemoryPointer.new(:int)
107
+
108
+ seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
109
+ raise "priv_key invalid" unless secp256k1_ec_seckey_verify(seckey)
110
+
111
+ while true do
112
+ break if secp256k1_ecdsa_sign_compact(msg32, sig64, seckey, nil, nil, rec_id)
113
+ end
114
+
115
+ header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C")
116
+ [ header, sig64.read_string(64) ].join
117
+ end
118
+
119
+ def self.recover_compact(message, signature)
120
+ init
121
+
122
+ return nil if signature.bytesize != 65
123
+
124
+ version = signature.unpack('C')[0]
125
+ return nil if version < 27 || version > 34
126
+
127
+ compressed = version >= 31 ? true : false
128
+ version -= 4 if compressed
129
+
130
+ recid = version - 27
131
+ msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message)
132
+ sig64 = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
133
+ pubkey = FFI::MemoryPointer.new(:uchar, pub_key_len = compressed ? 33 : 65)
134
+ pubkeylen = FFI::MemoryPointer.new(:int).write_int(pub_key_len)
135
+
136
+ result = secp256k1_ecdsa_recover_compact(msg32, sig64, pubkey, pubkeylen, (compressed ? 1 : 0), recid)
137
+
138
+ return nil unless result
139
+
140
+ pubkey.read_bytes(pubkeylen.read_int)
141
+ end
142
+
143
+ end
144
+ end