Class: Bitcoin::Wallet::MasterKey

Inherits:
Object
  • Object
show all
Extended by:
Util
Includes:
HexConverter, KeyPath, Util
Defined in:
lib/bitcoin/wallet/master_key.rb

Overview

HD Wallet master seed

Constant Summary collapse

NOT_ENCRYPTED =

The seed is not encrypted.

0
ENCRYPTED_V1 =

AES-256-CBC with a key derived by PBKDF2-HMAC-SHA1. Only supported to load a wallet which was already encrypted, #encrypt always writes ENCRYPTED_V2.

1
ENCRYPTED_V2 =

AES-256-GCM with a key derived by PBKDF2-HMAC-SHA512.

2
ENCRYPTION_VERSIONS =
[ENCRYPTED_V1, ENCRYPTED_V2]
V1_KDF_ROUNDS =
2000
KDF_ROUNDS =

Rounds for PBKDF2-HMAC-SHA512, which take roughly 100 ms.

210_000
SALT_SIZE =
16
GCM_IV_SIZE =
12
GCM_TAG_SIZE =
16

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util

byte_to_bit, calc_checksum, decode_base58_address, double_sha256, encode_base58_address, hash160, hkdf_sha256, hmac_sha256, pack_boolean, pack_var_int, pack_var_string, padding_zero, sha256, tagged_hash, unpack_boolean, unpack_var_int, unpack_var_int_from_io, unpack_var_string, valid_address?

Methods included from KeyPath

#parse_key_path, #to_key_path

Methods included from HexConverter

#to_hex

Constructor Details

#initialize(seed, salt: '', encrypted: false, mnemonic: nil, encryption_version: nil) ⇒ MasterKey

Returns a new instance of MasterKey.



36
37
38
39
40
41
42
# File 'lib/bitcoin/wallet/master_key.rb', line 36

def initialize(seed, salt: '', encrypted: false, mnemonic: nil, encryption_version: nil)
  @mnemonic = mnemonic
  @seed = seed
  @encrypted = encrypted
  @encryption_version = encrypted ? (encryption_version || ENCRYPTED_V2) : nil
  @salt = salt
end

Instance Attribute Details

#encryptedObject

Returns the value of attribute encrypted.



31
32
33
# File 'lib/bitcoin/wallet/master_key.rb', line 31

def encrypted
  @encrypted
end

#encryption_versionObject (readonly)

The encryption version of seed, nil unless encrypted.



33
34
35
# File 'lib/bitcoin/wallet/master_key.rb', line 33

def encryption_version
  @encryption_version
end

#mnemonicObject

ephemeral data existing only at initialization



34
35
36
# File 'lib/bitcoin/wallet/master_key.rb', line 34

def mnemonic
  @mnemonic
end

#saltObject

Returns the value of attribute salt.



30
31
32
# File 'lib/bitcoin/wallet/master_key.rb', line 30

def salt
  @salt
end

#seedObject (readonly)

Returns the value of attribute seed.



29
30
31
# File 'lib/bitcoin/wallet/master_key.rb', line 29

def seed
  @seed
end

Class Method Details

.generateObject

generate new master key.

Returns:

  • Bitcoin::Wallet::MasterKey



46
47
48
49
50
# File 'lib/bitcoin/wallet/master_key.rb', line 46

def self.generate
  entropy = SecureRandom.hex(32)
  mnemonic = Bitcoin::Mnemonic.new('english')
  self.recover_from_words(mnemonic.to_mnemonic(entropy))
end

.parse_from_payload(payload) ⇒ Bitcoin::Wallet::MasterKey

parse master key raw data

Parameters:

  • payload (String)

    raw data

Returns:



64
65
66
67
68
69
70
71
72
# File 'lib/bitcoin/wallet/master_key.rb', line 64

def self.parse_from_payload(payload)
  flag, payload = unpack_var_int(payload)
  raise 'encrypted flag is invalid.' unless [NOT_ENCRYPTED, *ENCRYPTION_VERSIONS].include?(flag)
  salt, payload = unpack_var_string(payload)
  salt = '' unless salt
  seed, payload = unpack_var_string(payload)
  self.new(seed.bth, salt: salt.bth,
           encrypted: flag != NOT_ENCRYPTED, encryption_version: flag)
end

.recover_from_words(words) ⇒ Object

recover master key from mnemonic word list.

Parameters:

  • words (Array)

    the mnemonic word list.

Returns:

  • Bitcoin::Wallet::MasterKey



55
56
57
58
59
# File 'lib/bitcoin/wallet/master_key.rb', line 55

def self.recover_from_words(words)
  mnemonic = Bitcoin::Mnemonic.new('english')
  seed = mnemonic.to_seed(words)
  self.new(seed, mnemonic: words)
end

Instance Method Details

#decrypt(passphrase) ⇒ Object

Decrypt the seed with passphrase.

Parameters:

Raises:

  • (ArgumentError)

    If passphrase is wrong or the encrypted seed is corrupted.



117
118
119
120
121
122
123
# File 'lib/bitcoin/wallet/master_key.rb', line 117

def decrypt(passphrase)
  raise 'The wallet is not encrypted.' unless encrypted
  @seed = encryption_version == ENCRYPTED_V1 ? decrypt_v1(passphrase) : decrypt_v2(passphrase)
  @encrypted = false
  @encryption_version = nil
  @salt = ''
end

#derive(path) ⇒ Bitcoin::ExtKey

derive child key using derivation path.

Returns:



90
91
92
93
94
# File 'lib/bitcoin/wallet/master_key.rb', line 90

def derive(path)
  derived_key = key
  parse_key_path(path).each{|num| derived_key = derived_key.derive(num)}
  derived_key
end

#encrypt(passphrase) ⇒ Object

Encrypt the seed with passphrase. The stored seed is [IV(12 bytes)][auth tag(16 bytes)][ciphertext].

Parameters:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/bitcoin/wallet/master_key.rb', line 99

def encrypt(passphrase)
  raise 'The wallet is already encrypted.' if encrypted
  salt = SecureRandom.bytes(SALT_SIZE)
  iv = SecureRandom.bytes(GCM_IV_SIZE)
  enc = OpenSSL::Cipher.new('AES-256-GCM')
  enc.encrypt
  enc.key = derive_key(passphrase, salt, enc.key_len)
  enc.iv = iv
  encrypted_data = enc.update(seed) << enc.final
  @salt = salt.bth
  @seed = (iv + enc.auth_tag(GCM_TAG_SIZE) + encrypted_data).bth
  @encryption_version = ENCRYPTED_V2
  @encrypted = true
end

#keyBitcoin::ExtKey

get master key

Returns:



83
84
85
86
# File 'lib/bitcoin/wallet/master_key.rb', line 83

def key
  raise 'seed is encrypted. please decrypt the seed.' if encrypted
  Bitcoin::ExtKey.generate_master(seed)
end

#to_payloadObject

generate payload with following format [encryption version(not encrypted:0, v1:1, v2:2)][salt(var str)][seed(var str)]



76
77
78
79
# File 'lib/bitcoin/wallet/master_key.rb', line 76

def to_payload
  flg = encrypted ? encryption_version : NOT_ENCRYPTED
  pack_var_int(flg) << [salt, seed].map{|v|pack_var_string(v.htb)}.join
end