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