keychain: add a btcwallet implementation of the KeyRing and SecretKeyRing interfaces
This commit is contained in:
parent
aebe7f35dc
commit
d6f54b30fc
288
keychain/btcwallet.go
Normal file
288
keychain/btcwallet.go
Normal file
@ -0,0 +1,288 @@
|
||||
package keychain
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/roasbeef/btcd/btcec"
|
||||
"github.com/roasbeef/btcwallet/waddrmgr"
|
||||
"github.com/roasbeef/btcwallet/wallet"
|
||||
"github.com/roasbeef/btcwallet/walletdb"
|
||||
)
|
||||
|
||||
var (
|
||||
// lightningKeyScope is the key scope that will be used within the
|
||||
// waddrmgr to create an HD chain for deriving all of our required
|
||||
// keys.
|
||||
lightningKeyScope = waddrmgr.KeyScope{
|
||||
Purpose: BIP0043Purpose,
|
||||
Coin: 0,
|
||||
}
|
||||
|
||||
// lightningAddrSchema is the scope addr schema for all keys that we
|
||||
// derive. We'll treat them all as p2wkh addresses, as atm we must
|
||||
// specify a particular type.
|
||||
lightningAddrSchema = waddrmgr.ScopeAddrSchema{
|
||||
ExternalAddrType: waddrmgr.WitnessPubKey,
|
||||
InternalAddrType: waddrmgr.WitnessPubKey,
|
||||
}
|
||||
|
||||
// waddrmgrNamespaceKey is the namespace key that the waddrmgr state is
|
||||
// stored within the top-level waleltdb buckets of btcwallet.
|
||||
waddrmgrNamespaceKey = []byte("waddrmgr")
|
||||
)
|
||||
|
||||
// BtcWalletKeyRing is an implementation of both the KeyRing and SecretKeyRing
|
||||
// interfaces backed by btcwallet's internal root waddrmgr. Internally, we'll
|
||||
// be using a ScopedKeyManager to do all of our derivations, using the key
|
||||
// scope and scope addr scehma defined above. Re-using the existing key scope
|
||||
// construction means that all key derivation will be protected under the root
|
||||
// seed of the wallet, making each derived key fully deterministic.
|
||||
type BtcWalletKeyRing struct {
|
||||
// wallet is a pointer to the active instance of the btcwallet core.
|
||||
// This is required as we'll need to manually open database
|
||||
// transactions in order to derive addresses and lookup relevant keys
|
||||
wallet *wallet.Wallet
|
||||
|
||||
// lightningScope is a pointer to the scope that we'll be using as a
|
||||
// sub key manager to derive all the keys that we require.
|
||||
lightningScope *waddrmgr.ScopedKeyManager
|
||||
}
|
||||
|
||||
// NewBtcWalletKeyRing creates a new implementation of the
|
||||
// keychain.SecretKeyRing interface backed by btcwallet.
|
||||
//
|
||||
// NOTE: The passed waddrmgr.Manager MUST be unlocked in order for the keychain
|
||||
// to function.
|
||||
func NewBtcWalletKeyRing(w *wallet.Wallet) SecretKeyRing {
|
||||
return &BtcWalletKeyRing{
|
||||
wallet: w,
|
||||
}
|
||||
}
|
||||
|
||||
// keyScope attempts to return the key scope that we'll use to derive all of
|
||||
// our keys. If the scope has already been fetched from the database, then a
|
||||
// cached version will be returned. Otherwise, we'll fetch it from the database
|
||||
// and cache it for subsequent accesses.
|
||||
func (b *BtcWalletKeyRing) keyScope() (*waddrmgr.ScopedKeyManager, error) {
|
||||
// If the scope has already been populated, then we'll return it
|
||||
// directly.
|
||||
if b.lightningScope != nil {
|
||||
return b.lightningScope, nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll first do a check to ensure that the root manager
|
||||
// isn't locked, as otherwise we won't be able to *use* the scope.
|
||||
if b.wallet.Manager.Locked() {
|
||||
return nil, fmt.Errorf("cannot create BtcWalletKeyRing with " +
|
||||
"locked waddrmgr.Manager")
|
||||
}
|
||||
|
||||
// If the manager is indeed unlocked, then we'll fetch the scope, cache
|
||||
// it, and return to the caller.
|
||||
lnScope, err := b.wallet.Manager.FetchScopedKeyManager(
|
||||
lightningKeyScope,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.lightningScope = lnScope
|
||||
|
||||
return lnScope, nil
|
||||
}
|
||||
|
||||
// createAccountIfNotExists will create the corresponding account for a key
|
||||
// family if it doesn't already exist in the database.
|
||||
func (b *BtcWalletKeyRing) createAccountIfNotExists(
|
||||
addrmgrNs walletdb.ReadWriteBucket, keyFam KeyFamily,
|
||||
scope *waddrmgr.ScopedKeyManager) error {
|
||||
|
||||
// If this is the multi-sig key family, then we can return early as
|
||||
// this is the default account that's created.
|
||||
if keyFam == KeyFamilyMultiSig {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, we'll check if the account already exists, if so, we can
|
||||
// once again bail early.
|
||||
_, err := scope.AccountName(addrmgrNs, uint32(keyFam))
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we reach this point, then the account hasn't yet been created, so
|
||||
// we'll need to create it before we can proceed.
|
||||
return scope.NewRawAccount(addrmgrNs, uint32(keyFam))
|
||||
}
|
||||
|
||||
// DeriveNextKey attempts to derive the *next* key within the key family
|
||||
// (account in BIP43) specified. This method should return the next external
|
||||
// child within this branch.
|
||||
//
|
||||
// NOTE: This is part of the keychain.KeyRing interface.
|
||||
func (b *BtcWalletKeyRing) DeriveNextKey(keyFam KeyFamily) (KeyDescriptor, error) {
|
||||
var pubKey *btcec.PublicKey
|
||||
|
||||
db := b.wallet.Database()
|
||||
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
scope, err := b.keyScope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the account doesn't exist, then we may need to create it
|
||||
// for the first time in order to derive the keys that we
|
||||
// require.
|
||||
err = b.createAccountIfNotExists(addrmgrNs, keyFam, scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addrs, err := scope.NextExternalAddresses(
|
||||
addrmgrNs, uint32(keyFam), 1,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pubKey = addrs[0].(waddrmgr.ManagedPubKeyAddress).PubKey()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return KeyDescriptor{}, err
|
||||
}
|
||||
|
||||
return KeyDescriptor{
|
||||
PubKey: pubKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeriveKey attempts to derive an arbitrary key specified by the passed
|
||||
// KeyLocator. This may be used in several recovery scenarios, or when manually
|
||||
// rotating something like our current default node key.
|
||||
//
|
||||
// NOTE: This is part of the keychain.KeyRing interface.
|
||||
func (b *BtcWalletKeyRing) DeriveKey(keyLoc KeyLocator) (KeyDescriptor, error) {
|
||||
var keyDesc KeyDescriptor
|
||||
|
||||
db := b.wallet.Database()
|
||||
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
scope, err := b.keyScope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the account doesn't exist, then we may need to create it
|
||||
// for the first time in order to derive the keys that we
|
||||
// require.
|
||||
err = b.createAccountIfNotExists(addrmgrNs, keyLoc.Family, scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := waddrmgr.DerivationPath{
|
||||
Account: uint32(keyLoc.Family),
|
||||
Branch: 0,
|
||||
Index: uint32(keyLoc.Index),
|
||||
}
|
||||
addr, err := scope.DeriveFromKeyPath(addrmgrNs, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyDesc.KeyLocator = keyLoc
|
||||
keyDesc.PubKey = addr.(waddrmgr.ManagedPubKeyAddress).PubKey()
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return keyDesc, err
|
||||
}
|
||||
|
||||
return keyDesc, nil
|
||||
}
|
||||
|
||||
// DerivePrivKey attempts to derive the private key that corresponds to the
|
||||
// passed key descriptor.
|
||||
//
|
||||
// NOTE: This is part of the keychain.SecretKeyRing interface.
|
||||
func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (*btcec.PrivateKey, error) {
|
||||
var key *btcec.PrivateKey
|
||||
|
||||
db := b.wallet.Database()
|
||||
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
scope, err := b.keyScope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the account doesn't exist, then we may need to create it
|
||||
// for the first time in order to derive the keys that we
|
||||
// require.
|
||||
err = b.createAccountIfNotExists(
|
||||
addrmgrNs, keyDesc.Family, scope,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that we know the account exists, we can safely derive
|
||||
// the full private key from the given path.
|
||||
path := waddrmgr.DerivationPath{
|
||||
Account: uint32(keyDesc.Family),
|
||||
Branch: 0,
|
||||
Index: uint32(keyDesc.Index),
|
||||
}
|
||||
addr, err := scope.DeriveFromKeyPath(addrmgrNs, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ScalarMult performs a scalar multiplication (ECDH-like operation) between
|
||||
// the target key descriptor and remote public key. The output returned will be
|
||||
// the sha256 of the resulting shared point serialized in compressed format. If
|
||||
// k is our private key, and P is the public key, we perform the following
|
||||
// operation:
|
||||
//
|
||||
// sx := k*P s := sha256(sx.SerializeCompressed())
|
||||
//
|
||||
// NOTE: This is part of the keychain.SecretKeyRing interface.
|
||||
func (b *BtcWalletKeyRing) ScalarMult(keyDesc KeyDescriptor,
|
||||
pub *btcec.PublicKey) ([]byte, error) {
|
||||
|
||||
privKey, err := b.DerivePrivKey(keyDesc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &btcec.PublicKey{}
|
||||
x, y := btcec.S256().ScalarMult(pub.X, pub.Y, privKey.D.Bytes())
|
||||
s.X = x
|
||||
s.Y = y
|
||||
|
||||
h := sha256.Sum256(s.SerializeCompressed())
|
||||
|
||||
return h[:], nil
|
||||
}
|
@ -6,7 +6,7 @@ const (
|
||||
// KeyDerivationVersion is the version of the key derivation schema
|
||||
// defined below. We use a version as this means that we'll be able to
|
||||
// accept new seed in the future and be able to discern if the software
|
||||
// is compatible with the version of the weed.
|
||||
// is compatible with the version of the seed.
|
||||
KeyDerivationVersion = 0
|
||||
|
||||
// BIP0043Purpose is the "purpose" value that we'll use for the first
|
||||
|
Loading…
Reference in New Issue
Block a user