Mnemonic For Master Private Key

Abstract

This is a simple extension of the widely used BIP39 Mnemonic seed phrase scheme where 12 to 24 words are used to encode entropy which is then used to derive an extended private key for use with the BRC-32 derivation scheme.

This document proposes a way to encode a single private key as a mnemonic phrase which people have become accustomed to.

Motivation

The purpose is to maintain the familiar interface to store a single key rather than an extended set. Users are already aware of the importance of keeping these words secret and secure, and have developed an awareness around not sharing them with a third party and so on. Rather than retraining users on a new concept, the idea is to do away with BIP32 in favor of BRC-42 style derivations, but keep the backup method for the master key as storing a mnemonic offline.

Note: There is no need to use BIP32 and BIP44 hereafter. You could just derive the first child key of an HD wallet and use that for BRC-44 derivations. However this is a simpler way to get to a single key and remains compatible with the menomics people may have been using for a decase now.

Overview

Using the standard BIP39 mnemonic scheme, a seed is created and can be maintained offline as words. Thereafter we must get from the mnemonic to a seed and then in this case to a private key. The simplest methodology can be used, a sha256 of the seed bytes is taken to arrive at the big number which is the private key.

Implementation

Using libsv/go-bk, we can generate a mnemonic, and derive the master key from it. All derivations for actual outputs can be derived using the scheme described in BRC-42.

package main

import (
	"encoding/hex"
	"fmt"

	"crypto/sha256"

	"github.com/libsv/go-bk/bec"
	"github.com/libsv/go-bk/bip39"
)

func main() {
    // We generate some random bytes (any method for creating good random data can be used here).
	random, _ := bip39.GenerateEntropy(128)

    // The random data is encoded as a mnemonic of 12 words using the PBKDF2 function to SHA512 hash of the mnemonic sentence concatenated with the passcode (blank in this case).
	mnemonic, seed, _ := bip39.Mnemonic(random, "")
    // The private key is a sha256 of the seed bytes, this normalizes the output to 256 bits needed.
	privKeyBytes := sha256.Sum256(seed)

    // Here we ensure that using the mnemonic in future will result in the same private key.
	seed, _ = bip39.MnemonicToSeed(mnemonic, "")
	if privKeyBytes != sha256.Sum256(seed) {
        panic("mnemonic is invalid")
    }

    // Type cast a slice of bytes
	pkb := make([]byte, 32)
	copy(pkb, privKeyBytes[:])
	fmt.Println(hex.EncodeToString(pkb))
    // > '1ad0895dd317163f0e83499c30bc593dbcc54cad96a5f57b065ce9f700513250'

    // Create a PrivKey on the secp256k1 curve. 
	masterKey, _ := bec.PrivKeyFromBytes(bec.S256(), pkb)

    // Derive the corresponding PubKey and log it.
	pubKey := masterKey.PubKey()
	pubBytes := pubKey.SerialiseCompressed()
	fmt.Println(hex.EncodeToString(pubBytes))
    // > '021c2361fa1c39e21422b1374c2a08106f99b5425ada71f46f55b8e8e9d4a932db'
}

JavaScript

Same idea in js but we use bsv npm package, and a specific mnemonic to check we get the same resulting key pair:

import { PrivKey, PubKey, Bip39, Hash, Bn } from 'bsv'

const m = new Bip39(`dial tunnel valid cry exhaust stand match purse hope since demand palace`)
const seed = m.mnemonic2Seed().seed
const privateKey = Hash.sha256(seed)

const masterKey = PrivKey.fromBn(Bn.fromBuffer(privateKey))
console.log(masterKey.bn.toBuffer().toString('hex'))
// > '1ad0895dd317163f0e83499c30bc593dbcc54cad96a5f57b065ce9f700513250'

const pubKey = PubKey.fromPrivKey(masterKey).toString()
console.log(pubKey)
// > '021c2361fa1c39e21422b1374c2a08106f99b5425ada71f46f55b8e8e9d4a932db'

Last updated