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,464 @@
|
|
1
|
+
# Implementation of BIP32 "Hierarchical Deterministic Wallets" (HD Wallets)
|
2
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
3
|
+
#
|
4
|
+
# Keychain encapsulates either a pair of "extended" keys (private and public),
|
5
|
+
# or only a public extended key.
|
6
|
+
# "Extended key" means the key (private or public) is accompanied by an extra
|
7
|
+
# 256 bits of entropy called "chain code" and some metadata about its position
|
8
|
+
# in a tree of keys (depth, parent fingerprint, index).
|
9
|
+
#
|
10
|
+
# Keychain has two modes of operation:
|
11
|
+
#
|
12
|
+
# 1. "Normal derivation" which allows to derive public keys separately
|
13
|
+
# from the private ones (internally i below 0x80000000).
|
14
|
+
#
|
15
|
+
# 2. "Hardened derivation" which derives only private keys (for i >= 0x80000000).
|
16
|
+
#
|
17
|
+
# Derivation can be treated as a single key or as a new branch of keychains.
|
18
|
+
# BIP43 and BIP44 propose a way for wallets to organize streams of keys using Keychain.
|
19
|
+
#
|
20
|
+
module BTC
|
21
|
+
class Keychain
|
22
|
+
|
23
|
+
MAX_INDEX = 0x7fffffff
|
24
|
+
|
25
|
+
PUBLIC_MAINNET_VERSION = 0x0488B21E # xpub
|
26
|
+
PRIVATE_MAINNET_VERSION = 0x0488ADE4 # xprv
|
27
|
+
PUBLIC_TESNET_VERSION = 0x043587CF # tpub
|
28
|
+
PRIVATE_TESTNET_VERSION = 0x04358394 # tprv
|
29
|
+
|
30
|
+
# Instance of BTC::Key that is a "head" of this keychain.
|
31
|
+
# If the keychain is public-only, key does not have a private component.
|
32
|
+
attr_reader :key
|
33
|
+
|
34
|
+
# 32-byte binary "chain code" string.
|
35
|
+
attr_reader :chain_code
|
36
|
+
|
37
|
+
# Base58Check-encoded extended public key.
|
38
|
+
attr_reader :extended_public_key
|
39
|
+
attr_reader :xpub
|
40
|
+
|
41
|
+
# Base58Check-encoded extended private key.
|
42
|
+
# Returns nil if it's a public-only keychain.
|
43
|
+
attr_reader :extended_private_key
|
44
|
+
attr_reader :xprv
|
45
|
+
|
46
|
+
# 160-bit binary identifier (aka "hash") of the keychain (RIPEMD160(SHA256(pubkey)))
|
47
|
+
attr_reader :identifier
|
48
|
+
|
49
|
+
# Base58Check-encoded identifier.
|
50
|
+
attr_reader :identifier_base58
|
51
|
+
|
52
|
+
# Fingerprint of the keychain (integer).
|
53
|
+
attr_reader :fingerprint
|
54
|
+
|
55
|
+
# Fingerprint of the parent keychain (integer).
|
56
|
+
# For master keychain it is always 0.
|
57
|
+
attr_reader :parent_fingerprint
|
58
|
+
|
59
|
+
# Index in the parent keychain (integer).
|
60
|
+
# Returns 0 for master keychain.
|
61
|
+
attr_reader :index
|
62
|
+
|
63
|
+
# Depth in the hierarchy (integer).
|
64
|
+
# Returns 0 for master keychain.
|
65
|
+
attr_reader :depth
|
66
|
+
|
67
|
+
# Network.mainnet or Network.testnet.
|
68
|
+
# Default is BTC::Network.default (mainnet if not overriden).
|
69
|
+
attr_accessor :network
|
70
|
+
|
71
|
+
# Returns true if the keychain can derive private keys (opposite of #public?).
|
72
|
+
def private?
|
73
|
+
!!@private_key
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true if the keychain can only derive public keys (opposite of #private?).
|
77
|
+
def public?
|
78
|
+
!@private_key
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns true if the keychain was derived via hardened derivation from its parent.
|
82
|
+
# This means internally parameter i = 0x80000000 | self.index.
|
83
|
+
# For the master keychain index is zero and hardened? returns false.
|
84
|
+
def hardened?
|
85
|
+
!!@hardened
|
86
|
+
end
|
87
|
+
|
88
|
+
def network
|
89
|
+
@network
|
90
|
+
end
|
91
|
+
|
92
|
+
def network=(network)
|
93
|
+
@network = network || Network.default
|
94
|
+
@extended_public_key = nil
|
95
|
+
@extended_private_key = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns true if this keychain is intended for mainnet.
|
99
|
+
def mainnet?
|
100
|
+
network.mainnet?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns true if this keychain is intended for testnet.
|
104
|
+
def testnet?
|
105
|
+
network.testnet?
|
106
|
+
end
|
107
|
+
|
108
|
+
def key
|
109
|
+
@key ||= if @private_key
|
110
|
+
BTC::Key.new(private_key: @private_key, public_key_compressed: true)
|
111
|
+
else
|
112
|
+
BTC::Key.new(public_key: @public_key)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def xpub
|
117
|
+
extended_public_key
|
118
|
+
end
|
119
|
+
|
120
|
+
def xprv
|
121
|
+
extended_private_key
|
122
|
+
end
|
123
|
+
|
124
|
+
def extended_public_key
|
125
|
+
@extended_public_key ||= begin
|
126
|
+
prefix = _extended_key_prefix(mainnet? ? PUBLIC_MAINNET_VERSION : PUBLIC_TESNET_VERSION)
|
127
|
+
BTC::Base58.base58check_from_data(prefix + @public_key)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def extended_private_key
|
132
|
+
@extended_private_key ||= begin
|
133
|
+
return nil if !@private_key
|
134
|
+
prefix = _extended_key_prefix(mainnet? ? PRIVATE_MAINNET_VERSION : PRIVATE_TESTNET_VERSION)
|
135
|
+
BTC::Base58.base58check_from_data(prefix + "\x00" + @private_key)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def identifier
|
140
|
+
@identifier ||= BTC.hash160(@public_key)
|
141
|
+
end
|
142
|
+
|
143
|
+
def identifier_base58
|
144
|
+
@identifier_base58 ||= BTC::Base58.base58check_from_data(self.identifier)
|
145
|
+
end
|
146
|
+
|
147
|
+
def fingerprint
|
148
|
+
@fingerprint ||= BTC::WireFormat.read_uint32be(data:self.identifier).first
|
149
|
+
end
|
150
|
+
|
151
|
+
def ==(other)
|
152
|
+
self.identifier == other.identifier &&
|
153
|
+
self.private? == other.private? &&
|
154
|
+
self.mainnet? == other.mainnet?
|
155
|
+
end
|
156
|
+
alias_method :eql?, :==
|
157
|
+
|
158
|
+
def dup
|
159
|
+
Keychain.new(extended_key: self.xprv || self.xpub)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns a copy of the keychain stripped of the private key.
|
163
|
+
# Equivalent to BTC::Keychain.new(xpub: keychain.xpub)
|
164
|
+
def public_keychain
|
165
|
+
self.class.new(_components: [
|
166
|
+
nil, # private_key
|
167
|
+
@public_key,
|
168
|
+
@chain_code,
|
169
|
+
@fingerprint,
|
170
|
+
@parent_fingerprint,
|
171
|
+
@index,
|
172
|
+
@depth,
|
173
|
+
@hardened,
|
174
|
+
@network])
|
175
|
+
end
|
176
|
+
|
177
|
+
# Initializes master keychain from a seed.
|
178
|
+
# This is the "root" keychain of the entire hierarchy.
|
179
|
+
def self.with_seed(seed, network: nil)
|
180
|
+
raise ArgumentError, "Use Keychain.new(seed: ...) instead"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Initializes keychain from a Base58Check-encoded extended public or private key.
|
184
|
+
def self.with_extended_key(xkey)
|
185
|
+
raise ArgumentError, "Use Keychain.new(extended_key/xpub/xprv: ...) instead"
|
186
|
+
end
|
187
|
+
|
188
|
+
# Instantiates Keychain with a binary seed or a base58-encoded extended public/private key.
|
189
|
+
# Usage:
|
190
|
+
# * Keychain.new(seed: ...[, network: ...])
|
191
|
+
# * Keychain.new(extended_key: "xpub..." or "xprv...")
|
192
|
+
# * Keychain.new(xpub: "xpub...")
|
193
|
+
# * Keychain.new(xprv: "xprv...")
|
194
|
+
def initialize(seed: nil,
|
195
|
+
extended_key: nil,
|
196
|
+
xpub: nil,
|
197
|
+
xprv: nil,
|
198
|
+
network: nil,
|
199
|
+
_components: nil # private API
|
200
|
+
)
|
201
|
+
|
202
|
+
if seed
|
203
|
+
init_with_seed(seed, network: network)
|
204
|
+
elsif xkey = (xprv || xpub || extended_key)
|
205
|
+
if network
|
206
|
+
raise ArgumentError, "Cannot use network argument with extended key to initialize BTC::Keychain (network type is already encoded in the key)"
|
207
|
+
end
|
208
|
+
if [xprv, xpub, extended_key].compact.size != 1
|
209
|
+
raise ArgumentError, "Only one of xpub/xprv/extended_key arguments could be used to initialize BTC::Keychain"
|
210
|
+
end
|
211
|
+
init_with_extended_key(xkey)
|
212
|
+
elsif _components
|
213
|
+
init_with_components(*_components)
|
214
|
+
else
|
215
|
+
raise ArgumentError, "Either seed or an extended " if !private_key && !public_key
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def init_with_seed(seed, network: nil)
|
220
|
+
hmac = BTC.hmac_sha512(data: seed, key: "Bitcoin seed".encode(Encoding::ASCII))
|
221
|
+
@private_key = hmac[0,32]
|
222
|
+
@public_key = BTC::Key.new(private_key: @private_key, public_key_compressed: true).public_key
|
223
|
+
@chain_code = hmac[32,32]
|
224
|
+
@fingerprint = nil
|
225
|
+
@parent_fingerprint = 0
|
226
|
+
@index = 0
|
227
|
+
@depth = 0
|
228
|
+
@network = network || BTC::Network.default
|
229
|
+
end
|
230
|
+
private :init_with_seed
|
231
|
+
|
232
|
+
|
233
|
+
def init_with_extended_key(xkey)
|
234
|
+
xkeydata = BTC::Base58.data_from_base58check(xkey)
|
235
|
+
|
236
|
+
if xkeydata.bytesize != 78
|
237
|
+
raise BTCError, "Invalid extended key length: must be 78 bytes (received #{xkeydata.bytesize} bytes)"
|
238
|
+
end
|
239
|
+
|
240
|
+
version, _ = BTC::WireFormat.read_uint32be(data: xkeydata)
|
241
|
+
|
242
|
+
bytes = xkeydata.bytes
|
243
|
+
keyprefix = bytes[45]
|
244
|
+
|
245
|
+
@network = Network.mainnet # not using Network.default because we set it explicitly based on xkey version.
|
246
|
+
|
247
|
+
# Check if it's a private key
|
248
|
+
if version == PRIVATE_MAINNET_VERSION || version == PRIVATE_TESTNET_VERSION
|
249
|
+
|
250
|
+
# Should have 0x00-prefixed private key (1 + 32 bytes).
|
251
|
+
if keyprefix != 0x00
|
252
|
+
raise BTCError, "Extended private key must be padded with 0x00 byte (received #{keyprefix})"
|
253
|
+
end
|
254
|
+
|
255
|
+
@private_key = xkeydata[46, 32]
|
256
|
+
@public_key = BTC::Key.new(private_key: @private_key, public_key_compressed: true).public_key
|
257
|
+
if version == PRIVATE_TESTNET_VERSION
|
258
|
+
@network = Network.testnet
|
259
|
+
end
|
260
|
+
|
261
|
+
elsif version == PUBLIC_MAINNET_VERSION || version == PUBLIC_TESNET_VERSION
|
262
|
+
# Should have a 33-byte public key with non-zero first byte.
|
263
|
+
if keyprefix == 0x00
|
264
|
+
raise BTCError, "Extended public key must have non-zero first byte (received #{keyprefix})"
|
265
|
+
end
|
266
|
+
@public_key = xkeydata[45, 33]
|
267
|
+
if version == PUBLIC_TESNET_VERSION
|
268
|
+
@network = Network.testnet
|
269
|
+
end
|
270
|
+
else
|
271
|
+
raise BTCError, "Unknown extended key version: 0x#{version.to_s(16).rjust(8, "0")}"
|
272
|
+
end
|
273
|
+
|
274
|
+
@depth = bytes[4] # 0 for master keychain, 1 for first level derived keychain etc.
|
275
|
+
|
276
|
+
@parent_fingerprint, _ = BTC::WireFormat.read_uint32be(data: xkeydata, offset: 5)
|
277
|
+
raise BTCError, "Cannot read uint32be parent_fingerprint" if !@parent_fingerprint
|
278
|
+
|
279
|
+
@index, _ = BTC::WireFormat.read_uint32be(data: xkeydata, offset: 9)
|
280
|
+
raise BTCError, "Cannot read uint32be index" if !@index
|
281
|
+
|
282
|
+
@hardened = false
|
283
|
+
if (0x80000000 & index) != 0
|
284
|
+
@index = (~0x80000000) & @index
|
285
|
+
@hardened = true
|
286
|
+
end
|
287
|
+
|
288
|
+
@chain_code = xkeydata[13, 32]
|
289
|
+
end
|
290
|
+
private :init_with_extended_key
|
291
|
+
|
292
|
+
def init_with_components(private_key,
|
293
|
+
public_key,
|
294
|
+
chain_code,
|
295
|
+
fingerprint,
|
296
|
+
parent_fingerprint,
|
297
|
+
index,
|
298
|
+
depth,
|
299
|
+
hardened,
|
300
|
+
network)
|
301
|
+
raise ArgumentError, "Either private or public key must be present" if !private_key && !public_key
|
302
|
+
@private_key = private_key
|
303
|
+
@public_key = public_key || BTC::Key.new(private_key: private_key, public_key_compressed: true).public_key
|
304
|
+
@chain_code = chain_code
|
305
|
+
@fingerprint = fingerprint
|
306
|
+
@parent_fingerprint = parent_fingerprint
|
307
|
+
@index = index
|
308
|
+
@depth = depth
|
309
|
+
@hardened = hardened
|
310
|
+
@network = network
|
311
|
+
end
|
312
|
+
private :init_with_components
|
313
|
+
|
314
|
+
def _extended_key_prefix(version)
|
315
|
+
data = [version,
|
316
|
+
@depth,
|
317
|
+
@parent_fingerprint,
|
318
|
+
@hardened ? (0x80000000 | @index) : @index
|
319
|
+
].pack(
|
320
|
+
WireFormat::PACK_FORMAT_UINT32BE +
|
321
|
+
WireFormat::PACK_FORMAT_UINT8 +
|
322
|
+
WireFormat::PACK_FORMAT_UINT32BE +
|
323
|
+
WireFormat::PACK_FORMAT_UINT32BE)
|
324
|
+
|
325
|
+
data + @chain_code
|
326
|
+
end
|
327
|
+
private :_extended_key_prefix
|
328
|
+
|
329
|
+
# Returns a derived keychain at a given index.
|
330
|
+
# If hardened = true, uses hardened derivation (possible only when private
|
331
|
+
# key is present; otherwise returns nil).
|
332
|
+
# Index must be less of equal BTC::Keychain::MAX_INDEX, otherwise raises ArgumentError.
|
333
|
+
# Raises BTCError for some indexes (when hashing leads to invalid EC points)
|
334
|
+
# which is very rare (chance is below 2^-127), but must be expected.
|
335
|
+
# In such case, simply use next index.
|
336
|
+
# By default, a normal (non-hardened) derivation is used.
|
337
|
+
def derived_keychain(index_or_path, hardened: nil)
|
338
|
+
|
339
|
+
if index_or_path.is_a?(String)
|
340
|
+
if hardened != nil
|
341
|
+
raise ArgumentError, "Ambiguous use of `hardened` flag when deriving keychain with a string path"
|
342
|
+
end
|
343
|
+
return derived_keychain_with_path(index_or_path)
|
344
|
+
end
|
345
|
+
|
346
|
+
index = index_or_path
|
347
|
+
|
348
|
+
raise ArgumentError, "Index must not be nil" if !index
|
349
|
+
|
350
|
+
# As we use explicit "hardened" argument, do not allow higher bit set.
|
351
|
+
if index < 0 || index > 0x7fffffff || (0x80000000 & index) != 0
|
352
|
+
raise ArgumentError, "Index >= 0x80000000 is not valid. Use `hardened: true` argument instead."
|
353
|
+
end
|
354
|
+
|
355
|
+
if hardened && !@private_key
|
356
|
+
# Not possible to derive hardened keychain without a private key.
|
357
|
+
raise BTCError, "Not possible to derive a hardened keychain without a private key (index: #{index})."
|
358
|
+
end
|
359
|
+
|
360
|
+
private_key = nil
|
361
|
+
public_key = nil
|
362
|
+
chain_code = nil
|
363
|
+
|
364
|
+
data = "".b
|
365
|
+
|
366
|
+
if hardened
|
367
|
+
data << "\x00" << @private_key
|
368
|
+
else
|
369
|
+
data << @public_key
|
370
|
+
end
|
371
|
+
|
372
|
+
data << BTC::WireFormat.encode_uint32be(hardened ? (0x80000000 | index) : index)
|
373
|
+
|
374
|
+
digest = BTC.hmac_sha512(data: data, key: @chain_code)
|
375
|
+
|
376
|
+
chain_code = digest[32,32]
|
377
|
+
|
378
|
+
lib = BTC::OpenSSL
|
379
|
+
lib.autorelease do |pool|
|
380
|
+
|
381
|
+
factor = pool.new_bn(digest[0,32])
|
382
|
+
n = lib.group_order
|
383
|
+
|
384
|
+
if lib.BN_cmp(factor, n) >= 0
|
385
|
+
raise BTCError, "Factor for index #{index} is greater than curve order."
|
386
|
+
end
|
387
|
+
|
388
|
+
if @private_key
|
389
|
+
pk = pool.new_bn(@private_key)
|
390
|
+
lib.BN_mod_add_quick(pk, pk, factor, n) # pk = (pk + factor) % n
|
391
|
+
|
392
|
+
# Check for invalid derivation.
|
393
|
+
|
394
|
+
if lib.BN_cmp(pk, pool.new_bn("\x00")) == 0
|
395
|
+
raise BTCError, "Private key is zero for index #{index}."
|
396
|
+
end
|
397
|
+
|
398
|
+
private_key = lib.data_from_bn(pk, min_length: 32)
|
399
|
+
else
|
400
|
+
|
401
|
+
# Convert pubkey to a EC point
|
402
|
+
pubkey_x = pool.new_bn(@public_key)
|
403
|
+
pubkey_point = pool.new_ec_point
|
404
|
+
lib.EC_POINT_bn2point(lib.group, pubkey_x, pubkey_point, pool.bn_ctx)
|
405
|
+
|
406
|
+
# Compute point = pubkey + factor*G
|
407
|
+
point = pool.new_ec_point
|
408
|
+
# /** Computes r = generator * n + q * m
|
409
|
+
# * \param group underlying EC_GROUP object
|
410
|
+
# * \param r EC_POINT object for the result
|
411
|
+
# * \param n BIGNUM with the multiplier for the group generator (optional)
|
412
|
+
# * \param q EC_POINT object with the first factor of the second summand
|
413
|
+
# * \param m BIGNUM with the second factor of the second summand
|
414
|
+
# * \param ctx BN_CTX object (optional)
|
415
|
+
# * \return 1 on success and 0 if an error occured
|
416
|
+
# */
|
417
|
+
# int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
|
418
|
+
lib.EC_POINT_mul(lib.group, point, factor, pubkey_point, pool.new_bn("\x01"), pool.bn_ctx)
|
419
|
+
|
420
|
+
# Check for invalid derivation.
|
421
|
+
if 1 == lib.EC_POINT_is_at_infinity(lib.group, point)
|
422
|
+
raise BTCError, "Resulting point is at infinity for index #{index}."
|
423
|
+
end
|
424
|
+
|
425
|
+
lib.EC_POINT_point2bn(lib.group, point, BTC::OpenSSL::POINT_CONVERSION_COMPRESSED, pubkey_x, pool.bn_ctx)
|
426
|
+
|
427
|
+
public_key = lib.data_from_bn(pubkey_x, required_length: 33)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
self.class.new(_components: [
|
432
|
+
private_key,
|
433
|
+
public_key,
|
434
|
+
chain_code,
|
435
|
+
nil,
|
436
|
+
self.fingerprint,
|
437
|
+
index,
|
438
|
+
@depth + 1,
|
439
|
+
!!hardened,
|
440
|
+
@network])
|
441
|
+
end
|
442
|
+
|
443
|
+
# Returns a derived BTC::Key from this keychain.
|
444
|
+
# This is a convenient way to do keychain.derived_keychain(i).key
|
445
|
+
# If the receiver contains a private key, child key will also contain a private key.
|
446
|
+
# If the receiver contains only a public key, child key will only contain a public key.
|
447
|
+
# (Or nil will be returned if hardened = true.)
|
448
|
+
# By default, a normal (non-hardened) derivation is used.
|
449
|
+
def derived_key(index_or_path, hardened: nil)
|
450
|
+
self.derived_keychain(index_or_path, hardened: hardened).key
|
451
|
+
end
|
452
|
+
|
453
|
+
def derived_keychain_with_path(path)
|
454
|
+
path.gsub(/^m\/?/, "").split("/").inject(self) do |keychain, segment|
|
455
|
+
if segment =~ /^\d+\'?$/
|
456
|
+
keychain.derived_keychain(segment.to_i, hardened: (segment[-1] == "'"))
|
457
|
+
else
|
458
|
+
raise ArgumentError, "Incorrect path format. Should be (\d+'?) separated by '/'."
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
end # Keychain
|
464
|
+
end # BTC
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BTC
|
2
|
+
class Network
|
3
|
+
|
4
|
+
# Name of the network (mainnet or testnet3)
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
# Is testnet (true/false). Alias: testnet?
|
8
|
+
attr_accessor :testnet
|
9
|
+
|
10
|
+
# Is mainnet (true/false). Alias: mainnet?
|
11
|
+
attr_accessor :mainnet
|
12
|
+
|
13
|
+
# Genesis block for this network
|
14
|
+
attr_accessor :genesis_block
|
15
|
+
|
16
|
+
# Genesis block header for this network
|
17
|
+
attr_accessor :genesis_block_header
|
18
|
+
|
19
|
+
# Maximum target (lowest difficulty) for this network.
|
20
|
+
attr_accessor :max_target
|
21
|
+
|
22
|
+
# Default network when it is not explicitly specified.
|
23
|
+
def self.default
|
24
|
+
return (@default ||= self.mainnet)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.default=(network)
|
28
|
+
@default = network
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.mainnet
|
32
|
+
@mainnet ||= begin
|
33
|
+
network = self.new
|
34
|
+
network.name = "mainnet"
|
35
|
+
network.genesis_block = Block.genesis_mainnet
|
36
|
+
network.genesis_block_header = BlockHeader.genesis_mainnet
|
37
|
+
network.max_target = ProofOfWork::MAX_TARGET_MAINNET
|
38
|
+
network
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.testnet
|
43
|
+
@testnet ||= begin
|
44
|
+
network = self.new
|
45
|
+
network.name = "testnet3"
|
46
|
+
network.testnet = true
|
47
|
+
network.genesis_block = Block.genesis_testnet
|
48
|
+
network.genesis_block_header = BlockHeader.genesis_testnet
|
49
|
+
network.max_target = ProofOfWork::MAX_TARGET_TESTNET
|
50
|
+
network
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def testnet?; @testnet || false; end
|
55
|
+
def testnet; @testnet || false; end
|
56
|
+
|
57
|
+
def mainnet?; !testnet?; end
|
58
|
+
def mainnet; !testnet?; end
|
59
|
+
def mainnet=(flag)
|
60
|
+
self.testnet = !flag
|
61
|
+
end
|
62
|
+
|
63
|
+
def dup
|
64
|
+
network = Network.new
|
65
|
+
network.name = self.name.dup
|
66
|
+
network.testnet = self.testnet
|
67
|
+
network.genesis_block = self.genesis_block
|
68
|
+
network.genesis_block_header = self.genesis_block_header
|
69
|
+
network
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|