diff --git a/go.mod b/go.mod index df7dff0..9fb092c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ require ( github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f github.com/btcsuite/btcutil v1.0.2 + github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200604005347-6390f167e5f8 github.com/coreos/bbolt v1.3.3 github.com/fortytw2/leaktest v1.3.0 github.com/golang/protobuf v1.3.2 @@ -11,9 +12,9 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.14.3 github.com/jessevdk/go-flags v1.4.0 github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d - github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200603030653-09bb9db78246 + github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f github.com/lightningnetwork/lnd/queue v1.0.4 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.5.1 github.com/urfave/cli v1.20.0 golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 golang.org/x/text v0.3.2 // indirect diff --git a/go.sum b/go.sum index 52c0c68..76ce44c 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,8 @@ github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2ut github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+a6CZr8= github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ= -github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe h1:0m9uXDcnUc3Fv72635O/MfLbhbW+0hfSVgRiWezpkHU= -github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE= +github.com/btcsuite/btcwallet v0.11.1-0.20200604005347-6390f167e5f8 h1:wyooGuOeqcpJDK9+KgO5/yfb3nmEhakbdKHERhEylNE= +github.com/btcsuite/btcwallet v0.11.1-0.20200604005347-6390f167e5f8/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c= github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU= github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w= @@ -43,8 +43,8 @@ github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji github.com/btcsuite/btcwallet/walletdb v1.3.1 h1:lW1Ac3F1jJY4K11P+YQtRNcP5jFk27ASfrV7C6mvRU0= github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc= github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY= -github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe h1:yQbJVYfsKbdqDQNLxd4hhiLSiMkIygefW5mSHMsdKpc= -github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ= +github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200604005347-6390f167e5f8 h1:tfeGHvGrm5nYE92eaO8PChBvMinpcq566dG1xPO08GI= +github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200604005347-6390f167e5f8/go.mod h1:cJGqxXtqQbmXuL7RlXjIM18x0bGHy1407/85mQCLca4= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= @@ -123,7 +123,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmo github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.3 h1:OCJlWkOUoTnl0neNGlf4fUm3TmbEtguw7vR+nGtnDjY= github.com/grpc-ecosystem/grpc-gateway v1.14.3/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -176,11 +175,12 @@ github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce7 github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI= github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea h1:oCj48NQ8u7Vz+MmzHqt0db6mxcFZo3Ho7M5gCJauY/k= github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4= -github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200603030653-09bb9db78246 h1:jQssf6KXK4cP4OkZj3Fi6wf88PXkFOeg/ZnF1joKjQM= -github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200603030653-09bb9db78246/go.mod h1:CfulZMor9slr+ZrrbzGVTV4/AJ8SnQCnLNPEvk6Tzng= +github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f h1:/IS0Gy94ZZ0pJdwMmyb1B8RKszTY1aFm2+r/PItpbvs= +github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f/go.mod h1:a2ejAHgjuwQ9YQJBzlMD8ZJHUcyWxXb8HRGe5QlJh9Y= github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo= -github.com/lightningnetwork/lnd/clock v1.0.0 h1:U9uknV7DwBsNUzc+x11tx1bPvoXiEYcJ9wrIemRibKE= -github.com/lightningnetwork/lnd/clock v1.0.0/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= +github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k= github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg= @@ -226,7 +226,6 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -242,6 +241,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -285,7 +286,6 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -354,10 +354,8 @@ gopkg.in/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI= gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= diff --git a/lndclient/walletkit_client.go b/lndclient/walletkit_client.go index 2b7adaf..d46409d 100644 --- a/lndclient/walletkit_client.go +++ b/lndclient/walletkit_client.go @@ -2,20 +2,46 @@ package lndclient import ( "context" + "encoding/hex" + "fmt" + "time" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightninglabs/loop/swap" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "google.golang.org/grpc" ) // WalletKitClient exposes wallet functionality. type WalletKitClient interface { + // ListUnspent returns a list of all utxos spendable by the wallet with + // a number of confirmations between the specified minimum and maximum. + ListUnspent(ctx context.Context, minConfs, maxConfs int32) ( + []*lnwallet.Utxo, error) + + // LeaseOutput locks an output to the given ID, preventing it from being + // available for any future coin selection attempts. The absolute time + // of the lock's expiration is returned. The expiration of the lock can + // be extended by successive invocations of this call. Outputs can be + // unlocked before their expiration through `ReleaseOutput`. + LeaseOutput(ctx context.Context, lockID wtxmgr.LockID, + op wire.OutPoint) (time.Time, error) + + // ReleaseOutput unlocks an output, allowing it to be available for coin + // selection if it remains unspent. The ID should match the one used to + // originally lock the output. + ReleaseOutput(ctx context.Context, lockID wtxmgr.LockID, + op wire.OutPoint) error + DeriveNextKey(ctx context.Context, family int32) ( *keychain.KeyDescriptor, error) @@ -38,6 +64,10 @@ type walletKitClient struct { walletKitMac serializedMacaroon } +// A compile-time constraint to ensure walletKitclient satisfies the +// WalletKitClient interface. +var _ WalletKitClient = (*walletKitClient)(nil) + func newWalletKitClient(conn *grpc.ClientConn, walletKitMac serializedMacaroon) *walletKitClient { @@ -47,6 +77,107 @@ func newWalletKitClient(conn *grpc.ClientConn, } } +// ListUnspent returns a list of all utxos spendable by the wallet with a number +// of confirmations between the specified minimum and maximum. +func (m *walletKitClient) ListUnspent(ctx context.Context, minConfs, + maxConfs int32) ([]*lnwallet.Utxo, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) + resp, err := m.client.ListUnspent(rpcCtx, &walletrpc.ListUnspentRequest{ + MinConfs: minConfs, + MaxConfs: maxConfs, + }) + if err != nil { + return nil, err + } + + utxos := make([]*lnwallet.Utxo, 0, len(resp.Utxos)) + for _, utxo := range resp.Utxos { + var addrType lnwallet.AddressType + switch utxo.AddressType { + case lnrpc.AddressType_WITNESS_PUBKEY_HASH: + addrType = lnwallet.WitnessPubKey + case lnrpc.AddressType_NESTED_PUBKEY_HASH: + addrType = lnwallet.NestedWitnessPubKey + default: + return nil, fmt.Errorf("invalid utxo address type %v", + utxo.AddressType) + } + + pkScript, err := hex.DecodeString(utxo.PkScript) + if err != nil { + return nil, err + } + + opHash, err := chainhash.NewHash(utxo.Outpoint.TxidBytes) + if err != nil { + return nil, err + } + + utxos = append(utxos, &lnwallet.Utxo{ + AddressType: addrType, + Value: btcutil.Amount(utxo.AmountSat), + Confirmations: utxo.Confirmations, + PkScript: pkScript, + OutPoint: wire.OutPoint{ + Hash: *opHash, + Index: utxo.Outpoint.OutputIndex, + }, + }) + } + + return utxos, nil +} + +// LeaseOutput locks an output to the given ID, preventing it from being +// available for any future coin selection attempts. The absolute time of the +// lock's expiration is returned. The expiration of the lock can be extended by +// successive invocations of this call. Outputs can be unlocked before their +// expiration through `ReleaseOutput`. +func (m *walletKitClient) LeaseOutput(ctx context.Context, lockID wtxmgr.LockID, + op wire.OutPoint) (time.Time, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) + resp, err := m.client.LeaseOutput(rpcCtx, &walletrpc.LeaseOutputRequest{ + Id: lockID[:], + Outpoint: &lnrpc.OutPoint{ + TxidBytes: op.Hash[:], + OutputIndex: op.Index, + }, + }) + if err != nil { + return time.Time{}, err + } + + return time.Unix(int64(resp.Expiration), 0), nil +} + +// ReleaseOutput unlocks an output, allowing it to be available for coin +// selection if it remains unspent. The ID should match the one used to +// originally lock the output. +func (m *walletKitClient) ReleaseOutput(ctx context.Context, + lockID wtxmgr.LockID, op wire.OutPoint) error { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx) + _, err := m.client.ReleaseOutput(rpcCtx, &walletrpc.ReleaseOutputRequest{ + Id: lockID[:], + Outpoint: &lnrpc.OutPoint{ + TxidBytes: op.Hash[:], + OutputIndex: op.Index, + }, + }) + return err +} + func (m *walletKitClient) DeriveNextKey(ctx context.Context, family int32) ( *keychain.KeyDescriptor, error) { diff --git a/test/walletkit_mock.go b/test/walletkit_mock.go index 5581fdf..769297a 100644 --- a/test/walletkit_mock.go +++ b/test/walletkit_mock.go @@ -3,13 +3,16 @@ package test import ( "context" "errors" + "time" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/wtxmgr" "github.com/lightninglabs/loop/lndclient" "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" ) @@ -21,6 +24,24 @@ type mockWalletKit struct { var _ lndclient.WalletKitClient = (*mockWalletKit)(nil) +func (m *mockWalletKit) ListUnspent(ctx context.Context, minConfs, + maxConfs int32) ([]*lnwallet.Utxo, error) { + + return nil, nil +} + +func (m *mockWalletKit) LeaseOutput(ctx context.Context, lockID wtxmgr.LockID, + op wire.OutPoint) (time.Time, error) { + + return time.Now(), nil +} + +func (m *mockWalletKit) ReleaseOutput(ctx context.Context, + lockID wtxmgr.LockID, op wire.OutPoint) error { + + return nil +} + func (m *mockWalletKit) DeriveNextKey(ctx context.Context, family int32) ( *keychain.KeyDescriptor, error) {