bitcoin-ruby 0.0.6 → 0.0.7

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 (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