The key manager module's goal is to encapusulate all private key interactions with the wallet project.
As of this writing, there is only one type of
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.
Currently bip39 password is supported at the library level, but is not supported for end users using the server project.
You can see that the bip39 password is hard coded to
There is a password that is used to encrypt your mnemonic seed on disk, but that password is hard coded to a default value. THIS MEANS THAT YOUR MNEMONIC SEED CAN TRIVIALLY BE STOLEN IF AN ATTACKER CAN ACCESS YOUR HARD DRIVE. TAKE PROPER OPSEC PRECAUTIONS.
Overall the key manager module should be considered insecure. For this release, it is more about setting up the module as a logical distinction for further development in subsequent releases.
Creating a key manager
The first thing you need create a key manager is some entropy.
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 // entropy: scodec.bits.BitVector = BitVector(256 bits, 0xbf2968260f174937863a69ac8e2e8f25813b5437246077b6c3f88f95ef932d76) val mnemonic = MnemonicCode.fromEntropy(entropy) // mnemonic: MnemonicCode = Masked(MnemonicCodeImpl) //you can print that mnemonic seed with this println(mnemonic.words) // Vector(sand, ensure, another, bullet, innocent, orange, body, essay, prosper, imitate, phrase, enrich, beauty, present, symptom, metal, jeans, render, wreck, busy, galaxy, sister, remind, sort)
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:
seedPaththere is where we store the
MnemonicCodeon your file system
purposewhich represents what type of utxo this
KeyManageris associated with. The specification for this is in BIP43
networkwhat cryptocurrency network this key manager is associated with
This controls how the root key is defined. The combination of
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!
//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) // seedPath: Path = /tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json //let's create a native segwit key manager val purpose = HDPurposes.SegWit // purpose: HDPurpose = m/84' //let's choose regtest as our network val network = RegTest // network: RegTest.type = RegTest val kmParams = KeyManagerParams(seedPath, purpose, network) // kmParams: KeyManagerParams = KeyManagerParams(/tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',RegTest) val km = BIP39KeyManager.initializeWithMnemonic(mnemonic, None, kmParams) // km: Either[KeyManagerInitializeError, BIP39KeyManager] = Right(BIP39KeyManager(Masked(MnemonicCodeImpl),KeyManagerParams(/tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',RegTest),None)) val rootXPub = km.right.get.getRootXPub // rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeZwnivVWfghDdWLN1osw3cJagfGXncZhnZ4T1r9m2D6Be4h6ceRY6YQR2hDLcvExdAq36cuKTKbEDDBn8CAUAjTxn5Y1o9JH println(rootXPub) // vpub5SLqN2bLY4WeZwnivVWfghDdWLN1osw3cJagfGXncZhnZ4T1r9m2D6Be4h6ceRY6YQR2hDLcvExdAq36cuKTKbEDDBn8CAUAjTxn5Y1o9JH
Which should print something that looks like this
which is a native segwit
ExtPubKey for the regtest network!
You can always change the
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
//let's create a nested segwit key manager for mainnet val mainnetKmParams = KeyManagerParams(seedPath, HDPurposes.SegWit, MainNet) // mainnetKmParams: KeyManagerParams = KeyManagerParams(/tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',MainNet) //we do not need to all `initializeWithMnemonic()` again as we have saved the seed to dis val mainnetKeyManager = BIP39KeyManager(mnemonic, mainnetKmParams, None) // mainnetKeyManager: BIP39KeyManager = BIP39KeyManager(Masked(MnemonicCodeImpl),KeyManagerParams(/tmp/key-manager-example6695794117403645345/encrypted-bitcoin-s-seed.json,m/84',MainNet),None) val mainnetXpub = mainnetKeyManager.getRootXPub // mainnetXpub: ExtPublicKey = zpub6jftahH18ngZy8ZCFvfAX3beCCwoaMu3GkfZnr7L8bDJmThvrnRGhLpC9Wvxe49nAxtFh7irktNphyVMVgyWWXxcgYZpXok7pNDMdoTT1K6 println(mainnetXpub) // zpub6jftahH18ngZy8ZCFvfAX3beCCwoaMu3GkfZnr7L8bDJmThvrnRGhLpC9Wvxe49nAxtFh7irktNphyVMVgyWWXxcgYZpXok7pNDMdoTT1K6
Which gives us something that looks like this
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