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
data/lib/btcruby/wif.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module BTC
|
2
|
+
# Private key in Wallet Import Format (WIF aka Sipa format).
|
3
|
+
# Examples: 5KQntKuhYWSRXNq... or L3p8oAcQTtuokSC...
|
4
|
+
class WIF < Address
|
5
|
+
|
6
|
+
KEY_LENGTH = 32
|
7
|
+
|
8
|
+
def self.mainnet_version
|
9
|
+
128
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.testnet_version
|
13
|
+
239
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :public_key_compressed
|
17
|
+
def public_key_compressed?
|
18
|
+
@public_key_compressed
|
19
|
+
end
|
20
|
+
|
21
|
+
def key
|
22
|
+
BTC::Key.new(private_key: self.private_key, public_key_compressed: @public_key_compressed, network: self.network)
|
23
|
+
end
|
24
|
+
|
25
|
+
def private_key
|
26
|
+
@data
|
27
|
+
end
|
28
|
+
|
29
|
+
def public_address
|
30
|
+
self.key.address(network: self.network)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
return false if !other
|
35
|
+
self.data == other.data &&
|
36
|
+
self.version == other.version &&
|
37
|
+
self.public_key_compressed == other.public_key_compressed
|
38
|
+
end
|
39
|
+
alias_method :eql?, :==
|
40
|
+
|
41
|
+
# private API
|
42
|
+
def self.with_string(base58check_string, raw_data = nil)
|
43
|
+
raise ArgumentError, "Use WIF.new(string: ...) instead"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates an address with private key data and non-compressed pubkey flag.
|
47
|
+
def self.with_data(data, public_key_compressed: false, network: nil)
|
48
|
+
raise ArgumentError, "Use WIF.new(private_key: ...) or WIF.new(key: ...) instead"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Usage:
|
52
|
+
# * WIF.new(string: ...)
|
53
|
+
# * WIF.new(private_key: ..., public_key_compressed: true|false, network: ...)
|
54
|
+
# * WIF.new(key: ...)
|
55
|
+
def initialize(string: nil,
|
56
|
+
data: nil,
|
57
|
+
network: nil,
|
58
|
+
_raw_data: nil,
|
59
|
+
private_key: nil,
|
60
|
+
key: nil,
|
61
|
+
public_key_compressed: nil)
|
62
|
+
if key
|
63
|
+
raise ArgumentError, "Key must contain private_key to be exported in WIF" if !key.private_key
|
64
|
+
private_key = key.private_key
|
65
|
+
if public_key_compressed == nil
|
66
|
+
public_key_compressed = key.public_key_compressed
|
67
|
+
end
|
68
|
+
network ||= key.network
|
69
|
+
end
|
70
|
+
if string
|
71
|
+
if data || private_key || key || (public_key_compressed != nil) || network
|
72
|
+
raise ArgumentError, "Cannot specify individual attributes when decoding WIF from string"
|
73
|
+
end
|
74
|
+
_raw_data ||= Base58.data_from_base58check(string)
|
75
|
+
if _raw_data.bytesize != (1 + KEY_LENGTH) && _raw_data.bytesize != (2 + KEY_LENGTH)
|
76
|
+
raise FormatError, "Raw WIF data should have size #{1 + KEY_LENGTH}(+1), but it is #{_raw_data.bytesize} instead"
|
77
|
+
end
|
78
|
+
# compressed flag is simply one more byte appended to the string
|
79
|
+
@base58check_string = string
|
80
|
+
@data = _raw_data[1, KEY_LENGTH]
|
81
|
+
@public_key_compressed = (_raw_data.bytesize == (2 + KEY_LENGTH))
|
82
|
+
@version = _raw_data.bytes.first
|
83
|
+
@network = nil
|
84
|
+
elsif data ||= private_key
|
85
|
+
if data.bytesize != KEY_LENGTH
|
86
|
+
raise FormatError, "Failed to create WIF: data should have size #{KEY_LENGTH}, but it is #{data.bytesize} instead"
|
87
|
+
end
|
88
|
+
@base58check_string = nil
|
89
|
+
@data = data
|
90
|
+
@public_key_compressed = public_key_compressed
|
91
|
+
if @public_key_compressed == nil
|
92
|
+
@public_key_compressed = false # legacy default is uncompressed pubkey
|
93
|
+
end
|
94
|
+
@version = nil
|
95
|
+
@network = network
|
96
|
+
else
|
97
|
+
raise ArgumentError, "Either data or string must be provided"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def data_for_base58check_encoding
|
102
|
+
data = BTC::Data.data_from_bytes([self.version]) + @data
|
103
|
+
if @public_key_compressed
|
104
|
+
data += BTC::Data.data_from_bytes([0x01])
|
105
|
+
end
|
106
|
+
return data
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
%{#<#{self.class}:#{to_s} privkey:#{BTC::Data.hex_from_data(data)} (#{@public_key_compressed ? '' : 'un'}compressed pubkey)>}
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# For compatibility
|
116
|
+
PrivateKeyAddress = WIF
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,362 @@
|
|
1
|
+
# A collection of routines dealing with parsing and writing protocol messages.
|
2
|
+
# Various structures have a variable-length data prepended by a length prefix which is itself of variable length.
|
3
|
+
# This length prefix is a variable-length integer, varint (aka "CompactSize").
|
4
|
+
#
|
5
|
+
# NB. varint refers to https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer and is what Satoshi called "CompactSize"
|
6
|
+
# BitcoinQT has later added even more compact format called CVarInt to use in its local block storage. CVarInt is not implemented here.
|
7
|
+
#
|
8
|
+
# Value Storage length Format
|
9
|
+
# < 0xfd 1 uint8_t
|
10
|
+
# <= 0xffff 3 0xfd followed by the value as little endian uint16_t
|
11
|
+
# <= 0xffffffff 5 0xfe followed by the value as little endian uint32_t
|
12
|
+
# > 0xffffffff 9 0xff followed by the value as little endian uint64_t
|
13
|
+
#
|
14
|
+
module BTC
|
15
|
+
module WireFormat
|
16
|
+
extend self
|
17
|
+
|
18
|
+
# Reads varint from data or stream.
|
19
|
+
# Either data or stream must be present (and only one of them).
|
20
|
+
# Optional offset is useful when reading from data.
|
21
|
+
# Returns [value, length] where value is a decoded integer value and length is number of bytes read (including offset bytes).
|
22
|
+
# Value may be nil when decoding failed (length might be zero or greater, depending on how much data was consumed before failing).
|
23
|
+
# Usage:
|
24
|
+
# i, _ = read_varint(data: buffer, offset: 42)
|
25
|
+
# i, _ = read_varint(stream: File.open('someblock','r'))
|
26
|
+
def read_varint(data: nil, stream: nil, offset: 0)
|
27
|
+
if data && !stream
|
28
|
+
return [nil, 0] if data.bytesize < 1 + offset
|
29
|
+
|
30
|
+
bytes = BTC::Data.bytes_from_data(data, offset: offset, limit: 9) # we don't need more than 9 bytes.
|
31
|
+
|
32
|
+
byte = bytes[0]
|
33
|
+
|
34
|
+
if byte < 0xfd
|
35
|
+
return [byte, offset + 1]
|
36
|
+
elsif byte == 0xfd
|
37
|
+
return [nil, 1] if data.bytesize < 3 + offset # 1 byte prefix, 2 bytes uint16
|
38
|
+
return [bytes[1] +
|
39
|
+
bytes[2]*256, offset + 3]
|
40
|
+
elsif byte == 0xfe
|
41
|
+
return [nil, 1] if data.bytesize < 5 + offset # 1 byte prefix, 4 bytes uint32
|
42
|
+
return [bytes[1] +
|
43
|
+
bytes[2]*256 +
|
44
|
+
bytes[3]*256*256 +
|
45
|
+
bytes[4]*256*256*256, offset + 5]
|
46
|
+
elsif byte == 0xff
|
47
|
+
return [nil, 1] if data.bytesize < 9 + offset # 1 byte prefix, 8 bytes uint64
|
48
|
+
return [bytes[1] +
|
49
|
+
bytes[2]*256 +
|
50
|
+
bytes[3]*256*256 +
|
51
|
+
bytes[4]*256*256*256 +
|
52
|
+
bytes[5]*256*256*256*256 +
|
53
|
+
bytes[6]*256*256*256*256*256 +
|
54
|
+
bytes[7]*256*256*256*256*256*256 +
|
55
|
+
bytes[8]*256*256*256*256*256*256*256, offset + 9]
|
56
|
+
end
|
57
|
+
|
58
|
+
elsif stream && !data
|
59
|
+
|
60
|
+
if stream.eof?
|
61
|
+
raise ArgumentError, "Can't parse varint from stream because it is already closed."
|
62
|
+
end
|
63
|
+
|
64
|
+
if offset > 0
|
65
|
+
buf = stream.read(offset)
|
66
|
+
return [nil, 0] if !buf
|
67
|
+
return [nil, buf.bytesize] if buf.bytesize < offset
|
68
|
+
end
|
69
|
+
|
70
|
+
prefix = stream.read(1)
|
71
|
+
|
72
|
+
return [nil, offset] if !prefix || prefix.bytesize == 0
|
73
|
+
|
74
|
+
byte = prefix.bytes[0]
|
75
|
+
|
76
|
+
if byte < 0xfd
|
77
|
+
return [byte, offset + 1]
|
78
|
+
elsif byte == 0xfd
|
79
|
+
buf = stream.read(2)
|
80
|
+
return [nil, offset + 1] if !buf
|
81
|
+
return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 2
|
82
|
+
return [buf.unpack("v").first, offset + 3]
|
83
|
+
elsif byte == 0xfe
|
84
|
+
buf = stream.read(4)
|
85
|
+
return [nil, offset + 1] if !buf
|
86
|
+
return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 4
|
87
|
+
return [buf.unpack("V").first, offset + 5]
|
88
|
+
elsif byte == 0xff
|
89
|
+
buf = stream.read(8)
|
90
|
+
return [nil, offset + 1] if !buf
|
91
|
+
return [nil, offset + 1 + buf.bytesize] if buf.bytesize < 8
|
92
|
+
return [buf.unpack("Q<").first, offset + 9]
|
93
|
+
end
|
94
|
+
|
95
|
+
else
|
96
|
+
raise ArgumentError, "Either data or stream must be specified."
|
97
|
+
end
|
98
|
+
end # read_varint
|
99
|
+
|
100
|
+
# Encodes integer and returns its binary varint representation.
|
101
|
+
def encode_varint(i)
|
102
|
+
raise ArgumentError, "int must be present" if !i
|
103
|
+
raise ArgumentError, "int must be non-negative" if i < 0
|
104
|
+
|
105
|
+
buf = if i < 0xfd
|
106
|
+
[i].pack("C")
|
107
|
+
elsif i <= 0xffff
|
108
|
+
[0xfd, i].pack("Cv")
|
109
|
+
elsif i <= 0xffffffff
|
110
|
+
[0xfe, i].pack("CV")
|
111
|
+
elsif i <= 0xffffffffffffffff
|
112
|
+
[0xff, i].pack("CQ<")
|
113
|
+
else
|
114
|
+
raise ArgumentError, "Does not support integers larger 0xffffffffffffffff (i = 0x#{i.to_s(16)})"
|
115
|
+
end
|
116
|
+
|
117
|
+
buf
|
118
|
+
end
|
119
|
+
|
120
|
+
# Encodes integer and returns its binary varint representation.
|
121
|
+
# If data is given, appends to a data.
|
122
|
+
# If stream is given, writes to a stream.
|
123
|
+
def write_varint(i, data: nil, stream: nil)
|
124
|
+
buf = encode_varint(i)
|
125
|
+
data << buf if data
|
126
|
+
stream.write(buf) if stream
|
127
|
+
buf
|
128
|
+
end
|
129
|
+
|
130
|
+
# Reads variable-length string from data buffer or IO stream.
|
131
|
+
# Either data or stream must be present (and only one of them).
|
132
|
+
# Returns [string, length] where length is a number of bytes read (includes length prefix and offset bytes).
|
133
|
+
# In case of failure, returns [nil, length] where length is a number of bytes read before the error was encountered.
|
134
|
+
def read_string(data: nil, stream: nil, offset: 0)
|
135
|
+
if data && !stream
|
136
|
+
|
137
|
+
string_length, read_length = read_varint(data: data, offset: offset)
|
138
|
+
|
139
|
+
# If failed to read the length prefix, return nil.
|
140
|
+
return [nil, read_length] if !string_length
|
141
|
+
|
142
|
+
# Check if we have enough bytes to read the string itself
|
143
|
+
return [nil, read_length] if data.bytesize < read_length + string_length
|
144
|
+
|
145
|
+
string = BTC::Data.ensure_binary_encoding(data)[read_length, string_length]
|
146
|
+
|
147
|
+
return [string, read_length + string_length]
|
148
|
+
|
149
|
+
elsif stream && !data
|
150
|
+
|
151
|
+
string_length, read_length = read_varint(stream: stream, offset: offset)
|
152
|
+
return [nil, read_length] if !string_length
|
153
|
+
|
154
|
+
buf = stream.read(string_length)
|
155
|
+
|
156
|
+
return [nil, read_length] if !buf
|
157
|
+
return [nil, read_length + buf.bytesize] if buf.bytesize < string_length
|
158
|
+
|
159
|
+
return [buf, read_length + buf.bytesize]
|
160
|
+
else
|
161
|
+
raise ArgumentError, "Either data or stream must be specified."
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns the binary representation of the var-length string.
|
166
|
+
def encode_string(string)
|
167
|
+
raise ArgumentError, "String must be present" if !string
|
168
|
+
encode_varint(string.bytesize) + BTC::Data.ensure_binary_encoding(string)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Writes variable-length string to a data buffer or IO stream.
|
172
|
+
# If data is given, appends to a data.
|
173
|
+
# If stream is given, writes to a stream.
|
174
|
+
# Returns the binary representation of the var-length string.
|
175
|
+
def write_string(string, data: nil, stream: nil)
|
176
|
+
raise ArgumentError, "String must be present" if !string
|
177
|
+
|
178
|
+
intbuf = write_varint(string.bytesize, data: data, stream: stream)
|
179
|
+
|
180
|
+
stringbuf = BTC::Data.ensure_binary_encoding(string)
|
181
|
+
|
182
|
+
data << stringbuf if data
|
183
|
+
stream.write(stringbuf) if stream
|
184
|
+
|
185
|
+
intbuf + stringbuf
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# LEB128 encoding used in Open Assets protocol
|
190
|
+
|
191
|
+
# Decodes an unsigned integer encoded in LEB128.
|
192
|
+
# Returns `[value, length]` where `value` is an integer decoded from LEB128 and `length`
|
193
|
+
# is a number of bytes read (includes length prefix and offset bytes).
|
194
|
+
def read_uleb128(data: nil, stream: nil, offset: 0)
|
195
|
+
if (data && stream) || (!data && !stream)
|
196
|
+
raise ArgumentError, "Either data or stream must be specified."
|
197
|
+
end
|
198
|
+
if data
|
199
|
+
data = BTC::Data.ensure_binary_encoding(data)
|
200
|
+
end
|
201
|
+
if stream
|
202
|
+
if stream.eof?
|
203
|
+
raise ArgumentError, "Can't read LEB128 from stream because it is already closed."
|
204
|
+
end
|
205
|
+
if offset > 0
|
206
|
+
buf = stream.read(offset)
|
207
|
+
return [nil, 0] if !buf
|
208
|
+
return [nil, buf.bytesize] if buf.bytesize < offset
|
209
|
+
end
|
210
|
+
end
|
211
|
+
result = 0
|
212
|
+
shift = 0
|
213
|
+
while true
|
214
|
+
byte = if data
|
215
|
+
return [nil, offset] if data.bytesize < 1 + offset
|
216
|
+
BTC::Data.bytes_from_data(data, offset: offset, limit: 1)[0]
|
217
|
+
elsif stream
|
218
|
+
buf = stream.read(1)
|
219
|
+
return [nil, offset] if !buf || buf.bytesize == 0
|
220
|
+
buf.bytes[0]
|
221
|
+
end
|
222
|
+
result |= (byte & 0x7f) << shift
|
223
|
+
break if byte & 0x80 == 0
|
224
|
+
shift += 7
|
225
|
+
offset += 1
|
226
|
+
end
|
227
|
+
[result, offset + 1]
|
228
|
+
end
|
229
|
+
|
230
|
+
# Encodes an unsigned integer using LEB128.
|
231
|
+
def encode_uleb128(value)
|
232
|
+
raise ArgumentError, "Signed integers are not supported" if value < 0
|
233
|
+
return "\x00" if value == 0
|
234
|
+
bytes = []
|
235
|
+
while value != 0
|
236
|
+
byte = value & 0b01111111 # 0x7f
|
237
|
+
value >>= 7
|
238
|
+
if value != 0
|
239
|
+
byte |= 0b10000000 # 0x80
|
240
|
+
end
|
241
|
+
bytes << byte
|
242
|
+
end
|
243
|
+
return BTC::Data.data_from_bytes(bytes)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Writes an unsigned integer encoded in LEB128 to a data buffer or a stream.
|
247
|
+
# Returns LEB128-encoded binary string.
|
248
|
+
def write_uleb128(value, data: nil, stream: nil)
|
249
|
+
raise ArgumentError, "Integer must be present" if !value
|
250
|
+
buf = encode_uleb128(value)
|
251
|
+
data << buf if data
|
252
|
+
stream.write(buf) if stream
|
253
|
+
buf
|
254
|
+
end
|
255
|
+
|
256
|
+
|
257
|
+
|
258
|
+
PACK_FORMAT_UINT8 = "C".freeze
|
259
|
+
PACK_FORMAT_INT8 = "c".freeze
|
260
|
+
PACK_FORMAT_UINT16LE = "S<".freeze
|
261
|
+
PACK_FORMAT_INT16LE = "s<".freeze
|
262
|
+
PACK_FORMAT_UINT32LE = "L<".freeze
|
263
|
+
PACK_FORMAT_INT32LE = "l<".freeze
|
264
|
+
PACK_FORMAT_UINT32BE = "L>".freeze # used in BIP32
|
265
|
+
PACK_FORMAT_UINT64LE = "Q<".freeze
|
266
|
+
PACK_FORMAT_INT64LE = "q<".freeze
|
267
|
+
|
268
|
+
# These read fixed-length integer in appropriate format ("le" stands for "little-endian")
|
269
|
+
# Return [value, length] or [nil, length] just like #read_varint method (see above).
|
270
|
+
def read_uint8(data: nil, stream: nil, offset: 0)
|
271
|
+
_read_fixint(name: :uint8, length: 1, pack_format: PACK_FORMAT_UINT8, data: data, stream: stream, offset: offset)
|
272
|
+
end
|
273
|
+
|
274
|
+
def read_int8(data: nil, stream: nil, offset: 0)
|
275
|
+
_read_fixint(name: :int8, length: 1, pack_format: PACK_FORMAT_INT8, data: data, stream: stream, offset: offset)
|
276
|
+
end
|
277
|
+
|
278
|
+
def read_uint16le(data: nil, stream: nil, offset: 0)
|
279
|
+
_read_fixint(name: :uint16le, length: 2, pack_format: PACK_FORMAT_UINT16LE, data: data, stream: stream, offset: offset)
|
280
|
+
end
|
281
|
+
|
282
|
+
def read_int16le(data: nil, stream: nil, offset: 0)
|
283
|
+
_read_fixint(name: :int16le, length: 2, pack_format: PACK_FORMAT_INT16LE, data: data, stream: stream, offset: offset)
|
284
|
+
end
|
285
|
+
|
286
|
+
def read_uint32le(data: nil, stream: nil, offset: 0)
|
287
|
+
_read_fixint(name: :uint32le, length: 4, pack_format: PACK_FORMAT_UINT32LE, data: data, stream: stream, offset: offset)
|
288
|
+
end
|
289
|
+
|
290
|
+
def read_int32le(data: nil, stream: nil, offset: 0)
|
291
|
+
_read_fixint(name: :int32le, length: 4, pack_format: PACK_FORMAT_INT32LE, data: data, stream: stream, offset: offset)
|
292
|
+
end
|
293
|
+
|
294
|
+
def read_uint32be(data: nil, stream: nil, offset: 0) # used in BIP32
|
295
|
+
_read_fixint(name: :uint32be, length: 4, pack_format: PACK_FORMAT_UINT32BE, data: data, stream: stream, offset: offset)
|
296
|
+
end
|
297
|
+
|
298
|
+
def read_uint64le(data: nil, stream: nil, offset: 0)
|
299
|
+
_read_fixint(name: :uint64le, length: 8, pack_format: PACK_FORMAT_UINT64LE, data: data, stream: stream, offset: offset)
|
300
|
+
end
|
301
|
+
|
302
|
+
def read_int64le(data: nil, stream: nil, offset: 0)
|
303
|
+
_read_fixint(name: :int64le, length: 8, pack_format: PACK_FORMAT_INT64LE, data: data, stream: stream, offset: offset)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Encode int into one of the formats
|
307
|
+
def encode_uint8(int); [int].pack(PACK_FORMAT_UINT8); end
|
308
|
+
def encode_int8(int); [int].pack(PACK_FORMAT_INT8); end
|
309
|
+
def encode_uint16le(int); [int].pack(PACK_FORMAT_UINT16LE); end
|
310
|
+
def encode_int16le(int); [int].pack(PACK_FORMAT_INT16LE); end
|
311
|
+
def encode_uint32le(int); [int].pack(PACK_FORMAT_UINT32LE); end
|
312
|
+
def encode_int32le(int); [int].pack(PACK_FORMAT_INT32LE); end
|
313
|
+
def encode_uint32be(int); [int].pack(PACK_FORMAT_UINT32BE); end # used in BIP32
|
314
|
+
def encode_uint64le(int); [int].pack(PACK_FORMAT_UINT64LE); end
|
315
|
+
def encode_int64le(int); [int].pack(PACK_FORMAT_INT64LE); end
|
316
|
+
|
317
|
+
|
318
|
+
protected
|
319
|
+
|
320
|
+
def _read_fixint(name: nil, length: nil, pack_format: nil, data: nil, stream: nil, offset: 0)
|
321
|
+
if data && !stream
|
322
|
+
|
323
|
+
if data.bytesize < offset + length
|
324
|
+
Diagnostics.current.add_message("BTC::WireFormat#read_#{name}: Not enough bytes to read #{name} in binary string.")
|
325
|
+
return [nil, 0]
|
326
|
+
end
|
327
|
+
|
328
|
+
if offset > 0
|
329
|
+
pack_format = "@#{offset}" + pack_format
|
330
|
+
end
|
331
|
+
|
332
|
+
return [data.unpack(pack_format).first, offset + length]
|
333
|
+
|
334
|
+
elsif stream && !data
|
335
|
+
|
336
|
+
if offset > 0
|
337
|
+
buf = stream.read(offset)
|
338
|
+
return [nil, 0] if !buf
|
339
|
+
return [nil, buf.bytesize] if buf.bytesize < offset
|
340
|
+
end
|
341
|
+
|
342
|
+
buf = stream.read(length)
|
343
|
+
|
344
|
+
if !buf
|
345
|
+
Diagnostics.current.add_message("BTC::WireFormat#read_#{name}: Failed to read #{name} from stream.")
|
346
|
+
return [nil, offset]
|
347
|
+
end
|
348
|
+
|
349
|
+
if buf.bytesize < length
|
350
|
+
Diagnostics.current.add_message("BTC::WireFormat#read_#{name}: Not enough bytes to read #{name} from stream.")
|
351
|
+
return [nil, offset + buf.bytesize]
|
352
|
+
end
|
353
|
+
|
354
|
+
return [buf.unpack(pack_format).first, offset + length]
|
355
|
+
|
356
|
+
else
|
357
|
+
raise ArgumentError, "BTC::WireFormat#read_#{name}: Either data or stream must be specified."
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
end
|
data/lib/btcruby.rb
CHANGED
@@ -1,2 +1,44 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
|
2
|
+
require 'ffi' # gem install ffi
|
3
|
+
|
4
|
+
# Tip: import 'btcruby/extensions' to enable extensions to standard classes (e.g. String#to_hex)
|
5
|
+
# Extensions are not imported by default to avoid conflicts with other libraries.
|
6
|
+
|
7
|
+
require_relative 'btcruby/version.rb'
|
8
|
+
require_relative 'btcruby/errors.rb'
|
9
|
+
require_relative 'btcruby/diagnostics.rb'
|
10
|
+
require_relative 'btcruby/safety.rb'
|
11
|
+
require_relative 'btcruby/hash_functions.rb'
|
12
|
+
require_relative 'btcruby/data.rb'
|
13
|
+
require_relative 'btcruby/openssl.rb'
|
14
|
+
require_relative 'btcruby/big_number.rb'
|
15
|
+
require_relative 'btcruby/base58.rb'
|
16
|
+
|
17
|
+
require_relative 'btcruby/constants.rb'
|
18
|
+
require_relative 'btcruby/currency_formatter.rb'
|
19
|
+
require_relative 'btcruby/network.rb'
|
20
|
+
require_relative 'btcruby/address.rb'
|
21
|
+
require_relative 'btcruby/wif.rb'
|
22
|
+
require_relative 'btcruby/key.rb'
|
23
|
+
require_relative 'btcruby/keychain.rb'
|
24
|
+
require_relative 'btcruby/wire_format.rb'
|
25
|
+
require_relative 'btcruby/hash_id.rb'
|
26
|
+
require_relative 'btcruby/transaction.rb'
|
27
|
+
require_relative 'btcruby/transaction_input.rb'
|
28
|
+
require_relative 'btcruby/transaction_output.rb'
|
29
|
+
require_relative 'btcruby/transaction_outpoint.rb'
|
30
|
+
require_relative 'btcruby/script.rb'
|
31
|
+
require_relative 'btcruby/opcode.rb'
|
32
|
+
require_relative 'btcruby/signature_hashtype.rb'
|
33
|
+
require_relative 'btcruby/transaction_builder.rb'
|
34
|
+
require_relative 'btcruby/proof_of_work.rb'
|
35
|
+
require_relative 'btcruby/block_header.rb'
|
36
|
+
require_relative 'btcruby/block.rb'
|
37
|
+
require_relative 'btcruby/open_assets.rb'
|
38
|
+
|
39
|
+
# TODO:
|
40
|
+
# require_relative 'btcruby/curve_point.rb'
|
41
|
+
# require_relative 'btcruby/script_machine.rb'
|
42
|
+
# require_relative 'btcruby/merkle_block.rb'
|
43
|
+
# require_relative 'btcruby/bloom_filter.rb'
|
44
|
+
# require_relative 'btcruby/processor.rb'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Creating a P2SH multisig address
|
2
|
+
# --------------------------------
|
3
|
+
#
|
4
|
+
# To create a P2SH multisig address you will need a set of public keys.
|
5
|
+
# In the example below we generate three random keys and compose 2-of-3 multisig script
|
6
|
+
# which is then transformed into a P2SH address. To redeem from this address you will need
|
7
|
+
# not only two signatures, but also the original multisig script.
|
8
|
+
|
9
|
+
require_relative "../lib/btcruby.rb"
|
10
|
+
|
11
|
+
keys = [BTC::Key.random, BTC::Key.random, BTC::Key.random]
|
12
|
+
pubkeys = keys.map(&:public_key)
|
13
|
+
|
14
|
+
multisig_script = BTC::Script.multisig_script(public_keys: pubkeys, signatures_required: 2)
|
15
|
+
puts multisig_script.to_s # => "OP_2 03e4e14a... 03b4b3f7... 030fa2ec... OP_3 OP_CHECKMULTISIG"
|
16
|
+
|
17
|
+
p2sh_script = multisig_script.p2sh_script
|
18
|
+
puts p2sh_script.to_s # => "OP_HASH160 26f5b7ad4e890c07b8c55fc551e39d6693c5e984 OP_EQUAL"
|
19
|
+
|
20
|
+
address = p2sh_script.standard_address
|
21
|
+
puts address.to_s # => 35F1xaoodzRZUBJHi6TgA85qPjXQcW8XsQ
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Creating a transaction manually
|
2
|
+
# -------------------------------
|
3
|
+
#
|
4
|
+
# To manually create a transaction, you will need to specify raw inputs,
|
5
|
+
# compute the signature and compose a signature script for each input and
|
6
|
+
# take care of calculating a change amount correctly.
|
7
|
+
|
8
|
+
require_relative "../lib/btcruby.rb"
|
9
|
+
|
10
|
+
include BTC
|
11
|
+
|
12
|
+
tx = Transaction.new
|
13
|
+
|
14
|
+
# 1. Add a raw input with previous transaction ID and output index.
|
15
|
+
tx.add_input(TransactionInput.new(
|
16
|
+
previous_id: "aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31",
|
17
|
+
previous_index: 0))
|
18
|
+
|
19
|
+
# 2. Add a raw output with a script
|
20
|
+
tx.add_output(TransactionOutput.new(
|
21
|
+
value: 100_000,
|
22
|
+
script: PublicKeyAddress.with_string("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG").script))
|
23
|
+
|
24
|
+
# 3. Get the private key from WIF
|
25
|
+
key = Key.new(wif: "L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy")
|
26
|
+
|
27
|
+
# 4. Sign the input (assuming it links to an output with address 18oxCAnbuKHDjP7KzLBDj8mLjggDBjE1Q9)
|
28
|
+
hashtype = BTC::SIGHASH_ALL
|
29
|
+
sighash = tx.signature_hash(input_index: 0,
|
30
|
+
output_script: PublicKeyAddress.with_string("18oxCAnbuKHDjP7KzLBDj8mLjggDBjE1Q9").script,
|
31
|
+
hash_type: hashtype)
|
32
|
+
tx.inputs[0].signature_script = Script.new << (key.ecdsa_signature(sighash) + WireFormat.encode_uint8(hashtype)) << key.public_key
|
33
|
+
|
34
|
+
# Get transaction data and broadcast it
|
35
|
+
puts "Binary transaction:"
|
36
|
+
puts tx.data # => raw binary data
|
37
|
+
puts "Hex transaction:"
|
38
|
+
puts tx.to_hex # hex-encoded data
|
39
|
+
# => 0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa
|
40
|
+
# 000000006a473044022039148258144202301221a305adb38ce0a182ecb4055c6015cdd735
|
41
|
+
# 8372d7ad6d022008aa259c87177f0e4e887dd0947c57fd140eb8f8a826f14ef8389dbc26ef
|
42
|
+
# a7b20121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59f
|
43
|
+
# ffffffff01a0860100000000001976a9147ab89f9fae3f8043dcee5f7b5467a0f0a6e2f7e1
|
44
|
+
# 88ac00000000
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Generating an address
|
2
|
+
# ---------------------
|
3
|
+
#
|
4
|
+
# This example demonstrates how to generate a key and get its address.
|
5
|
+
|
6
|
+
require_relative "../lib/btcruby.rb"
|
7
|
+
|
8
|
+
key = BTC::Key.random
|
9
|
+
|
10
|
+
puts key.to_wif # private key in WIF format
|
11
|
+
# => L4RqZhbn2VsVgy2wCWW8kUPpA4xEkH7WbfPtj1MdFug5MayHzLeT
|
12
|
+
|
13
|
+
puts key.address.to_s # public address
|
14
|
+
# => 1MFqAcAxNsAKj5e6yksZCCyfNukSdDGsEY
|
15
|
+
|
16
|
+
puts key.to_wif(network: BTC::Network.testnet)
|
17
|
+
# => cUnq2cbdTZZkrQWCavKG7ntsnJFeQjDCfhYMqRp8m2L5cL1yHDmc
|
18
|
+
|
19
|
+
puts key.address(network: BTC::Network.testnet).to_s
|
20
|
+
# => n1mnTfFwBtbaWC7ihKqw28BzEuM9YqxRyw
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Using transaction builder
|
2
|
+
# -------------------------
|
3
|
+
#
|
4
|
+
# Transaction builder helps composing arbitrary transactions using just keys or unspent outputs.
|
5
|
+
# It takes care of computing a proper change amount, adding fees and signing inputs.
|
6
|
+
# It is also highly customizable, so you may use it for very complex transactions.
|
7
|
+
|
8
|
+
require_relative "../lib/btcruby.rb"
|
9
|
+
|
10
|
+
builder = BTC::TransactionBuilder.new
|
11
|
+
|
12
|
+
# 1. Provide a list of address to get unspent outputs from.
|
13
|
+
# If address is a WIF instance, it will be used to sign corresponding input
|
14
|
+
# If address is a public address (or P2SH), its input will remain unsigned.
|
15
|
+
builder.input_addresses = [ BTC::Key.new(wif: "L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy").to_wif_object ]
|
16
|
+
|
17
|
+
# 2. Use external API (e.g. Chain.com) to fetch unspent outputs for the input addresses.
|
18
|
+
# In this example we simply hard-code a single unspent output.
|
19
|
+
# Note: transaction ID and output index must be provided.
|
20
|
+
builder.unspent_outputs_provider_block = lambda do |addresses, outputs_amount, outputs_size, fee|
|
21
|
+
txout = BTC::TransactionOutput.new(
|
22
|
+
value: 50_000,
|
23
|
+
script: BTC::PublicKeyAddress.with_string("17XBj6iFEsf8kzDMGQk5ghZipxX49VXuaV").script,
|
24
|
+
transaction_id: "115e8f72f39fad874cfab0deed11a80f24f967a84079fb56ddf53ea02e308986",
|
25
|
+
index: 0
|
26
|
+
)
|
27
|
+
[ txout ]
|
28
|
+
end
|
29
|
+
|
30
|
+
# 3. Specify payment address and amount
|
31
|
+
builder.outputs = [ BTC::TransactionOutput.new(
|
32
|
+
value: 10_000,
|
33
|
+
script: BTC::PublicKeyAddress.with_string("17XBj6iFEsf8kzDMGQk5ghZipxX49VXuaV").script) ]
|
34
|
+
|
35
|
+
# 4. Specify the change address
|
36
|
+
builder.change_address = BTC::PublicKeyAddress.with_string("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
|
37
|
+
|
38
|
+
# 5. Build the transaction and broadcast it.
|
39
|
+
result = builder.build
|
40
|
+
tx = result.transaction
|
41
|
+
puts tx.to_hex
|
42
|
+
|
43
|
+
# => 01000000018689302ea03ef5dd56fb7940a867f9240fa811eddeb0fa4c87ad9ff3728f5e11
|
44
|
+
# 000000006b483045022100e280f71106a84a4a1b1a2035eae70266eb53630beab2b59cc8cf
|
45
|
+
# f40b1a5bdbb902201dcbae9bb12730fe5563dc37e3a33e064f2efa78ba0af5c0179187aece
|
46
|
+
# 180b6c0121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b5
|
47
|
+
# 9fffffffff0210270000000000001976a91447862fe165e6121af80d5dde1ecb478ed17056
|
48
|
+
# 5b88ac30750000000000001976a9147ab89f9fae3f8043dcee5f7b5467a0f0a6e2f7e188ac
|
49
|
+
# 00000000
|