btcruby 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/FAQ.md +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/HOWTO.md +17 -0
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/TODO.txt +40 -0
- data/bin/console +19 -0
- data/btcruby.gemspec +20 -0
- data/documentation/address.md +73 -0
- data/documentation/base58.md +52 -0
- data/documentation/block.md +127 -0
- data/documentation/block_header.md +120 -0
- data/documentation/constants.md +88 -0
- data/documentation/data.md +54 -0
- data/documentation/diagnostics.md +90 -0
- data/documentation/extensions.md +76 -0
- data/documentation/hash_functions.md +58 -0
- data/documentation/hash_id.md +22 -0
- data/documentation/index.md +230 -0
- data/documentation/key.md +177 -0
- data/documentation/keychain.md +180 -0
- data/documentation/network.md +75 -0
- data/documentation/opcode.md +220 -0
- data/documentation/openssl.md +7 -0
- data/documentation/p2pkh.md +71 -0
- data/documentation/p2sh.md +64 -0
- data/documentation/proof_of_work.md +84 -0
- data/documentation/script.md +280 -0
- data/documentation/signature.md +71 -0
- data/documentation/transaction.md +213 -0
- data/documentation/transaction_builder.md +188 -0
- data/documentation/transaction_input.md +133 -0
- data/documentation/transaction_output.md +130 -0
- data/documentation/wif.md +72 -0
- data/documentation/wire_format.md +70 -0
- data/lib/btcruby/address.rb +296 -0
- data/lib/btcruby/base58.rb +108 -0
- data/lib/btcruby/big_number.rb +47 -0
- data/lib/btcruby/block.rb +170 -0
- data/lib/btcruby/block_header.rb +231 -0
- data/lib/btcruby/constants.rb +59 -0
- data/lib/btcruby/currency_formatter.rb +64 -0
- data/lib/btcruby/data.rb +98 -0
- data/lib/btcruby/diagnostics.rb +92 -0
- data/lib/btcruby/errors.rb +8 -0
- data/lib/btcruby/extensions.rb +65 -0
- data/lib/btcruby/hash_functions.rb +54 -0
- data/lib/btcruby/hash_id.rb +18 -0
- data/lib/btcruby/key.rb +517 -0
- data/lib/btcruby/keychain.rb +464 -0
- data/lib/btcruby/network.rb +73 -0
- data/lib/btcruby/opcode.rb +197 -0
- data/lib/btcruby/open_assets/asset.rb +35 -0
- data/lib/btcruby/open_assets/asset_address.rb +49 -0
- data/lib/btcruby/open_assets/asset_definition.rb +75 -0
- data/lib/btcruby/open_assets/asset_id.rb +24 -0
- data/lib/btcruby/open_assets/asset_marker.rb +94 -0
- data/lib/btcruby/open_assets/asset_processor.rb +377 -0
- data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
- data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
- data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
- data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
- data/lib/btcruby/open_assets.rb +26 -0
- data/lib/btcruby/openssl.rb +536 -0
- data/lib/btcruby/proof_of_work.rb +110 -0
- data/lib/btcruby/safety.rb +26 -0
- data/lib/btcruby/script.rb +733 -0
- data/lib/btcruby/signature_hashtype.rb +37 -0
- data/lib/btcruby/transaction.rb +511 -0
- data/lib/btcruby/transaction_builder/errors.rb +15 -0
- data/lib/btcruby/transaction_builder/provider.rb +54 -0
- data/lib/btcruby/transaction_builder/result.rb +73 -0
- data/lib/btcruby/transaction_builder/signer.rb +28 -0
- data/lib/btcruby/transaction_builder.rb +520 -0
- data/lib/btcruby/transaction_input.rb +298 -0
- data/lib/btcruby/transaction_outpoint.rb +30 -0
- data/lib/btcruby/transaction_output.rb +315 -0
- data/lib/btcruby/version.rb +3 -0
- data/lib/btcruby/wif.rb +118 -0
- data/lib/btcruby/wire_format.rb +362 -0
- data/lib/btcruby.rb +44 -2
- data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
- data/sample_code/creating_a_transaction_manually.rb +44 -0
- data/sample_code/generating_an_address.rb +20 -0
- data/sample_code/using_transaction_builder.rb +49 -0
- data/spec/address_spec.rb +206 -0
- data/spec/all.rb +6 -0
- data/spec/base58_spec.rb +83 -0
- data/spec/block_header_spec.rb +18 -0
- data/spec/block_spec.rb +18 -0
- data/spec/currency_formatter_spec.rb +46 -0
- data/spec/data_spec.rb +50 -0
- data/spec/diagnostics_spec.rb +41 -0
- data/spec/key_spec.rb +205 -0
- data/spec/keychain_spec.rb +261 -0
- data/spec/network_spec.rb +48 -0
- data/spec/open_assets/asset_address_spec.rb +33 -0
- data/spec/open_assets/asset_id_spec.rb +15 -0
- data/spec/open_assets/asset_marker_spec.rb +47 -0
- data/spec/open_assets/asset_processor_spec.rb +567 -0
- data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
- data/spec/open_assets/asset_transaction_spec.rb +70 -0
- data/spec/proof_of_work_spec.rb +53 -0
- data/spec/script_spec.rb +66 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/transaction_builder_spec.rb +338 -0
- data/spec/transaction_spec.rb +162 -0
- data/spec/wire_format_spec.rb +283 -0
- metadata +141 -7
@@ -0,0 +1,170 @@
|
|
1
|
+
module BTC
|
2
|
+
# Nodes collect new transactions into a block, hash them into a hash tree,
|
3
|
+
# and scan through nonce values to make the block's hash satisfy proof-of-work
|
4
|
+
# requirements. When they solve the proof-of-work, they broadcast the block
|
5
|
+
# to everyone and the block is added to the block chain. The first transaction
|
6
|
+
# in the block is a special one that creates a new coin owned by the creator
|
7
|
+
# of the block.
|
8
|
+
class Block < BlockHeader
|
9
|
+
|
10
|
+
# Array of BTC::Transaction objects
|
11
|
+
attr_accessor :transactions
|
12
|
+
|
13
|
+
def self.genesis_mainnet
|
14
|
+
self.new(
|
15
|
+
version: 1,
|
16
|
+
previous_block_hash: ZERO_HASH256,
|
17
|
+
merkle_root: BTC::Data.data_from_hex("3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"),
|
18
|
+
timestamp: 1231006505,
|
19
|
+
bits: 0x1d00ffff,
|
20
|
+
nonce: 0x7c2bac1d,
|
21
|
+
transactions: [BTC::Transaction.new(
|
22
|
+
version: 1,
|
23
|
+
inputs: [
|
24
|
+
BTC::TransactionInput.new(
|
25
|
+
coinbase_data: BTC::Data.data_from_hex("04FFFF001D010445"+
|
26
|
+
"5468652054696D65732030332F4A616E2F32303039204368616E63656C6C6F72206F6E2062" +
|
27
|
+
"72696E6B206F66207365636F6E64206261696C6F757420666F722062616E6B73"),
|
28
|
+
)
|
29
|
+
],
|
30
|
+
outputs: [
|
31
|
+
BTC::TransactionOutput.new(
|
32
|
+
value: 50*COIN,
|
33
|
+
script: Script.new(data: BTC::Data.data_from_hex("4104678AFDB0FE5548271967F1"+
|
34
|
+
"A67130B7105CD6A828E03909A67962E0EA1F61DEB649F6BC3F4CEF38"+
|
35
|
+
"C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5FAC"))
|
36
|
+
)
|
37
|
+
],
|
38
|
+
lock_time: 0
|
39
|
+
)],
|
40
|
+
height: 0
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.genesis_testnet
|
45
|
+
self.new(
|
46
|
+
version: 1,
|
47
|
+
previous_block_hash: ZERO_HASH256,
|
48
|
+
merkle_root: BTC::Data.data_from_hex("3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"),
|
49
|
+
timestamp: 1296688602,
|
50
|
+
bits: 0x1d00ffff,
|
51
|
+
nonce: 0x18aea41a,
|
52
|
+
transactions: [BTC::Transaction.new(
|
53
|
+
version: 1,
|
54
|
+
inputs: [
|
55
|
+
BTC::TransactionInput.new(
|
56
|
+
coinbase_data: BTC::Data.data_from_hex("04FFFF001D010445"+
|
57
|
+
"5468652054696D65732030332F4A616E2F32303039204368616E63656C6C6F72206F6E2062" +
|
58
|
+
"72696E6B206F66207365636F6E64206261696C6F757420666F722062616E6B73"),
|
59
|
+
)
|
60
|
+
],
|
61
|
+
outputs: [
|
62
|
+
BTC::TransactionOutput.new(
|
63
|
+
value: 50*COIN,
|
64
|
+
script: Script.new(data: BTC::Data.data_from_hex("4104678AFDB0FE5548271967F1"+
|
65
|
+
"A67130B7105CD6A828E03909A67962E0EA1F61DEB649F6BC3F4CEF38"+
|
66
|
+
"C4F35504E51EC112DE5C384DF7BA0B8D578A4C702B6BF11D5FAC"))
|
67
|
+
)
|
68
|
+
],
|
69
|
+
lock_time: 0
|
70
|
+
)],
|
71
|
+
height: 0
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(data: nil,
|
76
|
+
stream: nil,
|
77
|
+
version: CURRENT_VERSION,
|
78
|
+
previous_block_hash: nil,
|
79
|
+
previous_block_id: nil,
|
80
|
+
merkle_root: nil,
|
81
|
+
timestamp: 0,
|
82
|
+
time: nil,
|
83
|
+
bits: 0,
|
84
|
+
nonce: 0,
|
85
|
+
transactions: [],
|
86
|
+
|
87
|
+
# optional attributes
|
88
|
+
height: nil,
|
89
|
+
confirmations: nil)
|
90
|
+
super(
|
91
|
+
data: data,
|
92
|
+
stream: stream,
|
93
|
+
version: version,
|
94
|
+
previous_block_hash: previous_block_hash,
|
95
|
+
previous_block_id: previous_block_id,
|
96
|
+
merkle_root: merkle_root,
|
97
|
+
timestamp: timestamp,
|
98
|
+
time: time,
|
99
|
+
bits: bits,
|
100
|
+
nonce: nonce,
|
101
|
+
height: height,
|
102
|
+
confirmations: confirmations
|
103
|
+
)
|
104
|
+
|
105
|
+
@transactions = transactions if transactions
|
106
|
+
end
|
107
|
+
|
108
|
+
def init_with_stream(stream)
|
109
|
+
super(stream)
|
110
|
+
if !(txs_count = BTC::WireFormat.read_varint(stream: stream).first)
|
111
|
+
raise ArgumentError, "Failed to read count of transactions from the stream."
|
112
|
+
end
|
113
|
+
txs = (0...txs_count).map do
|
114
|
+
Transaction.new(stream: stream)
|
115
|
+
end
|
116
|
+
@transactions = txs
|
117
|
+
end
|
118
|
+
|
119
|
+
def data
|
120
|
+
data = super
|
121
|
+
data << BTC::WireFormat.encode_varint(self.transactions.size)
|
122
|
+
self.transactions.each do |tx|
|
123
|
+
data << tx.data
|
124
|
+
end
|
125
|
+
data
|
126
|
+
end
|
127
|
+
|
128
|
+
def block_header
|
129
|
+
BlockHeader.new(
|
130
|
+
version: self.version,
|
131
|
+
previous_block_hash: self.previous_block_hash,
|
132
|
+
merkle_root: self.merkle_root,
|
133
|
+
timestamp: self.timestamp,
|
134
|
+
bits: self.bits,
|
135
|
+
nonce: self.nonce,
|
136
|
+
height: self.height,
|
137
|
+
confirmations: self.confirmations)
|
138
|
+
end
|
139
|
+
|
140
|
+
def ==(other)
|
141
|
+
super(other) && @transactions == other.transactions
|
142
|
+
end
|
143
|
+
|
144
|
+
def dup
|
145
|
+
self.class.new(
|
146
|
+
version: self.version,
|
147
|
+
previous_block_hash: self.previous_block_hash,
|
148
|
+
merkle_root: self.merkle_root,
|
149
|
+
timestamp: self.timestamp,
|
150
|
+
bits: self.bits,
|
151
|
+
nonce: self.nonce,
|
152
|
+
transactions: self.transactions.map{|t|t.dup},
|
153
|
+
height: self.height,
|
154
|
+
confirmations: self.confirmations)
|
155
|
+
end
|
156
|
+
|
157
|
+
def inspect
|
158
|
+
%{#<#{self.class.name}:#{self.block_id[0,24]}} +
|
159
|
+
%{ ver:#{self.version}} +
|
160
|
+
%{ prev:#{self.previous_block_id[0,24]}} +
|
161
|
+
%{ merkle_root:#{BTC.id_from_hash(self.merkle_root)[0,16]}} +
|
162
|
+
%{ timestamp:#{self.timestamp}} +
|
163
|
+
%{ bits:0x#{self.bits.to_s(16)}} +
|
164
|
+
%{ nonce:0x#{self.nonce.to_s(16)}} +
|
165
|
+
%{ txs(#{self.transactions.size}): #{self.transactions.inspect}} +
|
166
|
+
%{>}
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
module BTC
|
2
|
+
# Block header is the 80-byte prefix of the block.
|
3
|
+
# Nodes collect new transactions into a block, hash them into a hash tree,
|
4
|
+
# and scan through nonce values to make the block's hash satisfy proof-of-work
|
5
|
+
# requirements. When they solve the proof-of-work, they broadcast the block
|
6
|
+
# to everyone and the block is added to the block chain. The first transaction
|
7
|
+
# in the block is a special one that creates a new coin owned by the creator
|
8
|
+
# of the block.
|
9
|
+
class BlockHeader
|
10
|
+
|
11
|
+
CURRENT_VERSION = 2
|
12
|
+
ZERO_HASH256 = "\x00".b*32
|
13
|
+
|
14
|
+
# Binary hash of the block
|
15
|
+
attr_reader :block_hash
|
16
|
+
|
17
|
+
# Hex big-endian hash of the block
|
18
|
+
attr_reader :block_id
|
19
|
+
|
20
|
+
# Block version.
|
21
|
+
attr_accessor :version
|
22
|
+
|
23
|
+
# Binary hash of the previous block
|
24
|
+
attr_accessor :previous_block_hash
|
25
|
+
|
26
|
+
# Hex big-endian hash of the previous block
|
27
|
+
attr_accessor :previous_block_id
|
28
|
+
|
29
|
+
# Raw binary root hash of the transaction merkle tree.
|
30
|
+
attr_accessor :merkle_root
|
31
|
+
|
32
|
+
# uint32 unix timestamp
|
33
|
+
attr_accessor :timestamp
|
34
|
+
|
35
|
+
# Time object derived from timestamp
|
36
|
+
attr_accessor :time
|
37
|
+
|
38
|
+
# uint32 proof-of-work in compact format
|
39
|
+
attr_accessor :bits
|
40
|
+
|
41
|
+
# uint32 nonce (used for mining iterations)
|
42
|
+
attr_accessor :nonce
|
43
|
+
|
44
|
+
|
45
|
+
# Optional attributes.
|
46
|
+
# These are not derived from block data, but attached externally (e.g. via external APIs).
|
47
|
+
|
48
|
+
# The distance from the first block in the chain (genesis block has height 0).
|
49
|
+
attr_accessor :height
|
50
|
+
|
51
|
+
# The number of blocks that have been processed since the previous block (including the block itself).
|
52
|
+
attr_accessor :confirmations
|
53
|
+
|
54
|
+
attr_accessor :my_name
|
55
|
+
|
56
|
+
def self.genesis_mainnet
|
57
|
+
self.new(
|
58
|
+
version: 1,
|
59
|
+
previous_block_hash: ZERO_HASH256,
|
60
|
+
merkle_root: BTC::Data.data_from_hex("3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"),
|
61
|
+
timestamp: 1231006505,
|
62
|
+
bits: 0x1d00ffff,
|
63
|
+
nonce: 0x7c2bac1d,
|
64
|
+
height: 0
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.genesis_testnet
|
69
|
+
self.new(
|
70
|
+
version: 1,
|
71
|
+
previous_block_hash: ZERO_HASH256,
|
72
|
+
merkle_root: BTC::Data.data_from_hex("3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"),
|
73
|
+
timestamp: 1296688602,
|
74
|
+
bits: 0x1d00ffff,
|
75
|
+
nonce: 0x18aea41a,
|
76
|
+
height: 0
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.with_data(data)
|
81
|
+
raise ArgumentError, "Use #{self}.new(data: ...) instead"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.with_stream(stream)
|
85
|
+
raise ArgumentError, "Use #{self}.new(stream: ...) instead"
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(data: nil,
|
89
|
+
stream: nil,
|
90
|
+
version: CURRENT_VERSION,
|
91
|
+
previous_block_hash: nil,
|
92
|
+
previous_block_id: nil,
|
93
|
+
merkle_root: nil,
|
94
|
+
timestamp: 0,
|
95
|
+
time: nil,
|
96
|
+
bits: 0,
|
97
|
+
nonce: 0,
|
98
|
+
|
99
|
+
# optional attributes
|
100
|
+
height: nil,
|
101
|
+
confirmations: nil)
|
102
|
+
|
103
|
+
if stream || data
|
104
|
+
init_with_stream(stream || StringIO.new(data))
|
105
|
+
else
|
106
|
+
@version = version || CURRENT_VERSION
|
107
|
+
@previous_block_hash = previous_block_hash || ZERO_HASH256
|
108
|
+
@previous_block_hash = BTC.hash_from_id(previous_block_id) if previous_block_id
|
109
|
+
@merkle_root = merkle_root || ZERO_HASH256
|
110
|
+
@timestamp = timestamp || 0
|
111
|
+
@timestamp = time.to_i if time
|
112
|
+
@bits = bits || 0
|
113
|
+
@nonce = nonce || 0
|
114
|
+
end
|
115
|
+
|
116
|
+
@height = height
|
117
|
+
@confirmations = confirmations
|
118
|
+
end
|
119
|
+
|
120
|
+
def init_with_stream(stream)
|
121
|
+
raise ArgumentError, "Stream is missing" if !stream
|
122
|
+
if stream.eof?
|
123
|
+
raise ArgumentError, "Can't parse block header from stream because it is already closed."
|
124
|
+
end
|
125
|
+
|
126
|
+
if !(version = BTC::WireFormat.read_int32le(stream: stream).first)
|
127
|
+
raise ArgumentError, "Failed to read block version prefix from the stream."
|
128
|
+
end
|
129
|
+
|
130
|
+
if !(prevhash = stream.read(32)) || prevhash.bytesize != 32
|
131
|
+
raise ArgumentError, "Failed to read 32-byte previous_block_hash from the stream."
|
132
|
+
end
|
133
|
+
|
134
|
+
if !(mrklroot = stream.read(32)) || mrklroot.bytesize != 32
|
135
|
+
raise ArgumentError, "Failed to read 32-byte block merkle_root from the stream."
|
136
|
+
end
|
137
|
+
|
138
|
+
if !(timestamp = BTC::WireFormat.read_uint32le(stream: stream).first)
|
139
|
+
raise ArgumentError, "Failed to read 32-byte block timestamp from the stream."
|
140
|
+
end
|
141
|
+
|
142
|
+
if !(bits = BTC::WireFormat.read_uint32le(stream: stream).first)
|
143
|
+
raise ArgumentError, "Failed to read 32-byte proof-of-work bits from the stream."
|
144
|
+
end
|
145
|
+
|
146
|
+
if !(nonce = BTC::WireFormat.read_uint32le(stream: stream).first)
|
147
|
+
raise ArgumentError, "Failed to read 32-byte nonce from the stream."
|
148
|
+
end
|
149
|
+
|
150
|
+
@version = version
|
151
|
+
@previous_block_hash = prevhash
|
152
|
+
@merkle_root = mrklroot
|
153
|
+
@timestamp = timestamp
|
154
|
+
@bits = bits
|
155
|
+
@nonce = nonce
|
156
|
+
end
|
157
|
+
|
158
|
+
def block_hash
|
159
|
+
BTC.hash256(self.header_data)
|
160
|
+
end
|
161
|
+
|
162
|
+
def block_id
|
163
|
+
BTC.id_from_hash(self.block_hash)
|
164
|
+
end
|
165
|
+
|
166
|
+
def previous_block_id
|
167
|
+
BTC.id_from_hash(self.previous_block_hash)
|
168
|
+
end
|
169
|
+
|
170
|
+
def previous_block_id=(block_id)
|
171
|
+
self.previous_block_hash = BTC.hash_from_id(block_id)
|
172
|
+
end
|
173
|
+
|
174
|
+
def time
|
175
|
+
Time.at(self.timestamp).utc
|
176
|
+
end
|
177
|
+
|
178
|
+
def time=(time)
|
179
|
+
self.timestamp = time.to_i
|
180
|
+
end
|
181
|
+
|
182
|
+
def data
|
183
|
+
header_data
|
184
|
+
end
|
185
|
+
|
186
|
+
def header_data # so that in subclass Block we don't hash the entire block
|
187
|
+
data = "".b
|
188
|
+
data << BTC::WireFormat.encode_int32le(self.version)
|
189
|
+
data << self.previous_block_hash
|
190
|
+
data << self.merkle_root
|
191
|
+
data << BTC::WireFormat.encode_uint32le(self.timestamp)
|
192
|
+
data << BTC::WireFormat.encode_uint32le(self.bits)
|
193
|
+
data << BTC::WireFormat.encode_uint32le(self.nonce)
|
194
|
+
data
|
195
|
+
end
|
196
|
+
|
197
|
+
def ==(other)
|
198
|
+
@version == other.version &&
|
199
|
+
@previous_block_hash == other.previous_block_hash &&
|
200
|
+
@merkle_root == other.merkle_root &&
|
201
|
+
@timestamp == other.timestamp &&
|
202
|
+
@bits == other.bits &&
|
203
|
+
@nonce == other.nonce
|
204
|
+
end
|
205
|
+
alias_method :eql?, :==
|
206
|
+
|
207
|
+
def dup
|
208
|
+
self.class.new(
|
209
|
+
version: self.version,
|
210
|
+
previous_block_hash: self.previous_block_hash,
|
211
|
+
merkle_root: self.merkle_root,
|
212
|
+
timestamp: self.timestamp,
|
213
|
+
bits: self.bits,
|
214
|
+
nonce: self.nonce,
|
215
|
+
height: self.height,
|
216
|
+
confirmations: self.confirmations)
|
217
|
+
end
|
218
|
+
|
219
|
+
def inspect
|
220
|
+
%{#<#{self.class.name}:#{self.block_id[0,24]}} +
|
221
|
+
%{ ver:#{self.version}} +
|
222
|
+
%{ prev:#{self.previous_block_id[0,24]}} +
|
223
|
+
%{ merkle_root:#{BTC.id_from_hash(self.merkle_root)[0,16]}} +
|
224
|
+
%{ timestamp:#{self.timestamp}} +
|
225
|
+
%{ bits:0x#{self.bits.to_s(16)}} +
|
226
|
+
%{ nonce:0x#{self.nonce.to_s(16)}} +
|
227
|
+
%{>}
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module BTC
|
2
|
+
|
3
|
+
# Satoshi is the smallest unit representable in Bitcoin transactions.
|
4
|
+
SATOSHI = 1
|
5
|
+
|
6
|
+
# 100 mln satoshis is one Bitcoin
|
7
|
+
COIN = 100_000_000
|
8
|
+
|
9
|
+
# 1 bit is 100 satoshis (1 millionth of a bitcoin)
|
10
|
+
BIT = 100
|
11
|
+
|
12
|
+
# Network Rules (changing these will result in incompatibility with other nodes)
|
13
|
+
|
14
|
+
# The maximum allowed size for a serialized block, in bytes
|
15
|
+
MAX_BLOCK_SIZE = 1000000
|
16
|
+
|
17
|
+
# The maximum allowed number of signature check operations in a block
|
18
|
+
MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50
|
19
|
+
|
20
|
+
# No amount larger than this (in satoshis) is valid
|
21
|
+
MAX_MONEY = 21_000_000 * COIN
|
22
|
+
|
23
|
+
# Coinbase transaction outputs can only be spent after this number of new blocks
|
24
|
+
COINBASE_MATURITY = 100
|
25
|
+
|
26
|
+
# Threshold for BTC::Transaction#lock_time: below this value it is interpreted
|
27
|
+
# as a block number, otherwise as UNIX timestamp.
|
28
|
+
LOCKTIME_THRESHOLD = 500000000 # Tue Nov 5 00:53:20 1985 UTC (max block number is in year ≈11521)
|
29
|
+
|
30
|
+
# P2SH BIP16 didn't become active until Apr 1 2012.
|
31
|
+
# All txs before this timestamp should not be verified with P2SH rule.
|
32
|
+
BIP16_TIMESTAMP = 1333238400
|
33
|
+
|
34
|
+
# Scripts longer than 10000 bytes are invalid.
|
35
|
+
MAX_SCRIPT_SIZE = 10000
|
36
|
+
|
37
|
+
# Maximum number of bytes per "pushdata" operation
|
38
|
+
MAX_SCRIPT_ELEMENT_SIZE = 520 # bytes
|
39
|
+
|
40
|
+
# Number of public keys allowed for OP_CHECKMULTISIG
|
41
|
+
MAX_KEYS_FOR_CHECKMULTISIG = 20
|
42
|
+
|
43
|
+
# Maximum number of operations allowed per script (excluding pushdata operations and OP_<N>)
|
44
|
+
# Multisig op additionally increases count by a number of pubkeys.
|
45
|
+
MAX_OPS_PER_SCRIPT = 201
|
46
|
+
|
47
|
+
# Soft Rules (can bend these without becoming incompatible with everyone)
|
48
|
+
|
49
|
+
# The maximum number of entries in an 'inv' protocol message
|
50
|
+
MAX_INV_SZ = 50000
|
51
|
+
|
52
|
+
# The maximum size for mined blocks
|
53
|
+
MAX_BLOCK_SIZE_GEN = MAX_BLOCK_SIZE / 2
|
54
|
+
|
55
|
+
# The maximum size for transactions we're willing to relay/mine
|
56
|
+
MAX_STANDARD_TX_SIZE = MAX_BLOCK_SIZE_GEN / 5
|
57
|
+
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module BTC
|
4
|
+
# Modeled after NSNumberFormatter in Cocoa, this class allows to convert
|
5
|
+
# bitcoin amounts to their string representations and vice versa.
|
6
|
+
class CurrencyFormatter
|
7
|
+
|
8
|
+
STYLE_BTC = :btc # 1.0 is 1 btc (100_000_000 satoshis)
|
9
|
+
STYLE_BTC_LONG = :btc_long # 1.00000000 is 1 btc (100_000_000 satoshis)
|
10
|
+
STYLE_MBTC = :mbtc # 1.0 is 0.001 btc (100_000 satoshis)
|
11
|
+
STYLE_BIT = :bit # 1.0 is 0.000001 btc (100 satoshis)
|
12
|
+
STYLE_SATOSHIS = :satoshis # 1.0 is 0.00000001 btc (1 satoshi)
|
13
|
+
|
14
|
+
attr_accessor :style
|
15
|
+
attr_accessor :show_suffix
|
16
|
+
|
17
|
+
# Returns a singleton formatter for BTC values (1.0 is one bitcoin) without suffix.
|
18
|
+
def self.btc_formatter
|
19
|
+
@btc_formatter ||= self.new(style: STYLE_BTC)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns a singleton formatter for BTC values where there are always 8 places
|
23
|
+
# after decimal point (e.g. "42.00000000").
|
24
|
+
def self.btc_long_formatter
|
25
|
+
@btc_long_formatter ||= self.new(style: STYLE_BTC_LONG)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(style: :btc, show_suffix: false)
|
29
|
+
@style = style
|
30
|
+
@show_suffix = show_suffix
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns formatted string for an amount in satoshis.
|
34
|
+
def string_from_number(number)
|
35
|
+
if @style == :btc
|
36
|
+
number = number.to_i
|
37
|
+
return "#{number / BTC::COIN}.#{'%08d' % [number % BTC::COIN]}".gsub(/0+$/,"").gsub(/\.$/,".0")
|
38
|
+
elsif @style == :btc_long
|
39
|
+
number = number.to_i
|
40
|
+
return "#{number / BTC::COIN}.#{'%08d' % [number % BTC::COIN]}"
|
41
|
+
else
|
42
|
+
# TODO: parse other styles
|
43
|
+
raise "Not implemented"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns amount of satoshis parsed from a formatted string according to style attribute.
|
48
|
+
def number_from_string(string)
|
49
|
+
bd = BigDecimal.new(string)
|
50
|
+
if @style == :btc || @style == :btc_long
|
51
|
+
return (bd * BTC::COIN).to_i
|
52
|
+
else
|
53
|
+
# TODO: support other styles
|
54
|
+
raise "Not Implemented"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Creates a copy if you want to customize another formatter (e.g. a global singleton like btc_formatter)
|
59
|
+
def dup
|
60
|
+
self.class.new(style: @style, show_suffix: @show_suffix)
|
61
|
+
end
|
62
|
+
|
63
|
+
end # BitcoinFormatter
|
64
|
+
end # BTC
|
data/lib/btcruby/data.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module BTC
|
4
|
+
module Data
|
5
|
+
extend self
|
6
|
+
include HashFunctions # obsolete, left for backwards compatibility
|
7
|
+
|
8
|
+
HEX_PACK_CODE = "H*".freeze
|
9
|
+
BYTE_PACK_CODE = "C*".freeze
|
10
|
+
|
11
|
+
# Generates a secure random number of a given length
|
12
|
+
def random_data(length = 32)
|
13
|
+
SecureRandom.random_bytes(length)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Converts hexadecimal string to a binary data string.
|
17
|
+
def data_from_hex(hex_string)
|
18
|
+
raise ArgumentError, "Hex string is missing" if !hex_string
|
19
|
+
hex_string = hex_string.strip
|
20
|
+
data = [hex_string].pack(HEX_PACK_CODE)
|
21
|
+
if hex_from_data(data) != hex_string.downcase # invalid hex string was detected
|
22
|
+
raise FormatError, "Hex string is invalid: #{hex_string.inspect}"
|
23
|
+
end
|
24
|
+
return data
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts binary string to lowercase hexadecimal representation.
|
28
|
+
def hex_from_data(data)
|
29
|
+
raise ArgumentError, "Data is missing" if !data
|
30
|
+
return data.unpack(HEX_PACK_CODE).first
|
31
|
+
end
|
32
|
+
|
33
|
+
# Converts a binary string to an array of bytes (list of integers).
|
34
|
+
# Returns a much more efficient slice of bytes if offset/limit or
|
35
|
+
# range are specified. That is, avoids converting the entire buffer to byte array.
|
36
|
+
#
|
37
|
+
# Note 1: if range is specified, it takes precedence over offset/limit.
|
38
|
+
#
|
39
|
+
# Note 2: byteslice(...).bytes is less efficient as it creates
|
40
|
+
# an intermediate shorter string.
|
41
|
+
#
|
42
|
+
def bytes_from_data(data, offset: 0, limit: nil, range: nil)
|
43
|
+
raise ArgumentError, "Data is missing" if !data
|
44
|
+
if offset == 0 && limit == nil && range == nil
|
45
|
+
return data.bytes
|
46
|
+
end
|
47
|
+
if range
|
48
|
+
offset = range.begin
|
49
|
+
limit = range.size
|
50
|
+
end
|
51
|
+
bytes = []
|
52
|
+
data.each_byte do |byte|
|
53
|
+
if offset > 0
|
54
|
+
offset -= 1
|
55
|
+
else
|
56
|
+
if !limit || limit > 0
|
57
|
+
bytes << byte
|
58
|
+
limit -= 1 if limit
|
59
|
+
else
|
60
|
+
break
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
bytes
|
65
|
+
end
|
66
|
+
|
67
|
+
# Converts binary string to an array of bytes (list of integers).
|
68
|
+
def data_from_bytes(bytes)
|
69
|
+
raise ArgumentError, "Bytes are missing" if !bytes
|
70
|
+
bytes.pack(BYTE_PACK_CODE)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns string as-is if it is ASCII-compatible
|
74
|
+
# (that is, if you are interested in 7-bit characters exposed as #bytes).
|
75
|
+
# If it is not, attempts to transcode to UTF8 replacing invalid characters if there are any.
|
76
|
+
# If options are not specified, uses safe default that replaces unknown characters with standard character.
|
77
|
+
# If options are specified, they are used as-is for String#encode method.
|
78
|
+
def ensure_ascii_compatible_encoding(string, options = nil)
|
79
|
+
if string.encoding.ascii_compatible?
|
80
|
+
string
|
81
|
+
else
|
82
|
+
string.encode(Encoding::UTF_8, options || {:invalid => :replace, :undef => :replace})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns string as-is if it is already encoded in binary encoding (aka BINARY or ASCII-8BIT).
|
87
|
+
# If it is not, converts to binary by calling stdlib's method #b.
|
88
|
+
def ensure_binary_encoding(string)
|
89
|
+
raise ArgumentError, "String is missing" if !string
|
90
|
+
if string.encoding == Encoding::BINARY
|
91
|
+
string
|
92
|
+
else
|
93
|
+
string.b
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|