diff --git a/keychain/btcwallet.go b/keychain/btcwallet.go new file mode 100644 index 00000000..749f09ec --- /dev/null +++ b/keychain/btcwallet.go @@ -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 +} diff --git a/keychain/derivation.go b/keychain/derivation.go index b2ffd167..cc619892 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -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