bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
class Bitcoin::Wallet::TxDP
|
2
|
+
|
3
|
+
attr_accessor :id, :tx, :inputs
|
4
|
+
def initialize tx = []
|
5
|
+
@id = Bitcoin.int_to_base58(rand(1e14))
|
6
|
+
@tx = tx
|
7
|
+
@inputs = []
|
8
|
+
return unless tx.any?
|
9
|
+
@tx[0].in.each_with_index do |input, i|
|
10
|
+
prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
|
11
|
+
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
12
|
+
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
13
|
+
prev_out = prev_tx.out[input.prev_out_index]
|
14
|
+
raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
|
15
|
+
out_script = Bitcoin::Script.new(prev_out.pk_script)
|
16
|
+
out_script.get_addresses.each do |addr|
|
17
|
+
add_sig(i, prev_out.value, addr, input.script_sig)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_sig(in_idx, value, addr, sig)
|
23
|
+
sig = sig ? [[addr, sig.unpack("H*")[0]]] : []
|
24
|
+
@inputs[in_idx] = [value, sig]
|
25
|
+
end
|
26
|
+
|
27
|
+
def sign_inputs
|
28
|
+
@inputs.each_with_index do |txin, i|
|
29
|
+
input = @tx[0].in[i]
|
30
|
+
prev_out_hash = input.prev_out.reverse.unpack("H*")[0]
|
31
|
+
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
32
|
+
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
33
|
+
prev_out = prev_tx.out[input.prev_out_index]
|
34
|
+
raise "prev out ##{input.prev_out_index} not found in tx #{@tx.hash}" unless prev_out
|
35
|
+
out_script = Bitcoin::Script.new(prev_out.pk_script)
|
36
|
+
out_script.get_addresses.each do |addr|
|
37
|
+
sig = yield(@tx[0], prev_tx, i, addr)
|
38
|
+
if sig
|
39
|
+
@inputs[i][1] ||= []
|
40
|
+
@inputs[i][1] << [addr, sig]
|
41
|
+
break
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize
|
48
|
+
lines = []
|
49
|
+
lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
|
50
|
+
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
|
51
|
+
lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
|
52
|
+
tx = @tx.map(&:to_payload).join.unpack("H*")[0]
|
53
|
+
tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
54
|
+
lines << tx_str.strip
|
55
|
+
@inputs.each_with_index do |input, idx|
|
56
|
+
lines << "_TXINPUT_#{idx.to_s.rjust(2, '0')}_#{"%.8f" % (input[0].to_f / 1e8)}"
|
57
|
+
next unless input[1]
|
58
|
+
input[1].each do |sig|
|
59
|
+
size = [sig[1]].pack("H*").bytesize
|
60
|
+
size = [size].pack("C").ljust(2, "\x00").reverse.unpack("H*")[0]
|
61
|
+
lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
|
62
|
+
sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
63
|
+
lines << sig_str.strip
|
64
|
+
end
|
65
|
+
end
|
66
|
+
lines << "-------END-TRANSACTION-#{@id}".ljust(80, '-')
|
67
|
+
lines.join("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse str
|
71
|
+
str.match(/-+BEGIN-TRANSACTION-(.*?)-+$(.*?)END-TRANSACTION-#{$1}/m) do |m|
|
72
|
+
_, id, content = *m
|
73
|
+
txdist, *inputs = content.split(/_TXINPUT_/)
|
74
|
+
@id = id
|
75
|
+
@txdist = parse_txdist(txdist)
|
76
|
+
inputs.each {|input| parse_input(input) }
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def parse_txdist txdist
|
82
|
+
_, magic, txdp_id, size, serialized_tx = *txdist.match(/_TXDIST_(.*?)_(.*?)_(.*?)$(.*)/m)
|
83
|
+
raise "Wrong network magic" unless [magic].pack("H*") == Bitcoin.network[:magic_head]
|
84
|
+
tx = Bitcoin::P::Tx.new(nil)
|
85
|
+
rest = [serialized_tx.gsub!("\n", '')].pack("H*")
|
86
|
+
while rest = tx.parse_data(rest)
|
87
|
+
@tx << tx
|
88
|
+
break if rest == true
|
89
|
+
tx = Bitcoin::P::Tx.new(nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_input input
|
94
|
+
m = input.match(/(\d+)_(\d+\.\d+)\n(.*)/m)
|
95
|
+
_, idx, value, sigs = *m
|
96
|
+
value = (value.sub('.','').to_i)
|
97
|
+
sigs = parse_sigs(sigs)
|
98
|
+
@inputs[idx.to_i] = [value, sigs]
|
99
|
+
end
|
100
|
+
|
101
|
+
def parse_sigs sigs
|
102
|
+
return nil unless sigs["_SIG_"]
|
103
|
+
sigs = sigs.split("_SIG_").map do |s|
|
104
|
+
if s == ""
|
105
|
+
nil
|
106
|
+
else
|
107
|
+
m = s.match(/(.*?)_(\d+)_(.*?)\n(.*)/m)
|
108
|
+
[$1, $4.gsub("\n", '').gsub('-', '')]
|
109
|
+
end
|
110
|
+
end.compact
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.parse str
|
114
|
+
new.parse str
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
Bitcoin.require_dependency :eventmachine, exit: false
|
2
|
+
|
3
|
+
# The wallet implementation consists of several concepts:
|
4
|
+
# Wallet:: the high-level API used to manage a wallet
|
5
|
+
# SimpleKeyStore:: key store to manage keys/addresses/labels
|
6
|
+
# SimpleCoinSelector:: coin selector to find unspent outputs to use when creating tx
|
7
|
+
module Bitcoin::Wallet
|
8
|
+
|
9
|
+
# A wallet manages a set of keys (through a +keystore+), can
|
10
|
+
# list transactions/balances for those keys (using a Storage backend for
|
11
|
+
# blockchain data).
|
12
|
+
# It can also create transactions with various kinds of outputs and
|
13
|
+
# connect with a CommandClient to relay those transactions through a node.
|
14
|
+
#
|
15
|
+
# TODO: new tx notification, keygenerators, keystore cleanup
|
16
|
+
class Wallet
|
17
|
+
|
18
|
+
include Bitcoin::Builder
|
19
|
+
|
20
|
+
# the keystore (SimpleKeyStore) managing keys/addresses/labels
|
21
|
+
attr_reader :keystore
|
22
|
+
|
23
|
+
# the Storage which holds the blockchain
|
24
|
+
attr_reader :storage
|
25
|
+
|
26
|
+
# open wallet with given +storage+ Storage backend, +keystore+ SimpleKeyStore
|
27
|
+
# and +selector+ SimpleCoinSelector
|
28
|
+
def initialize storage, keystore, selector
|
29
|
+
@storage = storage
|
30
|
+
@keystore = keystore
|
31
|
+
@selector = selector
|
32
|
+
@callbacks = {}
|
33
|
+
connect_node if defined?(EM)
|
34
|
+
end
|
35
|
+
|
36
|
+
def connect_node
|
37
|
+
return unless EM.reactor_running?
|
38
|
+
host, port = "127.0.0.1", 9999
|
39
|
+
@node = Bitcoin::Network::CommandClient.connect(host, port, self, @storage) do
|
40
|
+
on_connected { request :monitor, "block", "tx" }
|
41
|
+
on_block do |block, depth|
|
42
|
+
EM.defer do
|
43
|
+
block['tx'].each do |tx|
|
44
|
+
relevant, tx = @args[0].check_tx(tx['hash'])
|
45
|
+
@args[0].callback(:tx, :confirmed, tx) if relevant
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
on_tx do |response|
|
51
|
+
EM.defer do
|
52
|
+
relevant, tx = @args[0].check_tx(response['hash'])
|
53
|
+
@args[0].callback(:tx, relevant, tx) if relevant
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_tx tx_hash
|
60
|
+
relevant = false
|
61
|
+
addrs = addrs
|
62
|
+
tx = @storage.get_tx(tx_hash)
|
63
|
+
unless tx
|
64
|
+
log.warn { "Received tx #{response['hash']} but not found in storage" }
|
65
|
+
binding.pry
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
addrs = @keystore.keys.map {|k| k[:addr] }
|
69
|
+
tx.out.each do |txout|
|
70
|
+
return :incoming, tx if (txout.get_addresses & addrs).any?
|
71
|
+
end
|
72
|
+
tx.in.each do |txin|
|
73
|
+
next unless prev_out = txin.get_prev_out
|
74
|
+
return :outgoing, tx if (prev_out.get_addresses & addrs).any?
|
75
|
+
end
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
def log
|
80
|
+
return @log if @log
|
81
|
+
@log = Bitcoin::Logger.create("wallet")
|
82
|
+
@log.level = :debug
|
83
|
+
@log
|
84
|
+
end
|
85
|
+
|
86
|
+
# call the callback specified by +name+ passing in +args+
|
87
|
+
def callback name, *args
|
88
|
+
cb = @callbacks[name.to_sym]
|
89
|
+
return unless cb
|
90
|
+
log.debug { "callback: #{name}" }
|
91
|
+
cb.call(*args)
|
92
|
+
end
|
93
|
+
|
94
|
+
# register callback methods
|
95
|
+
def method_missing(name, *args, &block)
|
96
|
+
if name =~ /^on_/
|
97
|
+
@callbacks[name.to_s.split("on_")[1].to_sym] = block
|
98
|
+
log.debug { "callback #{name} registered" }
|
99
|
+
else
|
100
|
+
super(name, *args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# get all Storage::Models::TxOut concerning any address from this wallet
|
105
|
+
def get_txouts(unconfirmed = false)
|
106
|
+
txouts = @keystore.keys.map {|k|
|
107
|
+
@storage.get_txouts_for_address(k[:addr])}.flatten.uniq
|
108
|
+
unconfirmed ? txouts : txouts.select {|o| !!o.get_tx.get_block}
|
109
|
+
end
|
110
|
+
|
111
|
+
# get total balance for all addresses in this wallet
|
112
|
+
def get_balance(unconfirmed = false)
|
113
|
+
values = get_txouts(unconfirmed).select{|o| !o.get_next_in}.map(&:value)
|
114
|
+
|
115
|
+
([0] + values).inject(:+)
|
116
|
+
end
|
117
|
+
|
118
|
+
# list all addresses in this wallet
|
119
|
+
def addrs
|
120
|
+
@keystore.keys.map{|k| k[:addr]}
|
121
|
+
end
|
122
|
+
|
123
|
+
# add +key+ to wallet
|
124
|
+
def add_key key
|
125
|
+
@keystore.add_key(key)
|
126
|
+
end
|
127
|
+
|
128
|
+
# set label for key +old+ to +new+
|
129
|
+
def label old, new
|
130
|
+
@keystore.label_key(old, new)
|
131
|
+
end
|
132
|
+
|
133
|
+
# set +flag+ for key +name+ to +value+
|
134
|
+
def flag name, flag, value
|
135
|
+
@keystore.flag_key(name, flag, value)
|
136
|
+
end
|
137
|
+
|
138
|
+
# list all keys along with their balances
|
139
|
+
def list
|
140
|
+
@keystore.keys.map do |key|
|
141
|
+
[key, @storage.get_balance(Bitcoin.hash160_from_address(key[:addr]))]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# create new key and return its address
|
146
|
+
def get_new_addr
|
147
|
+
@keystore.new_key.addr
|
148
|
+
end
|
149
|
+
|
150
|
+
# get SimpleCoinSelector with txouts for this wallet
|
151
|
+
def get_selector
|
152
|
+
@selector.new(get_txouts)
|
153
|
+
end
|
154
|
+
|
155
|
+
# create a transaction with given +outputs+, +fee+ and +change_policy+.
|
156
|
+
#
|
157
|
+
# outputs are of the form
|
158
|
+
# [<type>, <recipients>, <value>]
|
159
|
+
# examples:
|
160
|
+
# [:address, <addr>, <value>]
|
161
|
+
# [:multisig, 2, 3, <addr>, <addr>, <addr>, <value>]
|
162
|
+
#
|
163
|
+
# inputs are selected automatically by the SimpleCoinSelector.
|
164
|
+
#
|
165
|
+
# change_policy controls where the change_output is spent to.
|
166
|
+
# see #get_change_addr
|
167
|
+
def new_tx outputs, fee = 0, change_policy = :back
|
168
|
+
output_value = outputs.map{|o|o[-1]}.inject(:+)
|
169
|
+
|
170
|
+
prev_outs = get_selector.select(output_value)
|
171
|
+
return nil if !prev_outs
|
172
|
+
|
173
|
+
input_value = prev_outs.map(&:value).inject(:+)
|
174
|
+
return nil unless input_value >= (output_value + fee)
|
175
|
+
|
176
|
+
tx = tx do |t|
|
177
|
+
outputs.each do |type, *addrs, value|
|
178
|
+
t.output do |o|
|
179
|
+
o.value value
|
180
|
+
o.script do |s|
|
181
|
+
s.type type
|
182
|
+
s.recipient *addrs
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
change_value = input_value - output_value - fee
|
188
|
+
if change_value > 0
|
189
|
+
change_addr = get_change_addr(change_policy, prev_outs.sample.get_address)
|
190
|
+
t.output do |o|
|
191
|
+
o.value change_value
|
192
|
+
o.script do |s|
|
193
|
+
s.type :address
|
194
|
+
s.recipient change_addr
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
prev_outs.each_with_index do |prev_out, idx|
|
200
|
+
t.input do |i|
|
201
|
+
prev_tx = prev_out.get_tx
|
202
|
+
i.prev_out prev_tx
|
203
|
+
i.prev_out_index prev_tx.out.index(prev_out)
|
204
|
+
pk_script = Bitcoin::Script.new(prev_out.pk_script)
|
205
|
+
if pk_script.is_pubkey? || pk_script.is_hash160?
|
206
|
+
i.signature_key @keystore.key(prev_out.get_address)[:key]
|
207
|
+
elsif pk_script.is_multisig?
|
208
|
+
raise "multisig not implemented"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
# TODO: spend multisig outputs again
|
214
|
+
# TODO: verify signatures
|
215
|
+
Bitcoin::Protocol::Tx.new(tx.to_payload)
|
216
|
+
end
|
217
|
+
|
218
|
+
protected
|
219
|
+
|
220
|
+
# get address to send change output to.
|
221
|
+
# +policy+ controls which address is chosen:
|
222
|
+
# first:: send to the first key in the wallets keystore
|
223
|
+
# random:: send to a random key from the wallets keystore
|
224
|
+
# new:: send to a new key generated in the wallets keystore
|
225
|
+
# back:: send to the address given as +in_addr+
|
226
|
+
def get_change_addr(policy, in_addr)
|
227
|
+
case policy
|
228
|
+
when :first
|
229
|
+
@keystore.keys[0].addr
|
230
|
+
when :random
|
231
|
+
@keystore.keys.sample.addr
|
232
|
+
when :new
|
233
|
+
@keystore.new_key.addr
|
234
|
+
when :back
|
235
|
+
in_addr
|
236
|
+
else
|
237
|
+
policy
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
@@ -0,0 +1,472 @@
|
|
1
|
+
require_relative 'spec_helper.rb'
|
2
|
+
require 'bitcoin'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'Bitcoin Address/Hash160/PubKey' do
|
6
|
+
|
7
|
+
it 'bitcoin-hash160 from public key' do
|
8
|
+
# 65 bytes (8 bit version + 512 bits) pubkey in hex (130 bytes)
|
9
|
+
pubkey = "04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
|
10
|
+
Bitcoin.hash160(pubkey).should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'bitcoin-address from bitcoin-hash160' do
|
14
|
+
# 20 bytes (160 bit) hash160 in hex (40 bytes)
|
15
|
+
|
16
|
+
Bitcoin::network = :testnet
|
17
|
+
Bitcoin.hash160_to_address("62e907b15cbf27d5425399ebf6f0fb50ebb88f18")
|
18
|
+
.should == "mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt"
|
19
|
+
|
20
|
+
Bitcoin::network = :bitcoin
|
21
|
+
Bitcoin.hash160_to_address("62e907b15cbf27d5425399ebf6f0fb50ebb88f18")
|
22
|
+
.should == "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'bitcoin-hash160 from bitcoin-address' do
|
26
|
+
Bitcoin::network = :testnet
|
27
|
+
Bitcoin.hash160_from_address("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt")
|
28
|
+
.should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
|
29
|
+
Bitcoin.hash160_from_address("totally-invalid").should == nil
|
30
|
+
|
31
|
+
Bitcoin::network = :bitcoin
|
32
|
+
Bitcoin.hash160_from_address("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
|
33
|
+
.should == "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
|
34
|
+
|
35
|
+
Bitcoin.hash160_from_address("11ofrrzv87Ls97jN4TUetfQp4gEsUSL7A")
|
36
|
+
.should == "0026f5494b39ea04b7bcb05e583acf3b0102d61f"
|
37
|
+
Bitcoin.hash160_from_address("11122RGUQSszAsTpptd2h8sdyYGR6nKs6f")
|
38
|
+
.should == "0000daec8d6f05e949710f202c4f73258aa7791e"
|
39
|
+
Bitcoin.hash160_from_address("11119uLoMQCBHmKevdsFKHMaUoyrwLa9Y")
|
40
|
+
.should == "00000090c66372823859c935149e2e32d276a1e6"
|
41
|
+
Bitcoin.hash160_from_address("1111136sgL8UNSTVL9ize2uGFPxFDGwFp")
|
42
|
+
.should == "0000000096d3ad65d030a36e2c23f7fdd5dfcadb"
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should survive rounds of hash160 <-> address' do
|
46
|
+
hex = "62e907b15cbf27d5425399ebf6f0fb50ebb88f18"
|
47
|
+
|
48
|
+
Bitcoin::network = :testnet
|
49
|
+
addr = "mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt"
|
50
|
+
Bitcoin.hash160_from_address(Bitcoin.hash160_to_address(hex)).should == hex
|
51
|
+
Bitcoin.hash160_to_address(Bitcoin.hash160_from_address(addr)).should == addr
|
52
|
+
|
53
|
+
Bitcoin::network = :bitcoin
|
54
|
+
addr = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
55
|
+
Bitcoin.hash160_from_address(Bitcoin.hash160_to_address(hex)).should == hex
|
56
|
+
Bitcoin.hash160_to_address(Bitcoin.hash160_from_address(addr)).should == addr
|
57
|
+
end
|
58
|
+
|
59
|
+
it '#address_checksum?' do
|
60
|
+
Bitcoin::network = :testnet
|
61
|
+
Bitcoin.address_checksum?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
|
62
|
+
Bitcoin.address_checksum?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == true
|
63
|
+
Bitcoin.address_checksum?("f0f0f0").should == false
|
64
|
+
|
65
|
+
Bitcoin::network = :bitcoin
|
66
|
+
Bitcoin.address_checksum?("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa").should == true
|
67
|
+
Bitcoin.address_checksum?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
|
68
|
+
Bitcoin.address_checksum?("f0f0f0").should == false
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'validate bitcoin-address' do
|
72
|
+
|
73
|
+
Bitcoin::network = :testnet
|
74
|
+
|
75
|
+
Bitcoin.valid_address?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == true
|
76
|
+
Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == false
|
77
|
+
Bitcoin.valid_address?("1moYFpRM4LkTV4Ho5eCxiEPB2bSm3AsJNGj").should == false
|
78
|
+
Bitcoin.valid_address?("f0f0f0").should == false
|
79
|
+
|
80
|
+
Bitcoin::network = :bitcoin
|
81
|
+
|
82
|
+
Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == true
|
83
|
+
Bitcoin.valid_address?("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == false
|
84
|
+
Bitcoin.valid_address?("2D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == false
|
85
|
+
Bitcoin.valid_address?("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cX").should == false
|
86
|
+
Bitcoin.valid_address?("1moYFpRM4LkTV4Ho5eCxiEPB2bSm3AsJNGj").should == false
|
87
|
+
|
88
|
+
Bitcoin.valid_address?("1ZQxJYBRmbb2rDNYPhd96x3eMbNnPD98q").should == true
|
89
|
+
Bitcoin.valid_address?("12KhCL8nGK3Luy7ehU3AxPs1mTocdessLM").should == true
|
90
|
+
Bitcoin.valid_address?("1AnNQgfaGgSKejzR6km74tyQPDGwZBBVT").should == true
|
91
|
+
Bitcoin.valid_address?("f0f0f0").should == false
|
92
|
+
|
93
|
+
|
94
|
+
Bitcoin.base58_to_int("114EpVhtPpJQKti8HiH2fvXZFPiPkgDZrE").should \
|
95
|
+
== 15016857106811133404017207799481956647721349092596212439
|
96
|
+
|
97
|
+
Bitcoin.network, success = :testnet, true
|
98
|
+
400.times{
|
99
|
+
addr = Bitcoin.generate_address
|
100
|
+
success = false if Bitcoin.hash160_from_address(addr[0]) != addr[-1]
|
101
|
+
success = false if Bitcoin.hash160_to_address(addr[-1]) != addr[0]
|
102
|
+
success = false if Bitcoin.valid_address?(addr[0]) != true
|
103
|
+
}
|
104
|
+
success.should == true
|
105
|
+
|
106
|
+
Bitcoin.network, success = :bitcoin, true
|
107
|
+
400.times{
|
108
|
+
addr = Bitcoin.generate_address
|
109
|
+
success = false if Bitcoin.hash160_from_address(addr[0]) != addr[-1]
|
110
|
+
success = false if Bitcoin.hash160_to_address(addr[-1]) != addr[0]
|
111
|
+
success = false if Bitcoin.valid_address?(addr[0]) != true
|
112
|
+
}
|
113
|
+
success.should == true
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'validate p2sh address' do
|
117
|
+
Bitcoin.network = :testnet
|
118
|
+
Bitcoin.valid_address?("2MyLngQnhzjzatKsB7XfHYoP9e2XUXSiBMM").should == true
|
119
|
+
Bitcoin.network = :bitcoin
|
120
|
+
Bitcoin.valid_address?("3CkxTG25waxsmd13FFgRChPuGYba3ar36B").should == true
|
121
|
+
end
|
122
|
+
|
123
|
+
it '#address_type' do
|
124
|
+
Bitcoin.network = :testnet
|
125
|
+
Bitcoin.address_type("2MyLngQnhzjzatKsB7XfHYoP9e2XUXSiBMM").should == :p2sh
|
126
|
+
Bitcoin.address_type("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == :hash160
|
127
|
+
Bitcoin.address_type("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == nil
|
128
|
+
Bitcoin.network = :bitcoin
|
129
|
+
Bitcoin.address_type("3CkxTG25waxsmd13FFgRChPuGYba3ar36B").should == :p2sh
|
130
|
+
Bitcoin.address_type("1D3KpY5kXnYhTbdCbZ9kXb2ZY7ZapD85cW").should == :hash160
|
131
|
+
Bitcoin.address_type("mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt").should == nil
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'Bitcoin#checksum' do
|
135
|
+
Bitcoin.checksum("0062e907b15cbf27d5425399ebf6f0fb50ebb88f18").should == "c29b7d93"
|
136
|
+
end
|
137
|
+
|
138
|
+
it '#bitcoin_mrkl' do
|
139
|
+
# block 170 is the first block that has a transaction.
|
140
|
+
# hash 00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
|
141
|
+
a = "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082"
|
142
|
+
b = "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"
|
143
|
+
c = "7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
|
144
|
+
|
145
|
+
Bitcoin.bitcoin_hash(b + a) .should == c
|
146
|
+
Bitcoin.bitcoin_mrkl(a , b) .should == c
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'mrkl_tree from transaction-hashes' do
|
150
|
+
|
151
|
+
# mrkl tree for block 170
|
152
|
+
mrkl_tree = [
|
153
|
+
"b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082",# tx 1
|
154
|
+
"f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16",# tx 2
|
155
|
+
"7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
|
156
|
+
]
|
157
|
+
Bitcoin.hash_mrkl_tree(mrkl_tree[0...2]).should == mrkl_tree
|
158
|
+
|
159
|
+
|
160
|
+
mrkl_tree = [
|
161
|
+
"4fa598026d9be1ca0c2bff1b531e566b7ea7f90b72e75fda0c1795bc2dfa375c",
|
162
|
+
"186640daf908156e2616790d7c816235b0c43f668c3c38351b348c08ca44d457",
|
163
|
+
"ef3928700309b4deceac9a992a19a7481b4e520cbc0b1ab74e2645eee39c8da0",
|
164
|
+
|
165
|
+
"688c53517f62f7a65c0e87519c18a4de98f2ccafbf389b269d0bb867f88d166a",
|
166
|
+
"01889506f7fe9210045f588361881e2d16a034a62bc48ebd7b6b0a3edeaf5a6d",
|
167
|
+
"74f3a7df861d6a58957b84a3e425a8cf57e1e2e3a3def046dd200baeb8714f00"
|
168
|
+
]
|
169
|
+
|
170
|
+
Bitcoin.hash_mrkl_tree( mrkl_tree[0...3] ).should == mrkl_tree
|
171
|
+
|
172
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[3]
|
173
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[2] ).should == mrkl_tree[4]
|
174
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[3], mrkl_tree[4] ).should == mrkl_tree[5]
|
175
|
+
|
176
|
+
|
177
|
+
mrkl_tree = [
|
178
|
+
"349f717b6630e1f305f95964a2d94117dacca76e0b715d4d7a5657698ec96c6c", # 0
|
179
|
+
"7f44a84349200473455bcfc05ee68036e23993a6f58dce3f6a7faab46a754440", # 1
|
180
|
+
"6b3ba3fdfb7eeb6c2e5fd0e36e5bb4634da294521f7b1b808286c214981f9b17", # 2
|
181
|
+
"0467b41043d654ba3dc3940cbefec0eb38feed6e7085a8e825e4f782eccb48e3", # 3
|
182
|
+
"83d0231624f7a7d5c37557461ac0d09a8ad7a1f4ab673dd697acb275d4b114de", # 4
|
183
|
+
"074970aaa98db7d00e6f97a719fe85e9c7f51b75fa5a9a92218d568ccc2b21fe", # 5
|
184
|
+
|
185
|
+
"c1b6d2a416de6b63e42c1b50f229911bfb07f816e0795bb86ff7dcf0463ab0df", # 6
|
186
|
+
"751bcfd8acc10792f42050dca5b852f7a2fcd5300897d05907a98473f59a5650", # 7
|
187
|
+
"7abf551000d942efb93afc2d6174dc1bb7d41e8ea5fd76724685000734f1d77b", # 8
|
188
|
+
|
189
|
+
"e268927aa50d44de5365c11a2e402478767b7b98856a21a0715f9db65709aabb", # 9
|
190
|
+
"ed5ecc6b0e2fdd81be806599d6509d166e26849049e60e8d8b398641282b1e5a", # 10
|
191
|
+
|
192
|
+
"72e0e61880cc5fc9c7b5990c2d40b22eba783391b72807d2d5349fb55875c015" # 11
|
193
|
+
]
|
194
|
+
|
195
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[6]
|
196
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[3] ).should == mrkl_tree[7]
|
197
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[4], mrkl_tree[5] ).should == mrkl_tree[8]
|
198
|
+
|
199
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[6], mrkl_tree[7] ).should == mrkl_tree[9]
|
200
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[8], mrkl_tree[8] ).should == mrkl_tree[10]
|
201
|
+
|
202
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[9], mrkl_tree[10] ).should == mrkl_tree[11]
|
203
|
+
|
204
|
+
Bitcoin.hash_mrkl_tree(mrkl_tree[0...6]).should == mrkl_tree
|
205
|
+
|
206
|
+
|
207
|
+
mrkl_tree = [
|
208
|
+
"627c859b5af6d537930fd16148eb0597542bea543f65fc2b0e5f188b5a458529", # 0
|
209
|
+
"d00b90525820a74f30ce26488db7f77c6ee9577e650568a051edd8560bbf83a1", # 1
|
210
|
+
"706b8ac1a433bc28385450626e12c1c7806032dc8b7e12221f417c5f22059d70", # 2
|
211
|
+
"10107ad569400a5f9621498e410845e6db0551671a2cafcf4358bd7867c6bc14", # 3
|
212
|
+
"ac6b08a363aedd5e58177c7f68bb213403ef78d24be0012c06b3483a9e2461fe", # 4
|
213
|
+
"d3074f5b33a44d9961f40eadf250cdc1425f7975012fccb6b06abc5202c53f4b", # 5
|
214
|
+
"3270c13599266d3a8da90a85a07fc003c58a8ff2938988356783b6261be335a6", # 6
|
215
|
+
"e097c3e2e3a07385628ac5a5a775e8a5e22dda3732bee32ae65b1430d080fc32", # 7
|
216
|
+
"c335a3963e8d89a9f46b94158d33f9b0dee25e776cba91be5eda44898bd31a78", # 8
|
217
|
+
|
218
|
+
"eb52315c6b26f72fa58ed95cd4886f1aa047ecd8f34ed8a367f59854f20733d8", # 9
|
219
|
+
"f97bf49e42e1732c0b515ecbac7cffc29c8c75c20c6783ad48b09d348fe0b6cf", # 10
|
220
|
+
"fc655dfc41eed4e2ea4cfe33ebb6bf593eb256bf86c17802fd03567668d0bf71", # 11
|
221
|
+
"06c4abf5dae15d5fa3632e7e5f82f05e7afbbfd495ea8015c6094764d868654c", # 12
|
222
|
+
"31bbd6e4523aeaaaf75b6ea4ef63d0fe3dba66fb719f4a4232891a3b58ad5cec", # 13
|
223
|
+
|
224
|
+
"0c728622b795e14ac40c4aa13cb732a0407b2b85c9108c6f06c083220bb6d65d", # 14
|
225
|
+
"f83f6ea46edcbeaa738ce4701bf48412a10c4b1a9f109efe44bc8fe5cb6b0017", # 15
|
226
|
+
"5e6168778c8407ace3ae9901a2f5197d6f21a6634ae0af639e52d14c39b13e02", # 16
|
227
|
+
|
228
|
+
"b92a7980b8a8a64f896027c0de732298d04ca56bea66c18cf97983037486e456", # 17
|
229
|
+
"2a2fd5e450b6ec31ccbe9d827f2e903714eb69c351300b1e76c587aef60e000c", # 18
|
230
|
+
|
231
|
+
"9ed561bb49c47648e0250cb074721d94cba84ed1e083f1e57c29eca78e36d73d" # 19
|
232
|
+
]
|
233
|
+
|
234
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[0], mrkl_tree[1] ).should == mrkl_tree[9]
|
235
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[2], mrkl_tree[3] ).should == mrkl_tree[10]
|
236
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[4], mrkl_tree[5] ).should == mrkl_tree[11]
|
237
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[6], mrkl_tree[7] ).should == mrkl_tree[12]
|
238
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[8], mrkl_tree[8] ).should == mrkl_tree[13]
|
239
|
+
|
240
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[9], mrkl_tree[10] ).should == mrkl_tree[14]
|
241
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[11], mrkl_tree[12] ).should == mrkl_tree[15]
|
242
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[13], mrkl_tree[13] ).should == mrkl_tree[16]
|
243
|
+
|
244
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[14], mrkl_tree[15] ).should == mrkl_tree[17]
|
245
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[16], mrkl_tree[16] ).should == mrkl_tree[18]
|
246
|
+
|
247
|
+
Bitcoin.bitcoin_mrkl( mrkl_tree[17], mrkl_tree[18] ).should == mrkl_tree[19]
|
248
|
+
|
249
|
+
Bitcoin.hash_mrkl_tree(mrkl_tree[0...9]).should == mrkl_tree
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'nonce compact bits to bignum hex' do
|
253
|
+
Bitcoin.decode_compact_bits( "1b00b5ac".to_i(16) ).index(/[^0]/).should == 12
|
254
|
+
Bitcoin.decode_compact_bits( "1b00b5ac".to_i(16) ).to_i(16).should ==
|
255
|
+
"000000000000b5ac000000000000000000000000000000000000000000000000".to_i(16)
|
256
|
+
|
257
|
+
|
258
|
+
target = 453031340
|
259
|
+
Bitcoin.decode_compact_bits( target ).should ==
|
260
|
+
"000000000000b5ac000000000000000000000000000000000000000000000000"
|
261
|
+
Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
|
262
|
+
|
263
|
+
target = 486604799
|
264
|
+
Bitcoin.decode_compact_bits( target ).should ==
|
265
|
+
"00000000ffff0000000000000000000000000000000000000000000000000000"
|
266
|
+
Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
|
267
|
+
|
268
|
+
target = 476399191 # from block 40,320
|
269
|
+
Bitcoin.decode_compact_bits(target).should ==
|
270
|
+
"0000000065465700000000000000000000000000000000000000000000000000"
|
271
|
+
Bitcoin.encode_compact_bits( Bitcoin.decode_compact_bits( target ) ).should == target
|
272
|
+
end
|
273
|
+
|
274
|
+
it '#block_hash' do
|
275
|
+
# block 0 n_tx: 1
|
276
|
+
prev_block="0000000000000000000000000000000000000000000000000000000000000000"
|
277
|
+
mrkl_root ="4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
|
278
|
+
time, bits, nonce, ver = 1231006505, 486604799, 2083236893, 1
|
279
|
+
|
280
|
+
Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
|
281
|
+
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
282
|
+
|
283
|
+
|
284
|
+
# block 1 n_tx: 1
|
285
|
+
prev_block="000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
286
|
+
mrkl_root ="0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
|
287
|
+
time, bits, nonce, ver = 1231469665, 486604799, 2573394689, 1
|
288
|
+
|
289
|
+
Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
|
290
|
+
"00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"
|
291
|
+
|
292
|
+
# .. only n_tx: 1
|
293
|
+
|
294
|
+
# block 169 n_tx: 1
|
295
|
+
prev_block="00000000567e95797f93675ac23683ae3787b183bb36859c18d9220f3fa66a69"
|
296
|
+
mrkl_root ="d7b9a9da6becbf47494c27e913241e5a2b85c5cceba4b2f0d8305e0a87b92d98"
|
297
|
+
time, bits, nonce, ver = 1231730523, 486604799, 3718213931, 1
|
298
|
+
|
299
|
+
Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
|
300
|
+
"000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55"
|
301
|
+
|
302
|
+
|
303
|
+
# block 170 n_tx: 2
|
304
|
+
prev_block="000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55"
|
305
|
+
mrkl_root ="7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff"
|
306
|
+
time, bits, nonce, ver = 1231731025, 486604799, 1889418792, 1
|
307
|
+
|
308
|
+
Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
|
309
|
+
"00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee"
|
310
|
+
|
311
|
+
|
312
|
+
# block 171 n_tx: 1
|
313
|
+
prev_block="00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee"
|
314
|
+
mrkl_root ="d5f2d21453a6f0e67b5c42959c9700853e4c4d46fa7519d1cc58e77369c893f2"
|
315
|
+
time, bits, nonce, ver = 1231731401, 486604799, 653436935, 1
|
316
|
+
Bitcoin.block_hash(prev_block, mrkl_root, time, bits, nonce, ver).should ==
|
317
|
+
"00000000c9ec538cab7f38ef9c67a95742f56ab07b0a37c5be6b02808dbfb4e0"
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'generates openssl-secp256k1 private/public keypair' do
|
321
|
+
private_key, public_key = Bitcoin.generate_key
|
322
|
+
|
323
|
+
private_key.size .should == 64 # bytes in hex
|
324
|
+
public_key.size .should == 130 # bytes in hex
|
325
|
+
|
326
|
+
key = Bitcoin.open_key(private_key, public_key)
|
327
|
+
Bitcoin.inspect_key( key ).should == [ private_key, public_key ]
|
328
|
+
end
|
329
|
+
|
330
|
+
begin
|
331
|
+
Bitcoin::OpenSSL_EC
|
332
|
+
it 'opens key from private key and resolves public key' do
|
333
|
+
private_key, public_key = Bitcoin.generate_key
|
334
|
+
key = Bitcoin.open_key(private_key)
|
335
|
+
[ key.private_key_hex, key.public_key_hex ].should == [ private_key, public_key ]
|
336
|
+
end
|
337
|
+
|
338
|
+
it 'extract private key from uncompressed DER format' do
|
339
|
+
der = "308201130201010420a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6cd417b808db1ccd0aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a14403420004768cfc6c44b927b0e69e9dd343e96132f7cd1d360d8cb8d65c83d89d7beaceadfd19918e076606a099344156acdb026b1065a958e39f098cfd0a34dd976291d6"
|
340
|
+
|
341
|
+
Bitcoin::OpenSSL_EC.der_to_private_key(der).should == "a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6cd417b808db1ccd0a"
|
342
|
+
end
|
343
|
+
rescue LoadError
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'generates new bitcoin-address' do
|
347
|
+
address, private_key, public_key, hash160 = Bitcoin.generate_address
|
348
|
+
|
349
|
+
private_key.size .should == 64 # bytes in hex
|
350
|
+
public_key.size .should == 130 # bytes in hex
|
351
|
+
#Bitcoin.valid_address?(address).should == true # fix/extend
|
352
|
+
Bitcoin.hash160_to_address(Bitcoin.hash160(public_key)).should == address
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'encodes and decodes base58' do
|
356
|
+
# fixtures from: https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp
|
357
|
+
bin = [
|
358
|
+
"",
|
359
|
+
"\x61",
|
360
|
+
"\x62\x62\x62",
|
361
|
+
"\x63\x63\x63",
|
362
|
+
"\x73\x69\x6d\x70\x6c\x79\x20\x61\x20\x6c\x6f\x6e\x67\x20\x73\x74\x72\x69\x6e\x67",
|
363
|
+
"\x00\xeb\x15\x23\x1d\xfc\xeb\x60\x92\x58\x86\xb6\x7d\x06\x52\x99\x92\x59\x15\xae\xb1\x72\xc0\x66\x47",
|
364
|
+
"\x51\x6b\x6f\xcd\x0f",
|
365
|
+
"\xbf\x4f\x89\x00\x1e\x67\x02\x74\xdd",
|
366
|
+
"\x57\x2e\x47\x94",
|
367
|
+
"\xec\xac\x89\xca\xd9\x39\x23\xc0\x23\x21",
|
368
|
+
"\x10\xc8\x51\x1e",
|
369
|
+
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
370
|
+
]
|
371
|
+
out = [
|
372
|
+
"",
|
373
|
+
"2g",
|
374
|
+
"a3gV",
|
375
|
+
"aPEr",
|
376
|
+
"2cFupjhnEsSn59qHXstmK2ffpLv2",
|
377
|
+
"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
|
378
|
+
"ABnLTmg",
|
379
|
+
"3SEo3LWLoPntC",
|
380
|
+
"3EFU7m",
|
381
|
+
"EJDM8drfXA6uyA",
|
382
|
+
"Rt5zm",
|
383
|
+
"1111111111"
|
384
|
+
]
|
385
|
+
|
386
|
+
fixtures = bin.zip(out).map{|b,out| [ b.unpack("H*")[0], out ] }
|
387
|
+
#fixtures.each{|hex,out| p [hex, out, Bitcoin.encode_base58(hex), Bitcoin.decode_base58(out)] }
|
388
|
+
fixtures.all?{|hex,out| Bitcoin.encode_base58(hex) == out }.should == true
|
389
|
+
fixtures.all?{|hex,out| Bitcoin.decode_base58(out) == hex }.should == true
|
390
|
+
end
|
391
|
+
|
392
|
+
it '#block_next_retarget' do
|
393
|
+
Bitcoin.block_next_retarget(189408).should == 189503
|
394
|
+
Bitcoin.block_next_retarget(189503).should == 189503
|
395
|
+
Bitcoin.block_next_retarget(189504).should == 191519
|
396
|
+
end
|
397
|
+
|
398
|
+
it '#block_difficulty' do
|
399
|
+
Bitcoin.block_difficulty(436835377).should == "1751454.5353407"
|
400
|
+
end
|
401
|
+
|
402
|
+
it '#block_hashes_to_win' do
|
403
|
+
Bitcoin.block_hashes_to_win(436835377).should == 7522554734795001
|
404
|
+
end
|
405
|
+
|
406
|
+
it '#block_probability' do
|
407
|
+
Bitcoin.block_probability(436835377).should ==
|
408
|
+
"0.0000000000000001329335625003267087884673003372881794348"
|
409
|
+
end
|
410
|
+
|
411
|
+
it '#block_average_hashing_time' do
|
412
|
+
Bitcoin.block_average_hashing_time(436835377, 630_000_000).should == 11940563
|
413
|
+
end
|
414
|
+
|
415
|
+
it '#blockchain_total_btc' do
|
416
|
+
# 0.step(6930000, 210000){|height|
|
417
|
+
# p total_btc(height-1) unless height == 0
|
418
|
+
# p total_btc(height)
|
419
|
+
# }
|
420
|
+
[0, 209999, 210000, 419999, 420000, 1680000].map{|height|
|
421
|
+
Bitcoin.blockchain_total_btc(height)
|
422
|
+
}.should == [
|
423
|
+
[5000000000, 1, 5000000000, 0],
|
424
|
+
[1050000000000000, 1, 5000000000, 209999],
|
425
|
+
[1050005000000000, 2, 2500000000, 210000],
|
426
|
+
[1575002500000000, 2, 2500000000, 419999],
|
427
|
+
[1575005000000000, 3, 1250000000, 420000],
|
428
|
+
[2091801875000000, 9, 19531250, 1680000]
|
429
|
+
]
|
430
|
+
end
|
431
|
+
|
432
|
+
it '#block_creation_reward' do
|
433
|
+
[0, 209999, 210000, 419999, 420000, 1680000].map{|height|
|
434
|
+
Bitcoin.block_creation_reward(height)
|
435
|
+
}.should == [ 5000000000, 5000000000, 2500000000, 2500000000, 1250000000, 19531250 ]
|
436
|
+
end
|
437
|
+
|
438
|
+
end
|
439
|
+
|
440
|
+
|
441
|
+
__END__
|
442
|
+
describe 'Bitcoin-Wiki - Common Standards - Hashes' do
|
443
|
+
# https://en.bitcoin.it/wiki/Protocol_specification
|
444
|
+
# Hashes
|
445
|
+
# Usually, when a hash is computed within bitcoin, it is computed twice.
|
446
|
+
# Most of the time SHA-256 hashes are used, however RIPEMD-160 is also
|
447
|
+
# used when a shorter hash is desirable.
|
448
|
+
|
449
|
+
require 'digest/sha2'
|
450
|
+
require 'digest/rmd160'
|
451
|
+
|
452
|
+
|
453
|
+
it 'double-SHA-256 encoding of string "hello"' do
|
454
|
+
# first round sha256
|
455
|
+
Digest::SHA256.hexdigest("hello").should ==
|
456
|
+
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
|
457
|
+
|
458
|
+
# second round sha256
|
459
|
+
Digest::SHA256.hexdigest( Digest::SHA256.digest("hello") ).should ==
|
460
|
+
"9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50"
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'RIPEMD-160 encoding of string "hello"' do
|
464
|
+
# first round sha256
|
465
|
+
Digest::SHA256.hexdigest("hello").should ==
|
466
|
+
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
|
467
|
+
|
468
|
+
# second round rmd160
|
469
|
+
Digest::RMD160.hexdigest( Digest::SHA256.digest("hello") ).should ==
|
470
|
+
"b6a9c8c230722b7c748331a8b450f05566dc7d0f"
|
471
|
+
end
|
472
|
+
end
|