lib/bitcoin.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin.rb in bitcoin-ruby-0.0.2
- old
+ new
@@ -1,5 +1,6 @@
+# encoding: ascii-8bit
# Bitcoin Utils and Network Protocol in Ruby.
require 'digest/sha2'
require 'digest/rmd160'
require 'openssl'
@@ -15,11 +16,14 @@
autoload :Storage, 'bitcoin/storage/storage'
autoload :Logger, 'bitcoin/logger'
autoload :Key, 'bitcoin/key'
autoload :Config, 'bitcoin/config'
autoload :Builder, 'bitcoin/builder'
+ autoload :Validation, 'bitcoin/validation'
+ autoload :Namecoin, 'bitcoin/namecoin'
+
module Network
autoload :ConnectionHandler, 'bitcoin/network/connection_handler'
autoload :CommandHandler, 'bitcoin/network/command_handler'
autoload :CommandClient, 'bitcoin/network/command_client'
autoload :Node, 'bitcoin/network/node'
@@ -41,10 +45,11 @@
def self.require_dependency name, opts = {}
begin
require name.to_s
rescue LoadError
+ return false if name.to_s == "log4r"
print "Cannot load #{opts[:exit] == false ? 'optional' : 'required'} dependency '#{name}'"
(opts[:gem] == false) ? puts("") :
puts(" - install with `gem install #{opts[:gem] || name}`")
puts opts[:message] if opts[:message]
exit 1 unless opts[:exit] == false
@@ -53,13 +58,10 @@
true
end
module Util
- def hth(h); h.unpack("H*")[0]; end
- def htb(h); [h].pack("H*"); end
-
def address_version; Bitcoin.network[:address_version]; end
def p2sh_version; Bitcoin.network[:p2sh_version]; end
# hash160 is a 20 bytes (160bits) rmd610-sha256 hexdigest.
def hash160(hex)
@@ -204,40 +206,67 @@
Digest::SHA256.digest(
Digest::SHA256.digest( [hex].pack("H*").reverse )
).reverse.unpack("H*")[0]
end
+ def bitcoin_byte_hash(bytes)
+ Digest::SHA256.digest(Digest::SHA256.digest(bytes))
+ end
+
def bitcoin_mrkl(a, b); bitcoin_hash(b + a); end
def block_hash(prev_block, mrkl_root, time, bits, nonce, ver)
h = "%08x%08x%08x%064s%064s%08x" %
[nonce, bits, time, mrkl_root, prev_block, ver]
bitcoin_hash(h)
end
+ # get merkle tree for given +tx+ list.
def hash_mrkl_tree(tx)
+ return [nil] if tx != tx.uniq
chunks = [ tx.dup ]
while chunks.last.size >= 2
- chunks << chunks.last.each_slice(2).map{|i|
- Bitcoin.bitcoin_mrkl( i[0], i[1] || i[0] )
- }
+ chunks << chunks.last.each_slice(2).map {|a, b|
+ Bitcoin.bitcoin_mrkl( a, b || a ) }
end
chunks.flatten
end
+ # get merkle branch connecting given +target+ to the merkle root of +tx+ list
+ def hash_mrkl_branch(tx, target)
+ return [ nil ] if tx != tx.uniq
+ branch, chunks = [], [ tx.dup ]
+ while chunks.last.size >= 2
+ chunks << chunks.last.each_slice(2).map {|a, b|
+ hash = Bitcoin.bitcoin_mrkl( a, b || a )
+ next hash unless [a, b].include?(target)
+ branch << (a == target ? (b || a) : a)
+ target = hash
+ }
+ end
+ branch
+ end
+ # get merkle root from +branch+ and +target+.
+ def mrkl_branch_root(branch, target, idx)
+ branch.map do |hash|
+ a, b = *( idx & 1 == 0 ? [target, hash] : [hash, target] )
+ idx >>= 1; target = Bitcoin.bitcoin_mrkl( a, b )
+ end.last
+ end
+
def sign_data(key, data)
key.dsa_sign_asn1(data)
end
def verify_signature(hash, signature, public_key)
key = bitcoin_elliptic_curve
key.public_key = ::OpenSSL::PKey::EC::Point.from_hex(key.group, public_key)
key.dsa_verify_asn1(hash, signature)
rescue OpenSSL::PKey::ECError, OpenSSL::PKey::EC::Point::Error
false
- end
+ end
def open_key(private_key, public_key=nil)
key = bitcoin_elliptic_curve
key.private_key = ::OpenSSL::BN.from_hex(private_key)
public_key = regenerate_public_key(private_key) unless public_key
@@ -247,11 +276,35 @@
def regenerate_public_key(private_key)
Bitcoin::OpenSSL_EC.regenerate_key(private_key)[1]
end
+ def bitcoin_signed_message_hash(message)
+ # TODO: this will fail horribly on messages with len > 255. It's a cheap implementation of Bitcoin's CDataStream.
+ data = "\x18Bitcoin Signed Message:\n" + [message.bytesize].pack("C") + message
+ Digest::SHA256.digest(Digest::SHA256.digest(data))
+ end
+ def sign_message(private_key_hex, public_key_hex, message)
+ hash = bitcoin_signed_message_hash(message)
+ signature = Bitcoin::OpenSSL_EC.sign_compact(hash, private_key_hex, public_key_hex)
+ { 'address' => pubkey_to_address(public_key_hex), 'message' => message, 'signature' => [ signature ].pack("m0") }
+ end
+
+ def verify_message(address, signature, message)
+ hash = bitcoin_signed_message_hash(message)
+ signature = signature.unpack("m0")[0] rescue nil # decode base64
+ raise "invalid address" unless valid_address?(address)
+ raise "malformed base64 encoding" unless signature
+ raise "malformed signature" unless signature.bytesize == 65
+ pubkey = Bitcoin::OpenSSL_EC.recover_compact(hash, signature)
+ pubkey_to_address(pubkey) == address if pubkey
+ rescue Exception => ex
+ p [ex.message, ex.backtrace]; false
+ end
+
+
RETARGET_INTERVAL = 2016
# block count when the next retarget will take place.
def block_next_retarget(block_height)
(block_height + (RETARGET_INTERVAL-block_height.divmod(RETARGET_INTERVAL).last)) - 1
@@ -281,10 +334,17 @@
# average time to find a block in seconds with the current target. (nbits)
def block_average_hashing_time(target_nbits, hashes_per_second)
block_hashes_to_win(target_nbits) / hashes_per_second
end
+ # average mining time (in days) using Mh/s to get btc
+ def block_average_mining_time(block_nbits, block_height, mega_hashes_per_second, target_btc=1.0)
+ seconds = block_average_hashing_time(block_nbits, mega_hashes_per_second * 1_000_000)
+ reward = block_creation_reward(block_height) / Bitcoin::COIN # satoshis to btc
+ (days = seconds / 60 / 60 / 24) * (target_btc / reward)
+ end
+
# shows the total number of Bitcoins in circulation, reward era and reward in that era.
def blockchain_total_btc(height)
reward, interval = 5000000000, 210000
total_btc = reward
reward_era, remainder = (height).divmod(interval)
@@ -299,19 +359,35 @@
def block_creation_reward(block_height)
5000000000 / (2 ** (block_height / 210000.0).floor)
end
end
+ extend Util
+
+
+ module BinaryExtensions
+ def hth; unpack("H*")[0]; end
+ def reverse_hth; reverse.hth; end
+ def htb; [self].pack("H*"); end
+ def htb_reverse; htb.reverse; end
+ end
+
+ class ::String
+ include Bitcoin::BinaryExtensions
+ end
+
+
module ::OpenSSL
class BN
def self.from_hex(hex); new(hex, 16); end
def to_hex; to_i.to_s(16); end
def to_mpi; to_s(0).unpack("C*"); end
end
class PKey::EC
def private_key_hex; private_key.to_hex.rjust(64, '0'); end
def public_key_hex; public_key.to_hex.rjust(130, '0'); end
+ def pubkey_compressed?; public_key.group.point_conversion_form == :compressed; end
end
class PKey::EC::Point
def self.from_hex(group, hex)
new(group, BN.from_hex(hex))
end
@@ -320,51 +396,265 @@
end
end
autoload :OpenSSL_EC, "bitcoin/ffi/openssl"
-
- extend Util
-
@network = :bitcoin
def self.network
NETWORKS[@network]
end
+ def self.network_name
+ @network
+ end
+
+ def self.network_project
+ @network_project
+ end
+
def self.network= name
+ raise "Network descriptor '#{name}' not found." unless NETWORKS[name.to_sym]
@network = name.to_sym
+ @network_project = network[:project] rescue nil
+ Bitcoin::Namecoin.load if namecoin?
+ @network
end
+ [:bitcoin, :namecoin, :litecoin, :freicoin].each do |n|
+ instance_eval "def #{n}?; network_project == :#{n}; end"
+ end
+
+
+ CENT = 1_000_000
+ COIN = 100_000_000
+ MAX_BLOCK_SIZE = 1_000_000
+ MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE/2
+ MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50
+ MAX_ORPHAN_TRANSACTIONS = MAX_BLOCK_SIZE/100
+
+ MIN_FEE_MODE = [ :block, :relay, :send ]
+
NETWORKS = {
+
:bitcoin => {
+ :project => :bitcoin,
:magic_head => "\xF9\xBE\xB4\xD9",
:address_version => "00",
:p2sh_version => "05",
:privkey_version => "80",
:default_port => 8333,
- :dns_seeds => ["bitseed.xf2.org", "dnsseed.bluematt.me",
- "dnsseed.bitcoin.dashjr.org", "seed.bitcoin.sipa.be"],
+ :protocol_version => 70001,
+ :coinbase_maturity => 100,
+ :retarget_interval => 2016,
+ :retarget_time => 1209600, # 2 weeks
+ :max_money => 21_000_000 * COIN,
+ :min_tx_fee => 50_000,
+ :min_relay_tx_fee => 10_000,
+ :dns_seeds => [
+ "seed.bitcoin.sipa.be",
+ "dnsseed.bluematt.me",
+ "dnsseed.bitcoin.dashjr.org",
+ "bitseed.xf2.org",
+ ],
:genesis_hash => "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
:proof_of_work_limit => 0x1d00ffff,
+ :alert_pubkeys => ["04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284"],
:known_nodes => [
'relay.eligius.st',
'mining.bitcoin.cz',
- 'bitcoins.lc',
'blockchain.info',
'blockexplorer.com',
- ]
+ ],
+ :checkpoints => {
+ 11111 => "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d",
+ 33333 => "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6",
+ 74000 => "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20",
+ 105000 => "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97",
+ 134444 => "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe",
+ 168000 => "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763",
+ 193000 => "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317",
+ 210000 => "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e",
+ 216116 => "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e",
+ 225430 => "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932",
+ }
},
+
:testnet => {
+ :project => :bitcoin,
:magic_head => "\xFA\xBF\xB5\xDA",
:address_version => "6f",
:p2sh_version => "c4",
:privkey_version => "ef",
:default_port => 18333,
- :dns_seeds => ["testseed.bitcoin.interesthings.de"],
+ :max_money => 21_000_000 * COIN,
+ :dns_seeds => [ "testseed.bitcoin.interesthings.de" ],
:genesis_hash => "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008",
:proof_of_work_limit => 0x1d07fff8,
- :known_nodes => []
- }
+ :alert_pubkeys => ["04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"],
+ :known_nodes => [],
+ :checkpoints => {}
+ },
+
+ :testnet3 => {
+ :project => :bitcoin,
+ :magic_head => "\x0b\x11\x09\x07",
+ :address_version => "6f",
+ :p2sh_version => "c4",
+ :privkey_version => "ef",
+ :default_port => 18333,
+ :protocol_version => 70001,
+ :coinbase_maturity => 100,
+ :retarget_interval => 2016,
+ :retarget_time => 1209600, # 2 weeks
+ :max_money => 21_000_000 * COIN,
+ :min_tx_fee => 50_000,
+ :min_relay_tx_fee => 10_000,
+ :dns_seeds => [
+ "testnet-seed.bitcoin.petertodd.org",
+ "testnet-seed.bluematt.me",
+ ],
+ :genesis_hash => "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
+ :proof_of_work_limit => 0x1d07fff8,
+ :alert_pubkeys => ["04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"],
+ :known_nodes => [],
+ :checkpoints => {
+ # 542 contains invalid transaction
+ 542 => "0000000083c1f82cf72c6724f7a317325806384b06408bce7a4327f418dfd5ad",
+ 71018 => "000000000010dd93dc55541116b2744eb8f4c3b706df6e8512d231a03fb9e435",
+ }
+ },
+
+ :litecoin => {
+ :project => :litecoin,
+ :magic_head => "\xfb\xc0\xb6\xdb",
+ :address_version => "30",
+ :p2sh_version => "05",
+ :privkey_version => "ef",
+ :default_port => 9333,
+ :protocol_version => 60002,
+ :max_money => 84_000_000 * COIN,
+ :min_tx_fee => 2_000_000,
+ :coinbase_maturity => 100,
+ :retarget_interval => 2016,
+ :retarget_time => 302400, # 3.5 days
+ :min_relay_tx_fee => 1_000_000,
+ :dns_seeds => [
+ "dnsseed.litecointools.com",
+ "dnsseed.litecoinpool.org",
+ "dnsseed.ltc.xurious.com",
+ "dnsseed.koin-project.com",
+ "dnsseed.weminemnc.com",
+ ],
+ :genesis_hash => "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2",
+ :proof_of_work_limit => 0,
+ :alert_pubkeys => [],
+ :known_nodes => [],
+ :checkpoints => {
+ 1 => "80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f",
+ 2 => "13957807cdd1d02f993909fa59510e318763f99a506c4c426e3b254af09f40d7",
+ 1500 => "841a2965955dd288cfa707a755d05a54e45f8bd476835ec9af4402a2b59a2967",
+ 4032 => "9ce90e427198fc0ef05e5905ce3503725b80e26afd35a987965fd7e3d9cf0846",
+ 8064 => "eb984353fc5190f210651f150c40b8a4bab9eeeff0b729fcb3987da694430d70",
+ 16128 => "602edf1859b7f9a6af809f1d9b0e6cb66fdc1d4d9dcd7a4bec03e12a1ccd153d",
+ 23420 => "d80fdf9ca81afd0bd2b2a90ac3a9fe547da58f2530ec874e978fce0b5101b507",
+ 50000 => "69dc37eb029b68f075a5012dcc0419c127672adb4f3a32882b2b3e71d07a20a6",
+ 80000 => "4fcb7c02f676a300503f49c764a89955a8f920b46a8cbecb4867182ecdb2e90a",
+ 120000 => "bd9d26924f05f6daa7f0155f32828ec89e8e29cee9e7121b026a7a3552ac6131",
+ 161500 => "dbe89880474f4bb4f75c227c77ba1cdc024991123b28b8418dbbf7798471ff43",
+ 179620 => "2ad9c65c990ac00426d18e446e0fd7be2ffa69e9a7dcb28358a50b2b78b9f709",
+ 240000 => "7140d1c4b4c2157ca217ee7636f24c9c73db39c4590c4e6eab2e3ea1555088aa",
+ 383640 => "2b6809f094a9215bafc65eb3f110a35127a34be94b7d0590a096c3f126c6f364",
+ }
+ },
+
+ :litecoin_testnet => {
+ :project => :litecoin,
+ :magic_head => "\xfc\xc1\xb7\xdc",
+ :address_version => "6f",
+ :p2sh_version => "c4",
+ :privkey_version => "ef",
+ :default_port => 19333,
+ :protocol_version => 60002,
+ :min_tx_fee => 2_000_000,
+ :min_relay_tx_fee => 1_000_000,
+ :coinbase_maturity => 100,
+ :retarget_interval => 2016,
+ :retarget_time => 302400, # 3.5 days
+ :max_money => 84_000_000 * COIN,
+ :dns_seeds => [
+ "testnet-seed.litecointools.com",
+ "testnet-seed.weminemnc.com",
+ ],
+ :genesis_hash => "f5ae71e26c74beacc88382716aced69cddf3dffff24f384e1808905e0188f68f",
+ :proof_of_work_limit => 0,
+ :alert_pubkeys => [],
+ :known_nodes => [],
+ :checkpoints => {}
+ },
+
+
+ :freicoin => {
+ :project => :freicoin,
+ :magic_head => "\x2c\xfe\x7e\x6d",
+ :address_version => "00",
+ :p2sh_version => "05",
+ :privkey_version => "80",
+ :default_port => 8639,
+ :protocol_version => 60002,
+ :max_money => 21_000_000 * COIN,
+ :min_tx_fee => 50_000,
+ :min_relay_tx_fee => 10_000,
+ :dns_seeds => [ "seed.freico.in", "fledge.freico.in" ],
+ :genesis_hash => "000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c",
+ :proof_of_work_limit => 0,
+ :alert_pubkeys => [],
+ :known_nodes => [],
+ :checkpoints => {
+ 10080 => "00000000003ff9c4b806639ec4376cc9acafcdded0e18e9dbcc2fc42e8e72331",
+ 15779 => "000000000003eb31742b35f5efd8ffb5cdd19dcd8e82cdaad90e592c450363b6",
+ }
+ },
+
+ :namecoin => {
+ :project => :namecoin,
+ :magic_head => "\xF9\xBE\xB4\xFE",
+ :address_version => "34",
+ :default_port => 8334,
+ :protocol_version => 35000,
+ :max_money => 21_000_000 * COIN,
+ :min_tx_fee => 50_000,
+ :min_relay_tx_fee => 10_000,
+ :dns_seeds => [],
+ :genesis_hash => "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770",
+ :proof_of_work_limit => 0x1d00ffff,
+ :known_nodes => ["bitcoin.tunl.in", "webbtc.com", "178.32.31.41",
+ "78.47.86.43", "69.164.206.88", ""],
+ :checkpoints => {
+ 0 => "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770",
+ 19200 => "d8a7c3e01e1e95bcee015e6fcc7583a2ca60b79e5a3aa0a171eddd344ada903d",
+ 24000 => "425ab0983cf04f43f346a4ca53049d0dc2db952c0a68eb0b55c3bb64108d5371",
+ 97778 => "7553b1e43da01cfcda4335de1caf623e941d43894bd81c2af27b6582f9d83c6f",
+ }
+ },
+
+ :namecoin_testnet => {
+ :project => :namecoin,
+ :magic_head => "\xFA\xBF\xB5\xFE",
+ :address_version => "34",
+ :default_port => 18334,
+ :protocol_version => 35000,
+ :min_tx_fee => 50_000,
+ :min_relay_tx_fee => 10_000,
+ :max_money => 21_000_000 * COIN,
+ :dns_seeds => [],
+ :genesis_hash => "00000001f8ab0d14bceaeb50d163b0bef15aecf62b87bd5f5c864d37f201db97",
+ :proof_of_work_limit => 0x1d00ffff,
+ :known_nodes => ["178.32.31.41"],
+ :checkpoints => {
+ 0 => "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770",
+
+ }
+ },
}
-
+
end