Class: BTC::Keychain
- Inherits:
-
Object
- Object
- BTC::Keychain
- Defined in:
- lib/btcruby/keychain.rb
Constant Summary collapse
- MAX_INDEX =
0x7fffffff- PUBLIC_MAINNET_VERSION =
xpub
0x0488B21E
- PRIVATE_MAINNET_VERSION =
xprv
0x0488ADE4
- PUBLIC_TESNET_VERSION =
tpub
0x043587CF
- PRIVATE_TESTNET_VERSION =
tprv
0x04358394
Instance Attribute Summary collapse
-
#chain_code ⇒ Object
readonly
32-byte binary “chain code” string.
-
#depth ⇒ Object
readonly
Depth in the hierarchy (integer).
-
#extended_private_key ⇒ Object
readonly
Base58Check-encoded extended private key.
-
#extended_public_key ⇒ Object
readonly
Base58Check-encoded extended public key.
-
#fingerprint ⇒ Object
readonly
Fingerprint of the keychain (integer).
-
#identifier ⇒ Object
readonly
160-bit binary identifier (aka “hash”) of the keychain (RIPEMD160(SHA256(pubkey))).
-
#identifier_base58 ⇒ Object
readonly
Base58Check-encoded identifier.
-
#index ⇒ Object
readonly
Index in the parent keychain (integer).
-
#key ⇒ Object
readonly
Instance of BTC::Key that is a “head” of this keychain.
-
#network ⇒ Object
Network.mainnet or Network.testnet.
-
#parent_fingerprint ⇒ Object
readonly
Fingerprint of the parent keychain (integer).
-
#xprv ⇒ Object
readonly
Returns the value of attribute xprv.
-
#xpub ⇒ Object
readonly
Returns the value of attribute xpub.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#derived_key(index_or_path, hardened: nil) ⇒ Object
Returns a derived BTC::Key from this keychain.
-
#derived_keychain(index_or_path, hardened: nil) ⇒ Object
Returns a derived keychain at a given index.
- #derived_keychain_with_path(path) ⇒ Object
- #dup ⇒ Object
-
#hardened? ⇒ Boolean
Returns true if the keychain was derived via hardened derivation from its parent.
-
#initialize(seed: nil, extended_key: nil, xpub: nil, xprv: nil, network: nil, _components: nil) ⇒ Keychain
constructor
Instantiates Keychain with a binary seed or a base58-encoded extended public/private key.
-
#mainnet? ⇒ Boolean
Returns true if this keychain is intended for mainnet.
-
#private? ⇒ Boolean
Returns true if the keychain can derive private keys (opposite of #public?).
-
#public? ⇒ Boolean
Returns true if the keychain can only derive public keys (opposite of #private?).
-
#public_keychain ⇒ Object
Returns a copy of the keychain stripped of the private key.
-
#testnet? ⇒ Boolean
Returns true if this keychain is intended for testnet.
- #to_s ⇒ Object
Constructor Details
#initialize(seed: nil, extended_key: nil, xpub: nil, xprv: nil, network: nil, _components: nil) ⇒ Keychain
Instantiates Keychain with a binary seed or a base58-encoded extended public/private key. Usage:
-
Keychain.new(seed: …[, network: …])
-
Keychain.new(extended_key: “xpub…” or “xprv…”)
-
Keychain.new(xpub: “xpub…”)
-
Keychain.new(xprv: “xprv…”)
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/btcruby/keychain.rb', line 187 def initialize(seed: nil, extended_key: nil, xpub: nil, xprv: nil, network: nil, _components: nil # private API ) if seed init_with_seed(seed, network: network) elsif xkey = (xprv || xpub || extended_key) if network raise ArgumentError, "Cannot use network argument with extended key to initialize BTC::Keychain (network type is already encoded in the key)" end if [xprv, xpub, extended_key].compact.size != 1 raise ArgumentError, "Only one of xpub/xprv/extended_key arguments could be used to initialize BTC::Keychain" end init_with_extended_key(xkey) elsif _components init_with_components(*_components) else raise ArgumentError, "Either seed or an extended " if !private_key && !public_key end end |
Instance Attribute Details
#chain_code ⇒ Object (readonly)
32-byte binary “chain code” string.
35 36 37 |
# File 'lib/btcruby/keychain.rb', line 35 def chain_code @chain_code end |
#depth ⇒ Object (readonly)
Depth in the hierarchy (integer). Returns 0 for master keychain.
65 66 67 |
# File 'lib/btcruby/keychain.rb', line 65 def depth @depth end |
#extended_private_key ⇒ Object (readonly)
Base58Check-encoded extended private key. Returns nil if it’s a public-only keychain.
43 44 45 |
# File 'lib/btcruby/keychain.rb', line 43 def extended_private_key @extended_private_key end |
#extended_public_key ⇒ Object (readonly)
Base58Check-encoded extended public key.
38 39 40 |
# File 'lib/btcruby/keychain.rb', line 38 def extended_public_key @extended_public_key end |
#fingerprint ⇒ Object (readonly)
Fingerprint of the keychain (integer).
53 54 55 |
# File 'lib/btcruby/keychain.rb', line 53 def fingerprint @fingerprint end |
#identifier ⇒ Object (readonly)
160-bit binary identifier (aka “hash”) of the keychain (RIPEMD160(SHA256(pubkey)))
47 48 49 |
# File 'lib/btcruby/keychain.rb', line 47 def identifier @identifier end |
#identifier_base58 ⇒ Object (readonly)
Base58Check-encoded identifier.
50 51 52 |
# File 'lib/btcruby/keychain.rb', line 50 def identifier_base58 @identifier_base58 end |
#index ⇒ Object (readonly)
Index in the parent keychain (integer). Returns 0 for master keychain.
61 62 63 |
# File 'lib/btcruby/keychain.rb', line 61 def index @index end |
#key ⇒ Object (readonly)
Instance of BTC::Key that is a “head” of this keychain. If the keychain is public-only, key does not have a private component.
32 33 34 |
# File 'lib/btcruby/keychain.rb', line 32 def key @key end |
#network ⇒ Object
Network.mainnet or Network.testnet. Default is BTC::Network.default (mainnet if not overriden).
69 70 71 |
# File 'lib/btcruby/keychain.rb', line 69 def network @network end |
#parent_fingerprint ⇒ Object (readonly)
Fingerprint of the parent keychain (integer). For master keychain it is always 0.
57 58 59 |
# File 'lib/btcruby/keychain.rb', line 57 def parent_fingerprint @parent_fingerprint end |
#xprv ⇒ Object (readonly)
Returns the value of attribute xprv.
44 45 46 |
# File 'lib/btcruby/keychain.rb', line 44 def xprv @xprv end |
#xpub ⇒ Object (readonly)
Returns the value of attribute xpub.
39 40 41 |
# File 'lib/btcruby/keychain.rb', line 39 def xpub @xpub end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
155 156 157 158 159 |
# File 'lib/btcruby/keychain.rb', line 155 def ==(other) self.identifier == other.identifier && self.private? == other.private? && self.mainnet? == other.mainnet? end |
#derived_key(index_or_path, hardened: nil) ⇒ Object
Returns a derived BTC::Key from this keychain. This is a convenient way to do keychain.derived_keychain(i).key If the receiver contains a private key, child key will also contain a private key. If the receiver contains only a public key, child key will only contain a public key. (Or nil will be returned if hardened = true.) By default, a normal (non-hardened) derivation is used.
442 443 444 |
# File 'lib/btcruby/keychain.rb', line 442 def derived_key(index_or_path, hardened: nil) self.derived_keychain(index_or_path, hardened: hardened).key end |
#derived_keychain(index_or_path, hardened: nil) ⇒ Object
Returns a derived keychain at a given index. If hardened = true, uses hardened derivation (possible only when private key is present; otherwise returns nil). Index must be less of equal BTC::Keychain::MAX_INDEX, otherwise raises ArgumentError. Raises BTCError for some indexes (when hashing leads to invalid EC points) which is very rare (chance is below 2^-127), but must be expected. In such case, simply use next index. By default, a normal (non-hardened) derivation is used.
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/btcruby/keychain.rb', line 330 def derived_keychain(index_or_path, hardened: nil) if index_or_path.is_a?(String) if hardened != nil raise ArgumentError, "Ambiguous use of `hardened` flag when deriving keychain with a string path" end return derived_keychain_with_path(index_or_path) end index = index_or_path raise ArgumentError, "Index must not be nil" if !index # As we use explicit "hardened" argument, do not allow higher bit set. if index < 0 || index > 0x7fffffff || (0x80000000 & index) != 0 raise ArgumentError, "Index >= 0x80000000 is not valid. Use `hardened: true` argument instead." end if hardened && !@private_key # Not possible to derive hardened keychain without a private key. raise BTCError, "Not possible to derive a hardened keychain without a private key (index: #{index})." end private_key = nil public_key = nil chain_code = nil data = "".b if hardened data << "\x00" << @private_key else data << @public_key end data << BTC::WireFormat.encode_uint32be(hardened ? (0x80000000 | index) : index) digest = BTC.hmac_sha512(data: data, key: @chain_code) chain_code = digest[32,32] lib = BTC::OpenSSL lib.autorelease do |pool| factor = pool.new_bn(digest[0,32]) n = lib.group_order if lib.BN_cmp(factor, n) >= 0 raise BTCError, "Factor for index #{index} is greater than curve order." end if @private_key pk = pool.new_bn(@private_key) lib.BN_mod_add_quick(pk, pk, factor, n) # pk = (pk + factor) % n # Check for invalid derivation. if lib.BN_cmp(pk, pool.new_bn("\x00")) == 0 raise BTCError, "Private key is zero for index #{index}." end private_key = lib.data_from_bn(pk, min_length: 32) else # Convert pubkey to a EC point pubkey_x = pool.new_bn(@public_key) pubkey_point = pool.new_ec_point lib.EC_POINT_bn2point(lib.group, pubkey_x, pubkey_point, pool.bn_ctx) # Compute point = pubkey + factor*G point = pool.new_ec_point # /** Computes r = generator * n + q * m # * \param group underlying EC_GROUP object # * \param r EC_POINT object for the result # * \param n BIGNUM with the multiplier for the group generator (optional) # * \param q EC_POINT object with the first factor of the second summand # * \param m BIGNUM with the second factor of the second summand # * \param ctx BN_CTX object (optional) # * \return 1 on success and 0 if an error occured # */ # int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); lib.EC_POINT_mul(lib.group, point, factor, pubkey_point, pool.new_bn("\x01"), pool.bn_ctx) # Check for invalid derivation. if 1 == lib.EC_POINT_is_at_infinity(lib.group, point) raise BTCError, "Resulting point is at infinity for index #{index}." end lib.EC_POINT_point2bn(lib.group, point, BTC::OpenSSL::POINT_CONVERSION_COMPRESSED, pubkey_x, pool.bn_ctx) public_key = lib.data_from_bn(pubkey_x, required_length: 33) end end self.class.new(_components: [ private_key, public_key, chain_code, nil, self.fingerprint, index, @depth + 1, !!hardened, @network]) end |
#derived_keychain_with_path(path) ⇒ Object
446 447 448 449 450 451 452 453 454 |
# File 'lib/btcruby/keychain.rb', line 446 def derived_keychain_with_path(path) path.gsub(/^m\/?/, "").split("/").inject(self) do |keychain, segment| if segment =~ /^\d+\'?$/ keychain.derived_keychain(segment.to_i, hardened: (segment[-1] == "'")) else raise ArgumentError, "Incorrect path format. Should be (\d+'?) separated by '/'." end end end |
#dup ⇒ Object
162 163 164 |
# File 'lib/btcruby/keychain.rb', line 162 def dup Keychain.new(extended_key: self.xprv || self.xpub) end |
#hardened? ⇒ Boolean
Returns true if the keychain was derived via hardened derivation from its parent. This means internally parameter i = 0x80000000 | self.index. For the master keychain index is zero and hardened? returns false.
84 85 86 |
# File 'lib/btcruby/keychain.rb', line 84 def hardened? !!@hardened end |
#mainnet? ⇒ Boolean
Returns true if this keychain is intended for mainnet.
99 100 101 |
# File 'lib/btcruby/keychain.rb', line 99 def mainnet? network.mainnet? end |
#private? ⇒ Boolean
Returns true if the keychain can derive private keys (opposite of #public?).
72 73 74 |
# File 'lib/btcruby/keychain.rb', line 72 def private? !!@private_key end |
#public? ⇒ Boolean
Returns true if the keychain can only derive public keys (opposite of #private?).
77 78 79 |
# File 'lib/btcruby/keychain.rb', line 77 def public? !@private_key end |
#public_keychain ⇒ Object
Returns a copy of the keychain stripped of the private key. Equivalent to BTC::Keychain.new(xpub: keychain.xpub)
168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/btcruby/keychain.rb', line 168 def public_keychain self.class.new(_components: [ nil, # private_key @public_key, @chain_code, @fingerprint, @parent_fingerprint, @index, @depth, @hardened, @network]) end |
#testnet? ⇒ Boolean
Returns true if this keychain is intended for testnet.
104 105 106 |
# File 'lib/btcruby/keychain.rb', line 104 def testnet? network.testnet? end |
#to_s ⇒ Object
116 117 118 |
# File 'lib/btcruby/keychain.rb', line 116 def to_s private? ? xprv : xpub end |