github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/identify3/adapter.go (about)

     1  package identify3
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/keybase/client/go/externals"
    10  	"github.com/keybase/client/go/libkb"
    11  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    12  )
    13  
    14  // UIAdapter converts between the Identify2 UI that Identify2 engine expects, and the
    15  // Identify3UI interface that the frontend implements. It's maintains the
    16  // state machine that was previously implemented in JS.
    17  type UIAdapter struct {
    18  	sync.Mutex
    19  	session     *libkb.Identify3Session
    20  	ui          keybase1.Identify3UiInterface
    21  	username    string
    22  	iFollowThem bool
    23  	sentResult  bool
    24  	// An upcall is when the service initiates the ID interaction.
    25  	// An downcall is when the frontend initiates the ID.
    26  	// Probably as directed by KBFS or the CLI.
    27  	// "Tell Nojima we're about to hit him."
    28  	isUpcall    bool
    29  	priorityMap map[string]int // map from proof key to display priority
    30  }
    31  
    32  var _ libkb.IdentifyUI = (*UIAdapter)(nil)
    33  
    34  func NewUIAdapter(mctx libkb.MetaContext, ui keybase1.Identify3UiInterface) (*UIAdapter, error) {
    35  	ret := &UIAdapter{
    36  		ui: ui,
    37  	}
    38  	ret.initPriorityMap(mctx)
    39  	return ret, nil
    40  }
    41  
    42  func NewUIAdapterMakeSession(mctx libkb.MetaContext, ui keybase1.Identify3UiInterface, guiid keybase1.Identify3GUIID) (ret *UIAdapter, err error) {
    43  	sess := libkb.NewIdentify3SessionWithID(mctx, guiid)
    44  	err = mctx.G().Identify3State.Put(sess)
    45  	if err != nil {
    46  		return nil, err
    47  	}
    48  
    49  	ret, err = NewUIAdapterWithSession(mctx, ui, sess)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	return ret, nil
    54  }
    55  
    56  func NewUIAdapterMakeSessionForUpcall(mctx libkb.MetaContext, ui keybase1.Identify3UiInterface) (ret *UIAdapter, err error) {
    57  	guiid, err := libkb.NewIdentify3GUIID()
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	ret, err = NewUIAdapterMakeSession(mctx, ui, guiid)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	ret.isUpcall = true
    66  	return ret, nil
    67  }
    68  
    69  func NewUIAdapterWithSession(mctx libkb.MetaContext, ui keybase1.Identify3UiInterface, sess *libkb.Identify3Session) (*UIAdapter, error) {
    70  	ret, err := NewUIAdapter(mctx, ui)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	ret.session = sess
    75  	return ret, nil
    76  }
    77  
    78  func (i *UIAdapter) Start(mctx libkb.MetaContext, user string, reason keybase1.IdentifyReason, force bool) error {
    79  	i.Lock()
    80  	i.username = user
    81  	i.Unlock()
    82  
    83  	if !i.isUpcall {
    84  		return nil
    85  	}
    86  	arg := keybase1.Identify3ShowTrackerArg{
    87  		GuiID:        i.session.ID(),
    88  		Assertion:    keybase1.Identify3Assertion(user),
    89  		Reason:       reason,
    90  		ForceDisplay: true,
    91  	}
    92  
    93  	err := i.ui.Identify3ShowTracker(mctx.Ctx(), arg)
    94  	if err != nil {
    95  		mctx.Debug("Failed to call Identify3ShowTracker: %s", err)
    96  	}
    97  	return err
    98  }
    99  
   100  func (i *UIAdapter) initPriorityMap(mctx libkb.MetaContext) {
   101  	i.priorityMap = make(map[string]int)
   102  	var haveDisplayConfigs bool
   103  	for _, displayConfig := range mctx.G().GetProofServices().ListDisplayConfigs(mctx) {
   104  		haveDisplayConfigs = true
   105  		i.priorityMap[displayConfig.Key] = displayConfig.Priority
   106  		var altKey string
   107  		switch displayConfig.Key {
   108  		case "zcash.t", "zcash.z", "zcash.s":
   109  			altKey = "zcash"
   110  		case "bitcoin":
   111  			altKey = "btc"
   112  		case "http", "https", "dns":
   113  			altKey = "web"
   114  		}
   115  		if len(altKey) > 0 {
   116  			if _, ok := i.priorityMap[altKey]; !ok {
   117  				i.priorityMap[altKey] = displayConfig.Priority
   118  			}
   119  		}
   120  	}
   121  	if !haveDisplayConfigs {
   122  		i.priorityMap["twitter"] = 1
   123  		i.priorityMap["https"] = 100
   124  		i.priorityMap["http"] = 101
   125  		i.priorityMap["dns"] = 102
   126  		i.priorityMap["pgp"] = 103
   127  		i.priorityMap["bitcoin"] = 104
   128  		i.priorityMap["zcash.s"] = 105
   129  		i.priorityMap["zcash.z"] = 106
   130  		i.priorityMap["zcash.t"] = 107
   131  		i.priorityMap["stellar"] = 108
   132  		i.priorityMap["github"] = 2
   133  		i.priorityMap["reddit"] = 3
   134  		i.priorityMap["hackernews"] = 4
   135  		i.priorityMap["facebook"] = 5
   136  	}
   137  }
   138  
   139  func (i *UIAdapter) priority(key string) int {
   140  	if i.priorityMap == nil {
   141  		return 0 // should be impossible but don't crash.
   142  	}
   143  	p, ok := i.priorityMap[key]
   144  	if !ok {
   145  		// Put it at the bottom of the list.
   146  		return 9999999
   147  	}
   148  	return p
   149  }
   150  
   151  func (i *UIAdapter) getColorForValid(following bool) keybase1.Identify3RowColor {
   152  	if following {
   153  		return keybase1.Identify3RowColor_GREEN
   154  	}
   155  	return keybase1.Identify3RowColor_BLUE
   156  }
   157  
   158  // return true if we need an upgrade
   159  func (i *UIAdapter) setRowStatus(mctx libkb.MetaContext, arg *keybase1.Identify3Row, lcr keybase1.LinkCheckResult) bool {
   160  
   161  	needUpgrade := false
   162  	mctx.Debug("ID3: setRowStatus(lcr: %+v, cached: %+v, diff: %+v, remoteDiff: %+v, hint: %+v)",
   163  		lcr, lcr.Cached, lcr.Diff, lcr.RemoteDiff, lcr.Hint)
   164  
   165  	switch {
   166  	// The proof worked, and either we tracked it as working, or we didn't track it at all.
   167  	case (lcr.ProofResult.State == keybase1.ProofState_OK && (lcr.RemoteDiff == nil || lcr.RemoteDiff.Type == keybase1.TrackDiffType_NONE)):
   168  		arg.Color = i.getColorForValid(lcr.RemoteDiff != nil)
   169  		arg.State = keybase1.Identify3RowState_VALID
   170  
   171  	// The proof worked, and it's new to us.
   172  	case lcr.ProofResult.State == keybase1.ProofState_OK && lcr.RemoteDiff != nil && lcr.RemoteDiff.Type == keybase1.TrackDiffType_NEW:
   173  		arg.Color = keybase1.Identify3RowColor_BLUE
   174  		arg.State = keybase1.Identify3RowState_VALID
   175  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "new"})
   176  		needUpgrade = true
   177  
   178  	// The proof worked, and it's upgraded.
   179  	case lcr.ProofResult.State == keybase1.ProofState_OK && lcr.RemoteDiff != nil && lcr.RemoteDiff.Type == keybase1.TrackDiffType_UPGRADED:
   180  		arg.Color = keybase1.Identify3RowColor_BLUE
   181  		arg.State = keybase1.Identify3RowState_VALID
   182  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "upgraded"})
   183  		needUpgrade = true
   184  
   185  	// The proof worked, we tracked failed, and now it's working
   186  	case lcr.ProofResult.State == keybase1.ProofState_OK && lcr.RemoteDiff != nil && lcr.RemoteDiff.Type == keybase1.TrackDiffType_REMOTE_WORKING:
   187  		arg.Color = keybase1.Identify3RowColor_BLUE
   188  		arg.State = keybase1.Identify3RowState_VALID
   189  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "new"})
   190  		needUpgrade = true
   191  
   192  	// The proof failed, but we didn't track it
   193  	case lcr.ProofResult.State != keybase1.ProofState_OK && lcr.RemoteDiff == nil:
   194  		arg.Color = keybase1.Identify3RowColor_ORANGE
   195  		arg.State = keybase1.Identify3RowState_WARNING
   196  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "unreachable"})
   197  
   198  	// The proof failed, but we did "ignore" it, so it's OK
   199  	case lcr.ProofResult.State != keybase1.ProofState_OK && (lcr.RemoteDiff == nil || lcr.RemoteDiff.Type == keybase1.TrackDiffType_NONE):
   200  		arg.Color = keybase1.Identify3RowColor_GREEN
   201  		arg.State = keybase1.Identify3RowState_WARNING
   202  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "ignored"})
   203  
   204  	// The proof failed, but we did "ignore" it via "SNOONZE", so it's OK, for now.
   205  	case lcr.ProofResult.State != keybase1.ProofState_OK && lcr.RemoteDiff != nil && lcr.RemoteDiff.Type == keybase1.TrackDiffType_NONE_VIA_TEMPORARY:
   206  		arg.Color = keybase1.Identify3RowColor_YELLOW
   207  		arg.State = keybase1.Identify3RowState_WARNING
   208  		arg.Metas = append(arg.Metas,
   209  			keybase1.Identify3RowMeta{Color: arg.Color, Label: "ignored"},
   210  			keybase1.Identify3RowMeta{Color: arg.Color, Label: "temporary"},
   211  		)
   212  
   213  	// The proof failed, we did track it, and it didn't match our track
   214  	case lcr.ProofResult.State != keybase1.ProofState_OK && lcr.RemoteDiff != nil && lcr.RemoteDiff.Type != keybase1.TrackDiffType_NONE:
   215  		arg.Color = keybase1.Identify3RowColor_RED
   216  		arg.State = keybase1.Identify3RowState_ERROR
   217  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "unreachable"})
   218  
   219  	default:
   220  		mctx.Warning("unhandled ID3 setRowStatus")
   221  
   222  	}
   223  	return needUpgrade
   224  }
   225  
   226  func (i *UIAdapter) rowPartial(mctx libkb.MetaContext, proof keybase1.RemoteProof, lcr *keybase1.LinkCheckResult) (row keybase1.Identify3Row) {
   227  	row = keybase1.Identify3Row{
   228  		Key:      proof.Key,
   229  		Value:    proof.Value,
   230  		SigID:    proof.SigID,
   231  		Ctime:    proof.MTime, // It's what we've got.
   232  		Priority: i.priority(proof.Key),
   233  	}
   234  
   235  	var humanURLOrSigchainURL string
   236  	if lcr != nil && lcr.Hint != nil {
   237  		humanURLOrSigchainURL = lcr.Hint.HumanUrl
   238  	}
   239  	if len(humanURLOrSigchainURL) == 0 {
   240  		humanURLOrSigchainURL = i.makeSigchainViewURL(mctx, proof.SigID)
   241  	}
   242  
   243  	row.ProofURL = humanURLOrSigchainURL
   244  	switch proof.ProofType {
   245  	case keybase1.ProofType_TWITTER:
   246  		row.SiteURL = fmt.Sprintf("https://twitter.com/%v", proof.Value)
   247  	case keybase1.ProofType_GITHUB:
   248  		row.SiteURL = fmt.Sprintf("https://github.com/%v", proof.Value)
   249  	case keybase1.ProofType_REDDIT:
   250  		row.SiteURL = fmt.Sprintf("https://reddit.com/user/%v", proof.Value)
   251  	case keybase1.ProofType_HACKERNEWS:
   252  		// hackernews profile urls must have the username in its original casing.
   253  		username := proof.Value
   254  		if libkb.Cicmp(proof.Value, proof.DisplayMarkup) {
   255  			username = proof.DisplayMarkup
   256  		}
   257  		row.SiteURL = fmt.Sprintf("https://news.ycombinator.com/user?id=%v", username)
   258  	case keybase1.ProofType_FACEBOOK:
   259  		row.SiteURL = fmt.Sprintf("https://facebook.com/%v", proof.Value)
   260  	case keybase1.ProofType_GENERIC_SOCIAL:
   261  		row.SiteURL = humanURLOrSigchainURL
   262  		serviceType := mctx.G().GetProofServices().GetServiceType(mctx.Ctx(), proof.Key)
   263  		if serviceType != nil {
   264  			if serviceType, ok := serviceType.(*externals.GenericSocialProofServiceType); ok {
   265  				profileURL, err := serviceType.ProfileURL(proof.Value)
   266  				if err == nil {
   267  					row.SiteURL = profileURL
   268  				}
   269  			}
   270  		}
   271  		row.ProofURL = i.makeSigchainViewURL(mctx, proof.SigID)
   272  	case keybase1.ProofType_GENERIC_WEB_SITE:
   273  		protocol := "https"
   274  		if proof.Key != "https" {
   275  			protocol = "http"
   276  		}
   277  		row.SiteURL = fmt.Sprintf("%v://%v", protocol, proof.Value)
   278  	case keybase1.ProofType_DNS:
   279  		row.SiteURL = fmt.Sprintf("http://%v", proof.Value)
   280  		row.ProofURL = i.makeSigchainViewURL(mctx, proof.SigID)
   281  	default:
   282  		row.SiteURL = humanURLOrSigchainURL
   283  	}
   284  	iconKey := libkb.ProofIconKey(mctx, proof.ProofType, proof.Key)
   285  	row.SiteIcon = libkb.MakeProofIcons(mctx, iconKey, libkb.ProofIconTypeSmall, 16)
   286  	row.SiteIconDarkmode = libkb.MakeProofIcons(mctx, iconKey, libkb.ProofIconTypeSmallDarkmode, 16)
   287  	row.SiteIconFull = libkb.MakeProofIcons(mctx, iconKey, libkb.ProofIconTypeFull, 64)
   288  	row.SiteIconFullDarkmode = libkb.MakeProofIcons(mctx, iconKey, libkb.ProofIconTypeFullDarkmode, 64)
   289  	switch proof.ProofType {
   290  	case keybase1.ProofType_NONE, keybase1.ProofType_PGP:
   291  		// These types are not eligible for web-of-trust selection.
   292  	default:
   293  		wotProof, err := libkb.NewWotProof(proof.ProofType, proof.Key, proof.Value)
   294  		if err != nil {
   295  			mctx.Debug("Error creating web-of-trust proof summary: %v", err)
   296  		} else {
   297  			row.WotProof = &wotProof
   298  		}
   299  	}
   300  	return row
   301  }
   302  
   303  func (i *UIAdapter) finishRemoteCheck(mctx libkb.MetaContext, proof keybase1.RemoteProof, lcr keybase1.LinkCheckResult) error {
   304  	if lcr.BreaksTracking {
   305  		i.session.SetTrackBroken()
   306  	}
   307  
   308  	arg := i.rowPartial(mctx, proof, &lcr)
   309  	needUpgrade := i.setRowStatus(mctx, &arg, lcr)
   310  	if needUpgrade {
   311  		i.session.SetNeedUpgrade()
   312  	}
   313  
   314  	i.updateRow(mctx, arg)
   315  	return nil
   316  }
   317  
   318  func (i *UIAdapter) FinishWebProofCheck(mctx libkb.MetaContext, proof keybase1.RemoteProof, lcr keybase1.LinkCheckResult) error {
   319  	return i.finishRemoteCheck(mctx, proof, lcr)
   320  }
   321  
   322  func (i *UIAdapter) FinishSocialProofCheck(mctx libkb.MetaContext, proof keybase1.RemoteProof, lcr keybase1.LinkCheckResult) error {
   323  	return i.finishRemoteCheck(mctx, proof, lcr)
   324  }
   325  
   326  func (i *UIAdapter) Confirm(libkb.MetaContext, *keybase1.IdentifyOutcome) (keybase1.ConfirmResult, error) {
   327  	return keybase1.ConfirmResult{}, nil
   328  }
   329  
   330  func (i *UIAdapter) DisplayCryptocurrency(mctx libkb.MetaContext, cc keybase1.Cryptocurrency) error {
   331  	i.plumbCryptocurrency(mctx, cc)
   332  	return nil
   333  }
   334  
   335  func (i *UIAdapter) DisplayStellarAccount(mctx libkb.MetaContext, s keybase1.StellarAccount) error {
   336  	i.plumbStellarAccount(mctx, s)
   337  	return nil
   338  }
   339  
   340  func (i *UIAdapter) DisplayKey(mctx libkb.MetaContext, key keybase1.IdentifyKey) error {
   341  	if key.BreaksTracking {
   342  		i.session.SetTrackBroken()
   343  	}
   344  	i.checkEldest(mctx, key)
   345  	i.displayKey(mctx, key)
   346  	return nil
   347  }
   348  
   349  func (i *UIAdapter) displayKey(mctx libkb.MetaContext, key keybase1.IdentifyKey) {
   350  	if key.PGPFingerprint == nil {
   351  		return
   352  	}
   353  
   354  	arg := keybase1.Identify3Row{
   355  		Key:      "pgp",
   356  		Value:    hex.EncodeToString(key.PGPFingerprint),
   357  		SigID:    key.SigID,
   358  		Ctime:    0,
   359  		Priority: i.priority("pgp"),
   360  		SiteURL:  i.makeKeybaseProfileURL(mctx),
   361  		// key.SigID is blank if the PGP key was there pre-sigchain
   362  		ProofURL:             i.makeSigchainViewURL(mctx, key.SigID),
   363  		SiteIcon:             libkb.MakeProofIcons(mctx, "pgp", libkb.ProofIconTypeSmall, 16),
   364  		SiteIconDarkmode:     libkb.MakeProofIcons(mctx, "pgp", libkb.ProofIconTypeSmallDarkmode, 16),
   365  		SiteIconFull:         libkb.MakeProofIcons(mctx, "pgp", libkb.ProofIconTypeFull, 64),
   366  		SiteIconFullDarkmode: libkb.MakeProofIcons(mctx, "pgp", libkb.ProofIconTypeFullDarkmode, 64),
   367  		Kid:                  &key.KID,
   368  		// PICNIC-1092 consider adding `WotProof` to support pgp in web-of-trust.
   369  	}
   370  
   371  	switch {
   372  	case key.TrackDiff == nil || key.TrackDiff.Type == keybase1.TrackDiffType_NONE:
   373  		arg.State = keybase1.Identify3RowState_VALID
   374  		arg.Color = i.getColorForValid(key.TrackDiff != nil)
   375  	case key.TrackDiff != nil && (key.TrackDiff.Type == keybase1.TrackDiffType_REVOKED || key.TrackDiff.Type == keybase1.TrackDiffType_NEW_ELDEST):
   376  		arg.State = keybase1.Identify3RowState_REVOKED
   377  		arg.Color = keybase1.Identify3RowColor_RED
   378  	case key.TrackDiff != nil && key.TrackDiff.Type == keybase1.TrackDiffType_NEW:
   379  		arg.State = keybase1.Identify3RowState_VALID
   380  		arg.Color = keybase1.Identify3RowColor_BLUE
   381  		arg.Metas = append(arg.Metas, keybase1.Identify3RowMeta{Color: arg.Color, Label: "new"})
   382  	default:
   383  		mctx.Warning("unhandled ID3 setRowStatus in displayKey: %+v", key)
   384  	}
   385  
   386  	i.updateRow(mctx, arg)
   387  }
   388  
   389  func (i *UIAdapter) checkEldest(mctx libkb.MetaContext, key keybase1.IdentifyKey) {
   390  	if key.TrackDiff == nil || key.TrackDiff.Type != keybase1.TrackDiffType_NEW_ELDEST {
   391  		return
   392  	}
   393  	err := i.ui.Identify3UserReset(mctx.Ctx(), i.session.ID())
   394  	if err != nil {
   395  		mctx.Debug("Error sending user reset message: %s", err)
   396  	}
   397  }
   398  
   399  func (i *UIAdapter) ReportLastTrack(mctx libkb.MetaContext, track *keybase1.TrackSummary) error {
   400  	if track != nil {
   401  		i.Lock()
   402  		i.iFollowThem = true
   403  		i.Unlock()
   404  	}
   405  	return nil
   406  }
   407  
   408  func (i *UIAdapter) plumbUncheckedProofs(mctx libkb.MetaContext, proofs []keybase1.IdentifyRow) {
   409  	err := i.ui.Identify3Summary(mctx.Ctx(), keybase1.Identify3Summary{
   410  		GuiID:            i.session.ID(),
   411  		NumProofsToCheck: len(proofs),
   412  	})
   413  	if err != nil {
   414  		mctx.Debug("Identify3Summary call failed: %s", err.Error())
   415  	}
   416  	for _, proof := range proofs {
   417  		i.plumbUncheckedProof(mctx, proof)
   418  	}
   419  }
   420  
   421  func (i *UIAdapter) plumbUncheckedProof(mctx libkb.MetaContext, row keybase1.IdentifyRow) {
   422  	arg := i.rowPartial(mctx, row.Proof, nil)
   423  	arg.State = keybase1.Identify3RowState_CHECKING
   424  	arg.Color = keybase1.Identify3RowColor_GRAY
   425  	i.updateRow(mctx, arg)
   426  }
   427  
   428  func (i *UIAdapter) updateRow(mctx libkb.MetaContext, arg keybase1.Identify3Row) {
   429  	arg.GuiID = i.session.ID()
   430  	err := i.ui.Identify3UpdateRow(mctx.Ctx(), arg)
   431  	mctx.Debug("update row %+v", arg)
   432  	if err != nil {
   433  		mctx.Debug("Failed to send update row (%+v): %s", arg, err)
   434  	}
   435  }
   436  
   437  func (i *UIAdapter) shouldSkipSendResult() bool {
   438  	i.Lock()
   439  	defer i.Unlock()
   440  	if i.sentResult {
   441  		return true
   442  	}
   443  	i.sentResult = true
   444  	return false
   445  }
   446  
   447  func (i *UIAdapter) sendResult(mctx libkb.MetaContext, typ keybase1.Identify3ResultType) error {
   448  	if i.shouldSkipSendResult() {
   449  		mctx.Debug("Skipping send result, already done")
   450  		return nil
   451  	}
   452  	arg := keybase1.Identify3ResultArg{
   453  		GuiID:  i.session.ID(),
   454  		Result: typ,
   455  	}
   456  
   457  	err := i.ui.Identify3Result(mctx.Ctx(), arg)
   458  	if err != nil {
   459  		mctx.Debug("Failed to send result (%+v): %s", arg, err)
   460  	}
   461  	return err
   462  }
   463  
   464  func (i *UIAdapter) makeKeybaseProfileURL(mctx libkb.MetaContext) string {
   465  	url := libkb.SiteURILookup[mctx.G().Env.GetRunMode()]
   466  	var parts []string
   467  	if url == "" {
   468  		return url
   469  	}
   470  	parts = append(parts, url, i.username)
   471  	return strings.Join(parts, "/")
   472  }
   473  
   474  func (i *UIAdapter) makeSigchainViewURL(mctx libkb.MetaContext, s keybase1.SigID) string {
   475  	url := libkb.SiteURILookup[mctx.G().Env.GetRunMode()]
   476  	var parts []string
   477  	if url == "" {
   478  		return url
   479  	}
   480  	parts = append(parts, url, i.username)
   481  	page := "sigchain"
   482  	if !s.IsNil() {
   483  		page = page + "#" + s.String()
   484  	}
   485  	parts = append(parts, page)
   486  	return strings.Join(parts, "/")
   487  }
   488  
   489  func (i *UIAdapter) plumbCryptocurrency(mctx libkb.MetaContext, crypto keybase1.Cryptocurrency) {
   490  	key := crypto.Type
   491  	switch crypto.Family {
   492  	case string(libkb.CryptocurrencyFamilyBitcoin):
   493  		key = "btc"
   494  	case string(libkb.CryptocurrencyFamilyZCash):
   495  		key = "zcash"
   496  	default:
   497  		mctx.Debug("unrecgonized crypto family: %v, %v", crypto.Type, crypto.Family)
   498  	}
   499  	i.updateRow(mctx, keybase1.Identify3Row{
   500  		Key:                  key,
   501  		Value:                crypto.Address,
   502  		Priority:             i.priority(key),
   503  		State:                keybase1.Identify3RowState_VALID,
   504  		Color:                i.getColorForValid(i.iFollowThem),
   505  		SigID:                crypto.SigID,
   506  		Ctime:                0,
   507  		SiteURL:              i.makeSigchainViewURL(mctx, crypto.SigID),
   508  		SiteIcon:             libkb.MakeProofIcons(mctx, key, libkb.ProofIconTypeSmall, 16),
   509  		SiteIconDarkmode:     libkb.MakeProofIcons(mctx, key, libkb.ProofIconTypeSmallDarkmode, 16),
   510  		SiteIconFull:         libkb.MakeProofIcons(mctx, key, libkb.ProofIconTypeFull, 64),
   511  		SiteIconFullDarkmode: libkb.MakeProofIcons(mctx, key, libkb.ProofIconTypeFullDarkmode, 64),
   512  		ProofURL:             i.makeSigchainViewURL(mctx, crypto.SigID),
   513  	})
   514  }
   515  
   516  func (i *UIAdapter) plumbStellarAccount(mctx libkb.MetaContext, str keybase1.StellarAccount) {
   517  	color := i.getColorForValid(i.iFollowThem)
   518  	if str.Hidden {
   519  		color = keybase1.Identify3RowColor_GRAY
   520  	}
   521  	i.updateRow(mctx, keybase1.Identify3Row{
   522  		Key:                  "stellar",
   523  		Value:                str.FederationAddress,
   524  		Priority:             i.priority("stellar"),
   525  		State:                keybase1.Identify3RowState_VALID,
   526  		Color:                color,
   527  		SigID:                str.SigID,
   528  		Ctime:                0,
   529  		SiteURL:              i.makeSigchainViewURL(mctx, str.SigID),
   530  		SiteIcon:             libkb.MakeProofIcons(mctx, "stellar", libkb.ProofIconTypeSmall, 16),
   531  		SiteIconDarkmode:     libkb.MakeProofIcons(mctx, "stellar", libkb.ProofIconTypeSmallDarkmode, 16),
   532  		SiteIconFull:         libkb.MakeProofIcons(mctx, "stellar", libkb.ProofIconTypeFull, 64),
   533  		SiteIconFullDarkmode: libkb.MakeProofIcons(mctx, "stellar", libkb.ProofIconTypeFullDarkmode, 64),
   534  		ProofURL:             i.makeSigchainViewURL(mctx, str.SigID),
   535  	})
   536  }
   537  
   538  func (i *UIAdapter) plumbRevoked(mctx libkb.MetaContext, row keybase1.RevokedProof) {
   539  	arg := i.rowPartial(mctx, row.Proof, nil)
   540  	arg.State = keybase1.Identify3RowState_REVOKED
   541  	arg.Color = keybase1.Identify3RowColor_RED
   542  	arg.Metas = append(arg.Metas,
   543  		keybase1.Identify3RowMeta{Color: arg.Color, Label: "revoked"},
   544  	)
   545  	i.updateRow(mctx, arg)
   546  }
   547  
   548  func (i *UIAdapter) plumbRevokeds(mctx libkb.MetaContext, rows []keybase1.RevokedProof) {
   549  	for _, row := range rows {
   550  		i.plumbRevoked(mctx, row)
   551  	}
   552  }
   553  
   554  func (i *UIAdapter) LaunchNetworkChecks(mctx libkb.MetaContext, id *keybase1.Identity, user *keybase1.User) error {
   555  
   556  	if id.BreaksTracking {
   557  		i.session.SetTrackBroken()
   558  	}
   559  	i.plumbUncheckedProofs(mctx, id.Proofs)
   560  	i.plumbRevokeds(mctx, id.RevokedDetails)
   561  
   562  	return nil
   563  }
   564  
   565  func (i *UIAdapter) DisplayTrackStatement(libkb.MetaContext, string) error {
   566  	return nil
   567  }
   568  
   569  func (i *UIAdapter) DisplayUserCard(mctx libkb.MetaContext, card keybase1.UserCard) error {
   570  	arg := keybase1.Identify3UpdateUserCardArg{
   571  		GuiID: i.session.ID(),
   572  		Card:  card,
   573  	}
   574  	err := i.ui.Identify3UpdateUserCard(mctx.Ctx(), arg)
   575  	if err != nil {
   576  		mctx.Debug("Failed to send update card: %s", err)
   577  	}
   578  	return nil
   579  }
   580  
   581  func (i *UIAdapter) ReportTrackToken(mctx libkb.MetaContext, token keybase1.TrackToken) error {
   582  	outcome, err := mctx.G().TrackCache().Get(token)
   583  	if err != nil {
   584  		return err
   585  	}
   586  	i.session.SetOutcome(outcome)
   587  	return nil
   588  }
   589  
   590  func (i *UIAdapter) Cancel(mctx libkb.MetaContext) error {
   591  	_ = i.sendResult(mctx, keybase1.Identify3ResultType_CANCELED)
   592  	return nil
   593  }
   594  
   595  func (i *UIAdapter) Finish(mctx libkb.MetaContext) error {
   596  	_ = i.sendResult(mctx, i.session.ResultType())
   597  	return nil
   598  }
   599  func (i *UIAdapter) DisplayTLFCreateWithInvite(libkb.MetaContext, keybase1.DisplayTLFCreateWithInviteArg) error {
   600  	return nil
   601  }
   602  func (i *UIAdapter) Dismiss(libkb.MetaContext, string, keybase1.DismissReason) error {
   603  	return nil
   604  }