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
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 // entropy: scodec.bits.BitVector = BitVector(256 bits, 0xab0f0bec8940364a139d6987d560ad962918338a06121f9b39deebad3d313316) val mnemonic = MnemonicCode.fromEntropy(entropy) // mnemonic: MnemonicCode = Masked(MnemonicCodeImpl) //you can print that mnemonic seed with this println(mnemonic.words) // Vector(prison, joke, will, barely, address, celery, example, pudding, march, prison, clock, clump, muscle, border, chimney, seat, buzz, supply, jeans, struggle, stable, obtain, slush, spike)
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 = /var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example171839988051633638/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(/var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example171839988051633638/encrypted-bitcoin-s-seed.json,m/84',RegTest) val aesPasswordOpt = Some(AesPassword.fromString("password")) // aesPasswordOpt: Some[AesPassword] = Some(Masked(AesPassword)) val km = BIP39KeyManager.initializeWithMnemonic(aesPasswordOpt, mnemonic, None, kmParams) // km: Either[KeyManagerInitializeError, BIP39KeyManager] = Right([email protected]) val rootXPub = km.right.get.getRootXPub // rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeYbzqd1Co9uY8noP4gBYJrKbsJfqgTNUZKUWxPaZih6ieTgVxFJvvWh2Ac5MP2GnQckBhDdRbi3gCw3uyZELkxrZSvBmenRu println(rootXPub) // vpub5SLqN2bLY4WeYbzqd1Co9uY8noP4gBYJrKbsJfqgTNUZKUWxPaZih6ieTgVxFJvvWh2Ac5MP2GnQckBhDdRbi3gCw3uyZELkxrZSvBmenRu
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(/var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example171839988051633638/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.fromMnemonic(mnemonic, mainnetKmParams, None, Instant.now, false) // mainnetKeyManager: BIP39KeyManager = [email protected] val mainnetXpub = mainnetKeyManager.getRootXPub // mainnetXpub: ExtPublicKey = zpub6jftahH18ngZwnmJxSMHzFv9UfxrSfWJWmgkSFRDyPz5XsmsQDDyBMMCYWLJEwYc9FVPbyjcrvCc9tdx6R5etzQcQQhftsci3kp2UZVBxgn println(mainnetXpub) // zpub6jftahH18ngZwnmJxSMHzFv9UfxrSfWJWmgkSFRDyPz5XsmsQDDyBMMCYWLJEwYc9FVPbyjcrvCc9tdx6R5etzQcQQhftsci3kp2UZVBxgn
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