bitcoin-s/docs/key-manager/key-manager.md
Ben Carman 52ef1fa185 Add ability to store ExtPrivateKey instead of Mnemonic (#2372)
* Add ability to store ExtPrivateKey instead of Mnemonic

* Use private vals, change name

* Fix docs

* private to km

* Correct scaladocs
2020-12-17 13:25:04 -06:00

5.0 KiB

id title
key-manager Key Manager

Key Manager

The key manager module's goal is to encapsulate all private key interactions with the wallet project.

As of this writing, there is only one type of KeyManager - BIP39KeyManager.

The BIP39KeyManager stores a MnemonicCode on disk which can be decrypted and used as a hot wallet.

Over the long run, we want to make it so that the wallet project needs to communicate with the key-manager to access private keys.

This means that ALL SIGNING should be done inside of the key-manager, and private keys should not leave the key manager.

This makes it easier to reason about the security characteristics of our private keys, and a way to provide a uniform interface for alternative key storage systems (hsm, cloud based key storage, etc) to be plugged into the bitcoin-s library.

Creating a key manager

The first thing you need create a key manager is some entropy.

A popular way for bitcoin wallet's to represent entropy is BIP39 which you can use in bitcoin-s

You can generate a MnemonicCode in bitcoin-s with the following code

import org.bitcoins.core.crypto._

//get 256 bits of random entropy
val entropy = MnemonicCode.getEntropy256Bits

val mnemonic = MnemonicCode.fromEntropy(entropy)

//you can print that mnemonic seed with this
println(mnemonic.words)

Now that we have a MnemonicCode that was securely generated, we need to now create KeyManagerParams which tells us how to generate generate specific kinds of addresses for wallets.

KeyManagerParams takes 3 parameters:

  1. seedPath there is where we store the MnemonicCode on your file system
  2. purpose which represents what type of utxo this KeyManager is associated with. The specification for this is in BIP43
  3. network what cryptocurrency network this key manager is associated with

This controls how the root key is defined. The combination of purpose and network determine how the root ExtKey is serialized. For more information on how this works please see hd-keys

Now we can construct a native segwit key manager for the regtest network!

import java.time.Instant

import org.bitcoins.crypto._

import org.bitcoins.core.crypto._

import org.bitcoins.core.config._

import org.bitcoins.core.hd._

import org.bitcoins.keymanager._

import org.bitcoins.keymanager.bip39._

import org.bitcoins.core.wallet.keymanagement._

import java.nio.file._


//this will create a temp directory with the prefix 'key-manager-example` that will
//have a file in it called "encrypted-bitcoin-s-seed.json"
val seedPath = Files.createTempDirectory("key-manager-example").resolve(WalletStorage.ENCRYPTED_SEED_FILE_NAME)

//let's create a native segwit key manager
val purpose = HDPurposes.SegWit

//let's choose regtest as our network
val network = RegTest

val kmParams = KeyManagerParams(seedPath, purpose, network)

val aesPasswordOpt = Some(AesPassword.fromString("password"))

val km = BIP39KeyManager.initializeWithMnemonic(aesPasswordOpt, mnemonic, None, kmParams)

val rootXPub = km.right.get.getRootXPub

println(rootXPub)

Which should print something that looks like this

vpub5SLqN2bLY4WeXxMqwJHJFBEwxSscGB2uDUnsTS3edVjZEwTrQDFDNqoR2xLqARQPabGaXsHSTenTRcqm2EnB9MpuC4vSk3LqSgNmGGZtuq7

which is a native segwit ExtPubKey for the regtest network!

You can always change the network or purpose to support different things. You do not need to initialize the key manager again after initializing it once. You can use the same mnemonic for different networks, which you control KeyManagerParams.


//let's create a nested segwit key manager for mainnet
val mainnetKmParams = KeyManagerParams(seedPath, HDPurposes.SegWit, MainNet)

//we do not need to all `initializeWithMnemonic()` again as we have saved the seed to dis
val mainnetKeyManager = BIP39KeyManager.fromMnemonic(mnemonic, mainnetKmParams, None, Instant.now)

val mainnetXpub = mainnetKeyManager.getRootXPub

println(mainnetXpub)

Which gives us something that looks like this

zpub6jftahH18ngZw98KGjRo5XcxeKTQ2eztsvskb1dC9XF5TLimQquTs6Ry7nBBA425D9joXmfgJJCexmJ1u2SELJZJfRi95gcnXadLpZzYb5c

which is a p2sh wrapped segwit ExtPubKey for the bitcoin main network!

Creating a key manager from existing mnemonic

To create a KeyManager from existing mnemonic you need to specify the seedPath and then construct the KeyManagerParams that you would like.

Finally you call KeyManager.fromParams() that reads the mnemonic from disk and create's the key manager