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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +2 -7
- data/COPYING +1 -1
- data/Gemfile +2 -6
- data/Gemfile.lock +34 -0
- data/README.rdoc +16 -68
- data/Rakefile +3 -6
- data/bin/bitcoin_shell +0 -1
- data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
- data/lib/bitcoin.rb +350 -296
- data/lib/bitcoin/builder.rb +3 -1
- data/lib/bitcoin/connection.rb +2 -1
- data/lib/bitcoin/contracthash.rb +76 -0
- data/lib/bitcoin/dogecoin.rb +97 -0
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
- data/lib/bitcoin/ffi/openssl.rb +98 -2
- data/lib/bitcoin/ffi/secp256k1.rb +144 -0
- data/lib/bitcoin/key.rb +12 -2
- data/lib/bitcoin/logger.rb +3 -12
- data/lib/bitcoin/protocol/block.rb +3 -9
- data/lib/bitcoin/protocol/parser.rb +6 -2
- data/lib/bitcoin/protocol/tx.rb +44 -13
- data/lib/bitcoin/protocol/txin.rb +4 -2
- data/lib/bitcoin/protocol/txout.rb +2 -2
- data/lib/bitcoin/script.rb +212 -37
- data/lib/bitcoin/trezor/mnemonic.rb +130 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +32 -3
- data/spec/bitcoin/builder_spec.rb +18 -0
- data/spec/bitcoin/contracthash_spec.rb +45 -0
- data/spec/bitcoin/dogecoin_spec.rb +176 -0
- data/spec/bitcoin/ffi_openssl.rb +45 -0
- data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
- data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
- data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
- data/spec/bitcoin/fixtures/coinbase.json +24 -0
- data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
- data/spec/bitcoin/protocol/block_spec.rb +0 -22
- data/spec/bitcoin/protocol/tx_spec.rb +145 -2
- data/spec/bitcoin/script/script_spec.rb +282 -0
- data/spec/bitcoin/secp256k1_spec.rb +48 -0
- data/spec/bitcoin/spec_helper.rb +0 -51
- data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
- metadata +48 -98
- data/bin/bitcoin_dns_seed +0 -130
- data/bin/bitcoin_gui +0 -80
- data/bin/bitcoin_node +0 -153
- data/bin/bitcoin_node_cli +0 -81
- data/bin/bitcoin_wallet +0 -402
- data/doc/CONFIG.rdoc +0 -66
- data/doc/EXAMPLES.rdoc +0 -13
- data/doc/NAMECOIN.rdoc +0 -34
- data/doc/NODE.rdoc +0 -225
- data/doc/STORAGE.rdoc +0 -33
- data/doc/WALLET.rdoc +0 -102
- data/examples/balance.rb +0 -66
- data/examples/forwarder.rb +0 -73
- data/examples/index_nhash.rb +0 -24
- data/examples/reindex_p2sh_addrs.rb +0 -44
- data/examples/relay_tx.rb +0 -22
- data/examples/verify_tx.rb +0 -57
- data/lib/bitcoin/config.rb +0 -58
- data/lib/bitcoin/gui/addr_view.rb +0 -44
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
- data/lib/bitcoin/gui/conn_view.rb +0 -38
- data/lib/bitcoin/gui/connection.rb +0 -70
- data/lib/bitcoin/gui/em_gtk.rb +0 -30
- data/lib/bitcoin/gui/gui.builder +0 -1643
- data/lib/bitcoin/gui/gui.rb +0 -292
- data/lib/bitcoin/gui/helpers.rb +0 -115
- data/lib/bitcoin/gui/tree_view.rb +0 -84
- data/lib/bitcoin/gui/tx_view.rb +0 -69
- data/lib/bitcoin/namecoin.rb +0 -280
- data/lib/bitcoin/network/command_client.rb +0 -104
- data/lib/bitcoin/network/command_handler.rb +0 -570
- data/lib/bitcoin/network/connection_handler.rb +0 -387
- data/lib/bitcoin/network/node.rb +0 -565
- data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
- data/lib/bitcoin/storage/models.rb +0 -171
- data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
- data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
- data/lib/bitcoin/storage/storage.rb +0 -517
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
- data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
- data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
- data/lib/bitcoin/validation.rb +0 -400
- data/lib/bitcoin/wallet/coinselector.rb +0 -33
- data/lib/bitcoin/wallet/keygenerator.rb +0 -77
- data/lib/bitcoin/wallet/keystore.rb +0 -207
- data/lib/bitcoin/wallet/txdp.rb +0 -118
- data/lib/bitcoin/wallet/wallet.rb +0 -281
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
- data/spec/bitcoin/namecoin_spec.rb +0 -182
- data/spec/bitcoin/node/command_api_spec.rb +0 -663
- data/spec/bitcoin/storage/models_spec.rb +0 -104
- data/spec/bitcoin/storage/reorg_spec.rb +0 -236
- data/spec/bitcoin/storage/storage_spec.rb +0 -387
- data/spec/bitcoin/storage/validation_spec.rb +0 -300
- data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
- data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
- data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
- data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
- data/spec/bitcoin/wallet/wallet_spec.rb +0 -238
data/lib/bitcoin/builder.rb
CHANGED
@@ -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")
|
data/lib/bitcoin/connection.rb
CHANGED
@@ -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 =
|
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
|
data/lib/bitcoin/ffi/openssl.rb
CHANGED
@@ -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
|
-
|
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], :
|
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
|