From 5449ba2b34e59214b16dff331ca51c4a79de3b9e Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 12 Aug 2016 15:24:22 -0700 Subject: [PATCH] lnwallet: revamp interfaces, add BlockChainIO and Singer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit revamps the previous WalletController interface, edging it closer to a more complete version. Additionally, this commit also introduces two new interfaces: BlockchainIO, and Singer along with a new factor driver struct, the WalletDriver. This BlockChainIO abstracts read-only access to the blockchain, while the Singer interface abstracts the signing of inputs from the base wallet paving the way to hardware wallets, air-gapped signing, etc. Finally, in order to provide an easy method for selecting a particular concrete implementation of a WalletController interface, the concept of registering “WalletDriver”s has been introduced. A wallet driver is essentially the encapsulation of a factory function capable of create a new instance of a Wallet Controller. --- lnwallet/interface.go | 267 +++++++++++++++++++++++++++++++++--------- 1 file changed, 212 insertions(+), 55 deletions(-) diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 087556a7..a29dffcc 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -1,11 +1,43 @@ package lnwallet import ( + "errors" + "fmt" + "sync" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) +// ErrNotMine is an error denoting that a WalletController instance is unable +// to spend a specifid output. +var ErrNotMine = errors.New("the passed output doesn't belong to the wallet") + +// AddressType is a enum-like type which denotes the possible address types +// WalletController supports. +type AddressType uint8 + +const ( + // WitnessPubKey represents a p2wkh address. + WitnessPubKey AddressType = iota + + // NestedWitnessPubKey represents a p2sh output which is itself a + // nested p2wkh output. + NestedWitnessPubKey + + // PublicKey represents a regular p2pkh output. + PubKeyHash +) + +// Utxo is an unspent output denoted by its outpoint, and output value of the +// original output. +type Utxo struct { + Value btcutil.Amount + wire.OutPoint +} + // WalletController defines an abstract interface for controlling a local Pure // Go wallet, a local or remote wallet via an RPC mechanism, or possibly even // a daemon assisted hardware wallet. This interface serves the purpose of @@ -17,82 +49,71 @@ import ( // behavior of all interface methods in order to ensure identical behavior // across all concrete implementations. type WalletController interface { + // FetchInputInfo queries for the WalletController's knowledge of the + // passed outpoint. If the base wallet determines this output is under + // its control, then the original txout should be returned. Otherwise, + // a non-nil error value of ErrNotMine should be returned instead. + FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) + // ConfirmedBalance returns the sum of all the wallet's unspent outputs // that have at least confs confirmations. If confs is set to zero, // then all unspent outputs, including those currently in the mempool // will be included in the final sum. - ConfirmedBalance(confs int32) btcutil.Amount + ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error) - // NewAddress returns the next external address for the wallet. The - // type of address returned is dictated by the wallet's capabilities, - // and may be of type: p2sh, p2pkh, p2wkh, p2wsh, etc. - NewAddress(witness bool) (btcutil.Address, error) - - // NewChangeAddress returns a new change address for the wallet. If the - // underlying wallet supports hd key chains, then this address should be - // dervied from an internal branch. - NewChangeAddress(witness bool) (btcutil.Address, error) + // NewAddress returns the next external or internal address for the + // wallet dicatated by the value of the `change` paramter. If change is + // true, then an internal address should be used, otherwise an external + // address should be returned. The type of address returned is dictated + // by the wallet's capabilities, and may be of type: p2sh, p2pkh, + // p2wkh, p2wsh, etc. + NewAddress(addrType AddressType, change bool) (btcutil.Address, error) // GetPrivKey retrives the underlying private key associated with the // passed address. If the wallet is unable to locate this private key // due to the address not being under control of the wallet, then an // error should be returned. - GetPrivKey(a *btcutil.Address) (*btcec.PrivateKey, error) + // TODO(roasbeef): should instead take tadge's derivation scheme in + GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) // NewRawKey returns a raw private key controlled by the wallet. These // keys are used for the 2-of-2 multi-sig outputs for funding // transactions, as well as the pub key used for commitment transactions. - // TODO(roasbeef): key pool due to cancelled reservations?? - NewRawKey() (*btcec.PrivateKey, error) + // TODO(roasbeef): may be scrapped, see above TODO + NewRawKey() (*btcec.PublicKey, error) - // FetchIdentityKey returns a private key which will be utilized as the - // wallet's Lightning Network identity for authentication purposes. - // TODO(roasbeef): rotate identity key? - FetchIdentityKey() (*btcec.PrivateKey, error) + // FetchRootKey returns a root key which will be used by the + // LightningWallet to deterministically generate secrets. The private + // key returned by this method should remain constant in-between + // WalletController restarts. + FetchRootKey() (*btcec.PrivateKey, error) - // FundTransaction creates a new unsigned transactions paying to the - // passed outputs, possibly using the specified change address. The - // includeFee parameter dictates if the wallet should also provide - // enough the funds necessary to create an adequate fee or not. - FundTransaction(outputs []*wire.TxOut, changeAddr btcutil.Address, - includeFee bool) (*wire.MsgTx, error) - - // SignTransaction performs potentially a sparse, or full signing of - // all inputs within the passed transaction that are spendable by the - // wallet. - SignTransaction(tx *wire.MsgTx) error - - // BroadcastTransaction performs cursory validation (dust checks, etc), - // then finally broadcasts the passed transaction to the Bitcoin network. - BroadcastTransaction(tx *wire.MsgTx) error - - // SendMany funds, signs, and broadcasts a Bitcoin transaction paying - // out to the specified outputs. In the case the wallet has insufficient - // funds, or the outputs are non-standard, and error should be returned. - SendMany(outputs []*wire.TxOut) (*wire.ShaHash, error) + // SendOutputs funds, signs, and broadcasts a Bitcoin transaction + // paying out to the specified outputs. In the case the wallet has + // insufficient funds, or the outputs are non-standard, and error + // should be returned. + SendOutputs(outputs []*wire.TxOut) (*wire.ShaHash, error) // ListUnspentWitness returns all unspent outputs which are version 0 // witness programs. The 'confirms' parameter indicates the minimum // number of confirmations an output needs in order to be returned by - // this method. Passing -1 as 'confirms' indicates that even unconfirmed - // outputs should be returned. - ListUnspentWitness(confirms int32) ([]*wire.OutPoint, error) + // this method. Passing -1 as 'confirms' indicates that even + // unconfirmed outputs should be returned. + ListUnspentWitness(confirms int32) ([]*Utxo, error) // LockOutpoint marks an outpoint as locked meaning it will no longer - // be deemed as eligble for coin selection. Locking outputs are utilized - // in order to avoid race conditions when selecting inputs for usage when - // funding a channel. + // be deemed as eligible for coin selection. Locking outputs are + // utilized in order to avoid race conditions when selecting inputs for + // usage when funding a channel. LockOutpoint(o wire.OutPoint) // UnlockOutpoint unlocks an previously locked output, marking it // eligible for coin seleciton. UnlockOutpoint(o wire.OutPoint) - // ImportScript imports the serialize public key script, or redeem - // script into the wallet's database. Scripts to be imported include - // the 2-of-2 script for funding transactions, commitment scripts, - // HTLCs scripts, and so on. - ImportScript(b []byte) error + // PublishTransaction performs cursory validation (dust checks, etc), + // then finally broadcasts the passed transaction to the Bitcoin network. + PublishTransaction(tx *wire.MsgTx) error // Start initializes the wallet, making any neccessary connections, // starting up required goroutines etc. @@ -101,11 +122,147 @@ type WalletController interface { // Stop signals the wallet for shutdown. Shutdown may entail closing // any active sockets, database handles, stopping goroutines, etc. Stop() error - - // WaitForShutdown blocks until the wallet finishes the shutdown - // procedure triggered by a prior call to Stop(). - WaitForShutdown() error - - // TODO(roasbeef): ImportPriv? - // * segwitty flag? +} + +// BlockChainIO is a dedicated source which will be used to obtain queries +// related to the current state of the blockchain. The data returned by each of +// the defined methods within this interface should always return the most up +// to date data possible. +// +// TODO(roasbeef): move to diff package perhaps? +type BlockChainIO interface { + // GetCurrentHeight returns the current height of the valid most-work + // chain the implementation is aware of. + GetCurrentHeight() (int32, error) + + // GetTxOut returns the original output referenced by the passed + // outpoint. + GetUtxo(txid *wire.ShaHash, index uint32) (*wire.TxOut, error) + + // GetTransaction returns the full transaction identified by the passed + // transaction ID. + GetTransaction(txid *wire.ShaHash) (*wire.MsgTx, error) +} + +// SignDescriptor houses the necessary information required to succesfully sign +// a given output. This struct is used by the Signer interface in order to gain +// access to critial data needed to generate a valid signature. +type SignDescriptor struct { + // Pubkey is the public key to which the signature should be generated + // over. The Signer should then generate a signature with the private + // key corresponding to this public key. + PubKey *btcec.PublicKey + + // RedeemScript is the full script required to properly redeem the + // output. This field will only be populated if a p2wsh or a p2sh + // output is being signed. + RedeemScript []byte + + // Output is the target output which should be signed. The PkScript and + // Value fields within the output should be properly populated, + // otherwise an invalid signature may be generated. + Output *wire.TxOut + + // HashType is the target sighash type that should be used when + // generating the final sighash, and signature. + HashType txscript.SigHashType + + // SigHashes is the pre-computed sighash midstate to be used when + // generating the final sighash for signing. + SigHashes *txscript.TxSigHashes + + // InputIndex is the target input within the transaction that should be + // signed. + InputIndex int +} + +// Signer represents an abstract object capable of generating raw signatures as +// well as full complete input scripts given a valid SignDescriptor and +// transaction. This interface fully abstracts away signing paving the way for +// Signer implementations such as hardware wallets, hardware tokens, HSM's, or +// simply a regular wallet. +type Signer interface { + // SignOutputRaw generates a signature for the passed transaction + // according to the data within the passed SignDescriptor. + // + // NOTE: The resulting signature should be void of a sighash byte. + SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) + + // ComputeInputScript generates a complete InputIndex for the passed + // transaction with the signature as defined within the passed + // SignDescriptor. This method should be capable of generating the + // proper input script for both regular p2wkh output and p2wkh outputs + // nested within a regualr p2sh output. + ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) +} + +// WalletDriver represents a "driver" for a particular concrete +// WalletController implementation. A driver is indentified by a globally +// unique string identifier along with a 'New()' method which is responsible +// for initializing a particular WalletController concrete implementation. +type WalletDriver struct { + // WalletType is a string which uniquely identifes the WalletController + // that this driver, drives. + WalletType string + + // New creates a new instance of a concrete WalletController + // implementation given a variadic set up arguments. The function takes + // a varidaic number of interface paramters in order to provide + // initialization flexibility, thereby accomodating several potential + // WalletController implementations. + New func(args ...interface{}) (WalletController, error) +} + +var ( + wallets = make(map[string]*WalletDriver) + registerMtx sync.Mutex +) + +// RegisteredWallets returns a slice of all currently registered notifiers. +// +// NOTE: This function is safe for concurrent access. +func RegisteredWallets() []*WalletDriver { + registerMtx.Lock() + defer registerMtx.Unlock() + + registeredWallets := make([]*WalletDriver, 0, len(wallets)) + for _, wallet := range wallets { + registeredWallets = append(registeredWallets, wallet) + } + + return registeredWallets +} + +// RegisterWallet registers a WalletDriver which is capable of driving a +// concrete WalletController interface. In the case that this driver has +// already been registered, an error is returned. +// +// NOTE: This function is safe for concurrent access. +func RegisterWallet(driver *WalletDriver) error { + registerMtx.Lock() + defer registerMtx.Unlock() + + if _, ok := wallets[driver.WalletType]; ok { + return fmt.Errorf("wallet already registered") + } + + wallets[driver.WalletType] = driver + + return nil +} + +// SupportedWallets returns a slice of strings that represents the walelt +// drivers that have been registered and are therefore supported. +// +// NOTE: This function is safe for concurrent access. +func SupportedWallets() []string { + registerMtx.Lock() + defer registerMtx.Unlock() + + supportedWallets := make([]string, 0, len(wallets)) + for walletName := range wallets { + supportedWallets = append(supportedWallets, walletName) + } + + return supportedWallets }