github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/chain_link_v2.go (about)

     1  package libkb
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"fmt"
     7  
     8  	"github.com/keybase/client/go/msgpack"
     9  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    10  	"github.com/keybase/go-codec/codec"
    11  )
    12  
    13  // See comment at top of sig_chain.go for a description of V1, V2 and
    14  // V2 stubbed sigchain links.
    15  
    16  type SigchainV2Type int
    17  
    18  // These values must match constants.iced in the proofs library.
    19  const (
    20  	SigchainV2TypeNone                        SigchainV2Type = 0
    21  	SigchainV2TypeEldest                      SigchainV2Type = 1
    22  	SigchainV2TypeWebServiceBinding           SigchainV2Type = 2
    23  	SigchainV2TypeTrack                       SigchainV2Type = 3
    24  	SigchainV2TypeUntrack                     SigchainV2Type = 4
    25  	SigchainV2TypeRevoke                      SigchainV2Type = 5
    26  	SigchainV2TypeCryptocurrency              SigchainV2Type = 6
    27  	SigchainV2TypeAnnouncement                SigchainV2Type = 7
    28  	SigchainV2TypeDevice                      SigchainV2Type = 8
    29  	SigchainV2TypeWebServiceBindingWithRevoke SigchainV2Type = 9
    30  	SigchainV2TypeCryptocurrencyWithRevoke    SigchainV2Type = 10
    31  	SigchainV2TypeSibkey                      SigchainV2Type = 11
    32  	SigchainV2TypeSubkey                      SigchainV2Type = 12
    33  	SigchainV2TypePGPUpdate                   SigchainV2Type = 13
    34  	SigchainV2TypePerUserKey                  SigchainV2Type = 14
    35  	SigchainV2TypeWalletStellar               SigchainV2Type = 15
    36  	SigchainV2TypeWotVouch                    SigchainV2Type = 16
    37  	SigchainV2TypeWotVouchWithRevoke          SigchainV2Type = 17
    38  	SigchainV2TypeWotReact                    SigchainV2Type = 18
    39  
    40  	// Team link types
    41  	// If you add a new one be sure to get all of these too:
    42  	// - A corresponding libkb.LinkType in constants.go
    43  	// - SigchainV2TypeFromV1TypeTeams
    44  	// - SigChainV2Type.IsSupportedTeamType
    45  	// - SigChainV2Type.TeamAllowStubWithAdminFlag
    46  	// - TeamSigChainPlayer.addInnerLink (add a case)
    47  	SigchainV2TypeTeamRoot             SigchainV2Type = 33
    48  	SigchainV2TypeTeamNewSubteam       SigchainV2Type = 34
    49  	SigchainV2TypeTeamChangeMembership SigchainV2Type = 35
    50  	SigchainV2TypeTeamRotateKey        SigchainV2Type = 36
    51  	SigchainV2TypeTeamLeave            SigchainV2Type = 37
    52  	SigchainV2TypeTeamSubteamHead      SigchainV2Type = 38
    53  	SigchainV2TypeTeamRenameSubteam    SigchainV2Type = 39
    54  	SigchainV2TypeTeamInvite           SigchainV2Type = 40
    55  	SigchainV2TypeTeamRenameUpPointer  SigchainV2Type = 41
    56  	SigchainV2TypeTeamDeleteRoot       SigchainV2Type = 42
    57  	SigchainV2TypeTeamDeleteSubteam    SigchainV2Type = 43
    58  	SigchainV2TypeTeamDeleteUpPointer  SigchainV2Type = 44
    59  	// Note that 45 is skipped, since it's retired; used to be LegacyTLFUpgrade
    60  	SigchainV2TypeTeamSettings     SigchainV2Type = 46
    61  	SigchainV2TypeTeamKBFSSettings SigchainV2Type = 47
    62  	SigchainV2TypeTeamBotSettings  SigchainV2Type = 48
    63  )
    64  
    65  // NeedsSignature is untrue of most supported link types. If a link can
    66  // be stubbed, that means we potentially won't get to verify its signature,
    67  // since we need the full link to verify signatures. However, in some cases,
    68  // signature verification is required, and hence stubbing is disallowed.
    69  // NOTE when modifying this function ensure that web/sig.iced#_allow_stubbing
    70  // is updated as well.
    71  func (t SigchainV2Type) AllowStubbing() bool {
    72  
    73  	// Unsupported types don't need signatures. Otherwise we can't
    74  	// make code forwards-compatible.
    75  	if !t.IsSupportedUserType() {
    76  		return true
    77  	}
    78  
    79  	// Of known types, Track, Untrack and Announcement can be stubbed, but
    80  	// nothing else, for now....
    81  	switch t {
    82  	case SigchainV2TypeTrack, SigchainV2TypeUntrack, SigchainV2TypeAnnouncement:
    83  		return true
    84  	default:
    85  		return false
    86  	}
    87  }
    88  
    89  // NOTE when modifying this function ensure that web/sig.iced#_is_supported_user_type
    90  // is updated as well.
    91  func (t SigchainV2Type) IsSupportedUserType() bool {
    92  	switch t {
    93  	case SigchainV2TypeNone,
    94  		SigchainV2TypeEldest,
    95  		SigchainV2TypeWebServiceBinding,
    96  		SigchainV2TypeTrack,
    97  		SigchainV2TypeUntrack,
    98  		SigchainV2TypeRevoke,
    99  		SigchainV2TypeCryptocurrency,
   100  		SigchainV2TypeAnnouncement,
   101  		SigchainV2TypeDevice,
   102  		SigchainV2TypeWebServiceBindingWithRevoke,
   103  		SigchainV2TypeCryptocurrencyWithRevoke,
   104  		SigchainV2TypeSibkey,
   105  		SigchainV2TypeSubkey,
   106  		SigchainV2TypePGPUpdate,
   107  		SigchainV2TypePerUserKey,
   108  		SigchainV2TypeWotVouchWithRevoke,
   109  		SigchainV2TypeWalletStellar:
   110  		return true
   111  	default:
   112  		return false
   113  	}
   114  }
   115  
   116  func (t SigchainV2Type) IsSupportedType() bool {
   117  	return t.IsSupportedTeamType() || t.IsSupportedUserType()
   118  }
   119  
   120  // Whether a type is for team sigchains.
   121  // Also the list of which types are supported by this client.
   122  func (t SigchainV2Type) IsSupportedTeamType() bool {
   123  	switch t {
   124  	case SigchainV2TypeTeamRoot,
   125  		SigchainV2TypeTeamNewSubteam,
   126  		SigchainV2TypeTeamChangeMembership,
   127  		SigchainV2TypeTeamRotateKey,
   128  		SigchainV2TypeTeamLeave,
   129  		SigchainV2TypeTeamSubteamHead,
   130  		SigchainV2TypeTeamRenameSubteam,
   131  		SigchainV2TypeTeamInvite,
   132  		SigchainV2TypeTeamRenameUpPointer,
   133  		SigchainV2TypeTeamDeleteRoot,
   134  		SigchainV2TypeTeamDeleteSubteam,
   135  		SigchainV2TypeTeamDeleteUpPointer,
   136  		SigchainV2TypeTeamKBFSSettings,
   137  		SigchainV2TypeTeamSettings,
   138  		SigchainV2TypeTeamBotSettings:
   139  		return true
   140  	default:
   141  		return false
   142  	}
   143  }
   144  
   145  func (t SigchainV2Type) RequiresAtLeastRole() keybase1.TeamRole {
   146  	if !t.IsSupportedTeamType() {
   147  		// Links from the future require a bare minimum.
   148  		// They should be checked later by a code update that busts the cache.
   149  		return keybase1.TeamRole_RESTRICTEDBOT
   150  	}
   151  	switch t {
   152  	case SigchainV2TypeTeamLeave:
   153  		return keybase1.TeamRole_RESTRICTEDBOT
   154  	case SigchainV2TypeTeamRoot:
   155  		return keybase1.TeamRole_BOT
   156  	case SigchainV2TypeTeamRotateKey,
   157  		SigchainV2TypeTeamKBFSSettings:
   158  		return keybase1.TeamRole_WRITER
   159  	default:
   160  		return keybase1.TeamRole_ADMIN
   161  	}
   162  }
   163  
   164  func (t SigchainV2Type) TeamAllowStubWithAdminFlag(isAdmin bool) bool {
   165  	if isAdmin {
   166  		// Links cannot be stubbed for owners and admins
   167  		return false
   168  	}
   169  	switch t {
   170  	case SigchainV2TypeTeamNewSubteam,
   171  		SigchainV2TypeTeamRenameSubteam,
   172  		SigchainV2TypeTeamDeleteSubteam,
   173  		SigchainV2TypeTeamInvite,
   174  		SigchainV2TypeTeamSettings,
   175  		SigchainV2TypeTeamKBFSSettings,
   176  		SigchainV2TypeTeamBotSettings:
   177  		return true
   178  	default:
   179  		// Disallow stubbing of other known links.
   180  		// Allow stubbing of unknown link types for forward compatibility.
   181  		return !t.IsSupportedTeamType()
   182  	}
   183  }
   184  
   185  type OuterLinkV2Partial0 struct {
   186  	_struct  bool           `codec:",toarray"` //nolint
   187  	Version  SigVersion     `codec:"version"`
   188  	Seqno    keybase1.Seqno `codec:"seqno"`
   189  	Prev     LinkID         `codec:"prev"`
   190  	Curr     LinkID         `codec:"curr"`
   191  	LinkType SigchainV2Type `codec:"type"`
   192  }
   193  
   194  type OuterLinkV2Partial1 struct {
   195  	_struct  bool             `codec:",toarray"` //nolint
   196  	Version  SigVersion       `codec:"version"`
   197  	Seqno    keybase1.Seqno   `codec:"seqno"`
   198  	Prev     LinkID           `codec:"prev"`
   199  	Curr     LinkID           `codec:"curr"`
   200  	LinkType SigchainV2Type   `codec:"type"`
   201  	SeqType  keybase1.SeqType `codec:"seqtype"`
   202  }
   203  
   204  type OuterLinkV2Partial2 struct {
   205  	_struct             bool                   `codec:",toarray"` //nolint
   206  	Version             SigVersion             `codec:"version"`
   207  	Seqno               keybase1.Seqno         `codec:"seqno"`
   208  	Prev                LinkID                 `codec:"prev"`
   209  	Curr                LinkID                 `codec:"curr"`
   210  	LinkType            SigchainV2Type         `codec:"type"`
   211  	SeqType             keybase1.SeqType       `codec:"seqtype"`
   212  	IgnoreIfUnsupported SigIgnoreIfUnsupported `codec:"ignore_if_unsupported"`
   213  }
   214  
   215  // OuterLinkV2 is the second version of Keybase sigchain signatures.
   216  type OuterLinkV2 struct {
   217  	_struct  bool           `codec:",toarray"` //nolint
   218  	Version  SigVersion     `codec:"version"`
   219  	Seqno    keybase1.Seqno `codec:"seqno"`
   220  	Prev     LinkID         `codec:"prev"`
   221  	Curr     LinkID         `codec:"curr"`
   222  	LinkType SigchainV2Type `codec:"type"`
   223  	// -- Links exist in the wild that are missing fields below this line (see Partial0)
   224  	SeqType keybase1.SeqType `codec:"seqtype"`
   225  	// -- Links exist in the wild that are missing fields below this line too (see Partial1)
   226  	// Whether the link can be ignored by clients that do not support its link type.
   227  	// This does _not_ mean the link can be ignored if the client supports the link type.
   228  	// When it comes to stubbing, if the link is unsupported and this bit is set then
   229  	// - it can be stubbed for non-admins
   230  	// - it cannot be stubbed for admins
   231  	IgnoreIfUnsupported SigIgnoreIfUnsupported `codec:"ignore_if_unsupported"`
   232  	// -- Links exist in the wild that are missing fields below this line too (see Partial2)
   233  	// If not provided, both of these are nil, and highSkip in the inner link is set to nil.
   234  	// Note that a link providing HighSkipSeqno == 0 and HighSkipHash == nil is valid
   235  	// (and mandatory) for an initial link.
   236  	HighSkipSeqno *keybase1.Seqno `codec:"high_skip_seqno"`
   237  	HighSkipHash  *LinkID         `codec:"high_skip_hash"`
   238  }
   239  
   240  func (o OuterLinkV2) Encode() ([]byte, error) {
   241  	return msgpack.Encode(o)
   242  }
   243  
   244  type OuterLinkV2WithMetadata struct {
   245  	OuterLinkV2
   246  	raw   []byte
   247  	sigID keybase1.SigID
   248  	sig   string
   249  	kid   keybase1.KID
   250  }
   251  
   252  // An OuterLinkV2WithMetadata should never be encoded/decoded
   253  // directly. This is to avoid problems like
   254  // https://github.com/keybase/saltpack/pull/43 .
   255  
   256  var _ codec.Selfer = (*OuterLinkV2WithMetadata)(nil)
   257  
   258  var errCodecEncodeSelf = errors.New("Unexpected call to OuterLinkV2WithMetadata.CodecEncodeSelf")
   259  var errCodecDecodeSelf = errors.New("Unexpected call to OuterLinkV2WithMetadata.CodecDecodeSelf")
   260  
   261  func (o *OuterLinkV2WithMetadata) CodecEncodeSelf(e *codec.Encoder) {
   262  	panic(errCodecEncodeSelf)
   263  }
   264  
   265  func (o *OuterLinkV2WithMetadata) CodecDecodeSelf(d *codec.Decoder) {
   266  	panic(errCodecDecodeSelf)
   267  }
   268  
   269  type SigIgnoreIfUnsupported bool
   270  type SigHasRevokes bool
   271  
   272  func (b SigIgnoreIfUnsupported) Bool() bool { return bool(b) }
   273  
   274  func encodeOuterLink(
   275  	m MetaContext,
   276  	v1LinkType LinkType,
   277  	seqno keybase1.Seqno,
   278  	innerLinkJSON []byte,
   279  	prevLinkID LinkID,
   280  	hasRevokes SigHasRevokes,
   281  	seqType keybase1.SeqType,
   282  	ignoreIfUnsupported SigIgnoreIfUnsupported,
   283  	highSkip *HighSkip,
   284  ) ([]byte, error) {
   285  	var encodedOuterLink []byte
   286  
   287  	currLinkID := ComputeLinkID(innerLinkJSON)
   288  
   289  	v2LinkType, err := SigchainV2TypeFromV1TypeAndRevocations(string(v1LinkType), hasRevokes, ignoreIfUnsupported)
   290  	if err != nil {
   291  		return encodedOuterLink, err
   292  	}
   293  
   294  	// When 2.3 links are mandatory, it will be invalid for highSkip == nil,
   295  	// so the featureflag check will be removed and the nil check will result
   296  	// in an error.
   297  	allowHighSkips := m.G().Env.GetFeatureFlags().HasFeature(EnvironmentFeatureAllowHighSkips)
   298  	var highSkipSeqno *keybase1.Seqno
   299  	var highSkipHash *LinkID
   300  	if highSkip != nil && allowHighSkips {
   301  		highSkipSeqno = &highSkip.Seqno
   302  		highSkipHash = &highSkip.Hash
   303  	}
   304  	return encodeOuterLinkWithLinkID(v2LinkType, seqno, currLinkID, prevLinkID, seqType, ignoreIfUnsupported, highSkipSeqno, highSkipHash)
   305  }
   306  
   307  func encodeOuterLinkWithLinkID(
   308  	v2LinkType SigchainV2Type,
   309  	seqno keybase1.Seqno,
   310  	currLinkID LinkID,
   311  	prevLinkID LinkID,
   312  	seqType keybase1.SeqType,
   313  	ignoreIfUnsupported SigIgnoreIfUnsupported,
   314  	highSkipSeqno *keybase1.Seqno,
   315  	highSkipHash *LinkID,
   316  ) ([]byte, error) {
   317  
   318  	var encodedOuterLink []byte
   319  	var err error
   320  
   321  	if highSkipSeqno != nil && highSkipHash != nil {
   322  		outerLink := OuterLinkV2{
   323  			Version:             2,
   324  			Seqno:               seqno,
   325  			Prev:                prevLinkID,
   326  			Curr:                currLinkID,
   327  			LinkType:            v2LinkType,
   328  			SeqType:             seqType,
   329  			IgnoreIfUnsupported: ignoreIfUnsupported,
   330  			HighSkipSeqno:       highSkipSeqno,
   331  			HighSkipHash:        highSkipHash,
   332  		}
   333  		encodedOuterLink, err = outerLink.Encode()
   334  	} else {
   335  		// This is a helper struct. When the code for Sigchain 2.3
   336  		// is released, it is possible some clients will still post 2.2
   337  		// links, i.e., without high_skip information. Due to a bug
   338  		// in Keybase's fork of go-codec, omitempty does not work
   339  		// for arrays. So, we send up the serialization of the
   340  		// appropriate struct depending on whether we are making a 2.3 link.
   341  		// When 2.3 links are mandatory, this struct can be deleted.
   342  		encodedOuterLink, err = msgpack.Encode(OuterLinkV2Partial2{
   343  			Version:             2,
   344  			Seqno:               seqno,
   345  			Prev:                prevLinkID,
   346  			Curr:                currLinkID,
   347  			LinkType:            v2LinkType,
   348  			SeqType:             seqType,
   349  			IgnoreIfUnsupported: ignoreIfUnsupported,
   350  		})
   351  	}
   352  
   353  	if err != nil {
   354  		return encodedOuterLink, err
   355  	}
   356  
   357  	return encodedOuterLink, err
   358  }
   359  
   360  func MakeSigchainV2OuterSig(
   361  	m MetaContext,
   362  	signingKey GenericKey,
   363  	v1LinkType LinkType,
   364  	seqno keybase1.Seqno,
   365  	innerLinkJSON []byte,
   366  	prevLinkID LinkID,
   367  	hasRevokes SigHasRevokes,
   368  	seqType keybase1.SeqType,
   369  	ignoreIfUnsupported SigIgnoreIfUnsupported,
   370  	highSkip *HighSkip,
   371  ) (sig string, sigid keybase1.SigID, linkID LinkID, err error) {
   372  
   373  	encodedOuterLink, err := encodeOuterLink(m, v1LinkType, seqno, innerLinkJSON, prevLinkID, hasRevokes, seqType, ignoreIfUnsupported, highSkip)
   374  	if err != nil {
   375  		return sig, sigid, linkID, err
   376  	}
   377  	var sigIDBase keybase1.SigIDBase
   378  
   379  	sig, sigIDBase, err = signingKey.SignToString(encodedOuterLink)
   380  	if err != nil {
   381  		return sig, sigid, linkID, err
   382  	}
   383  	params := keybase1.SigIDSuffixParametersFromTypeAndVersion(string(v1LinkType), keybase1.SigVersion(2))
   384  	sigid = sigIDBase.ToSigID(params)
   385  
   386  	linkID = ComputeLinkID(encodedOuterLink)
   387  	return sig, sigid, linkID, nil
   388  }
   389  
   390  func (o OuterLinkV2) EncodeTruncateHighSkips() ([]byte, error) {
   391  	return encodeOuterLinkWithLinkID(o.LinkType, o.Seqno, o.Curr, o.Prev, o.SeqType, o.IgnoreIfUnsupported, o.HighSkipSeqno, o.HighSkipHash)
   392  }
   393  
   394  func (o OuterLinkV2) EncodePartial(numFields int) ([]byte, error) {
   395  	switch numFields {
   396  	case 5:
   397  		return msgpack.Encode(OuterLinkV2Partial0{
   398  			Version:  2,
   399  			Seqno:    o.Seqno,
   400  			Prev:     o.Prev,
   401  			Curr:     o.Curr,
   402  			LinkType: o.LinkType,
   403  		})
   404  	case 6:
   405  		return msgpack.Encode(OuterLinkV2Partial1{
   406  			Version:  2,
   407  			Seqno:    o.Seqno,
   408  			Prev:     o.Prev,
   409  			Curr:     o.Curr,
   410  			LinkType: o.LinkType,
   411  			SeqType:  o.SeqType,
   412  		})
   413  	case 7:
   414  		return msgpack.Encode(OuterLinkV2Partial2{
   415  			Version:             2,
   416  			Seqno:               o.Seqno,
   417  			Prev:                o.Prev,
   418  			Curr:                o.Curr,
   419  			LinkType:            o.LinkType,
   420  			SeqType:             o.SeqType,
   421  			IgnoreIfUnsupported: o.IgnoreIfUnsupported,
   422  		})
   423  	case 9:
   424  		return msgpack.Encode(OuterLinkV2{
   425  			Version:             2,
   426  			Seqno:               o.Seqno,
   427  			Prev:                o.Prev,
   428  			Curr:                o.Curr,
   429  			LinkType:            o.LinkType,
   430  			SeqType:             o.SeqType,
   431  			IgnoreIfUnsupported: o.IgnoreIfUnsupported,
   432  			HighSkipHash:        o.HighSkipHash,
   433  			HighSkipSeqno:       o.HighSkipSeqno,
   434  		})
   435  	default:
   436  		return nil, fmt.Errorf("Cannot encode sig2 partial with %d fields", numFields)
   437  	}
   438  
   439  }
   440  
   441  func DecodeStubbedOuterLinkV2(b64encoded string) (*OuterLinkV2WithMetadata, error) {
   442  	payload, err := base64.StdEncoding.DecodeString(b64encoded)
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  	if !msgpack.IsEncodedMsgpackArray(payload) {
   447  		return nil, ChainLinkError{"expected a msgpack array but got leading junk"}
   448  	}
   449  	var ol OuterLinkV2
   450  	if err = msgpack.Decode(&ol, payload); err != nil {
   451  		return nil, err
   452  	}
   453  	return &OuterLinkV2WithMetadata{OuterLinkV2: ol, raw: payload}, nil
   454  }
   455  
   456  func (o OuterLinkV2WithMetadata) EncodeStubbed() string {
   457  	return base64.StdEncoding.EncodeToString(o.raw)
   458  }
   459  
   460  func (o OuterLinkV2WithMetadata) LinkID() LinkID {
   461  	return ComputeLinkID(o.raw)
   462  }
   463  
   464  func (o OuterLinkV2WithMetadata) SigID() keybase1.SigID {
   465  	return o.sigID
   466  }
   467  
   468  func (o OuterLinkV2WithMetadata) Raw() []byte {
   469  	return o.raw
   470  }
   471  
   472  func (o OuterLinkV2WithMetadata) Verify(ctx VerifyContext) (kid keybase1.KID, err error) {
   473  	key, err := ImportKeypairFromKID(o.kid)
   474  	if err != nil {
   475  		return kid, err
   476  	}
   477  	_, err = key.VerifyString(ctx, o.sig, o.raw)
   478  	if err != nil {
   479  		return kid, err
   480  	}
   481  	return o.kid, nil
   482  }
   483  
   484  func (t SigchainV2Type) SigIDSuffixParams(v keybase1.SigVersion) keybase1.SigIDSuffixParameters {
   485  	return keybase1.SigIDSuffixParameters{
   486  		IsUserSig:       t.IsSupportedUserType(),
   487  		IsWalletStellar: (t == SigchainV2TypeWalletStellar),
   488  		SigVersion:      v,
   489  	}
   490  }
   491  
   492  func DecodeOuterLinkV2(armored string) (*OuterLinkV2WithMetadata, error) {
   493  	payload, kid, sigIDBase, err := SigExtractPayloadAndKID(armored)
   494  	if err != nil {
   495  		return nil, err
   496  	}
   497  	if !msgpack.IsEncodedMsgpackArray(payload) {
   498  		return nil, ChainLinkError{"expected a msgpack array but got leading junk"}
   499  	}
   500  
   501  	var ol OuterLinkV2
   502  	if err := msgpack.Decode(&ol, payload); err != nil {
   503  		return nil, err
   504  	}
   505  	params := ol.LinkType.SigIDSuffixParams(keybase1.SigVersion(2))
   506  	ret := OuterLinkV2WithMetadata{
   507  		OuterLinkV2: ol,
   508  		sigID:       sigIDBase.ToSigID(params),
   509  		raw:         payload,
   510  		kid:         kid,
   511  		sig:         armored,
   512  	}
   513  	return &ret, nil
   514  }
   515  
   516  func SigchainV2TypeFromV1TypeAndRevocations(s string, hasRevocations SigHasRevokes, ignoreIfUnsupported SigIgnoreIfUnsupported) (ret SigchainV2Type, err error) {
   517  
   518  	switch s {
   519  	case "eldest":
   520  		ret = SigchainV2TypeEldest
   521  	case "web_service_binding":
   522  		if hasRevocations {
   523  			ret = SigchainV2TypeWebServiceBindingWithRevoke
   524  		} else {
   525  			ret = SigchainV2TypeWebServiceBinding
   526  		}
   527  	case "track":
   528  		ret = SigchainV2TypeTrack
   529  	case "untrack":
   530  		ret = SigchainV2TypeUntrack
   531  	case "revoke":
   532  		ret = SigchainV2TypeRevoke
   533  	case "cryptocurrency":
   534  		if hasRevocations {
   535  			ret = SigchainV2TypeCryptocurrencyWithRevoke
   536  		} else {
   537  			ret = SigchainV2TypeCryptocurrency
   538  		}
   539  	case "announcement":
   540  		ret = SigchainV2TypeAnnouncement
   541  	case "device":
   542  		ret = SigchainV2TypeDevice
   543  	case "sibkey":
   544  		ret = SigchainV2TypeSibkey
   545  	case "subkey":
   546  		ret = SigchainV2TypeSubkey
   547  	case "pgp_update":
   548  		ret = SigchainV2TypePGPUpdate
   549  	case "per_user_key":
   550  		ret = SigchainV2TypePerUserKey
   551  	case string(LinkTypeWalletStellar):
   552  		ret = SigchainV2TypeWalletStellar
   553  	case string(LinkTypeWotVouch):
   554  		if hasRevocations {
   555  			ret = SigchainV2TypeWotVouchWithRevoke
   556  		} else {
   557  			ret = SigchainV2TypeWotVouch
   558  		}
   559  	case string(LinkTypeWotReact):
   560  		ret = SigchainV2TypeWotReact
   561  	default:
   562  		teamRes, teamErr := SigchainV2TypeFromV1TypeTeams(s)
   563  		if teamErr == nil {
   564  			ret = teamRes
   565  		} else {
   566  			ret = SigchainV2TypeNone
   567  			if !ignoreIfUnsupported {
   568  				err = ChainLinkError{fmt.Sprintf("Unknown sig v1 type: %s", s)}
   569  			}
   570  		}
   571  	}
   572  
   573  	if ret.AllowStubbing() && bool(hasRevocations) {
   574  		err = ChainLinkError{fmt.Sprintf("invalid chain link of type %d with a revocation", ret)}
   575  	}
   576  
   577  	return ret, err
   578  }
   579  
   580  func SigchainV2TypeFromV1TypeTeams(s string) (ret SigchainV2Type, err error) {
   581  	switch LinkType(s) {
   582  	case LinkTypeTeamRoot:
   583  		ret = SigchainV2TypeTeamRoot
   584  	case LinkTypeNewSubteam:
   585  		ret = SigchainV2TypeTeamNewSubteam
   586  	case LinkTypeChangeMembership:
   587  		ret = SigchainV2TypeTeamChangeMembership
   588  	case LinkTypeRotateKey:
   589  		ret = SigchainV2TypeTeamRotateKey
   590  	case LinkTypeLeave:
   591  		ret = SigchainV2TypeTeamLeave
   592  	case LinkTypeSubteamHead:
   593  		ret = SigchainV2TypeTeamSubteamHead
   594  	case LinkTypeRenameSubteam:
   595  		ret = SigchainV2TypeTeamRenameSubteam
   596  	case LinkTypeInvite:
   597  		ret = SigchainV2TypeTeamInvite
   598  	case LinkTypeRenameUpPointer:
   599  		ret = SigchainV2TypeTeamRenameUpPointer
   600  	case LinkTypeDeleteRoot:
   601  		ret = SigchainV2TypeTeamDeleteRoot
   602  	case LinkTypeDeleteSubteam:
   603  		ret = SigchainV2TypeTeamDeleteSubteam
   604  	case LinkTypeDeleteUpPointer:
   605  		ret = SigchainV2TypeTeamDeleteUpPointer
   606  	case LinkTypeKBFSSettings:
   607  		ret = SigchainV2TypeTeamKBFSSettings
   608  	case LinkTypeSettings:
   609  		ret = SigchainV2TypeTeamSettings
   610  	case LinkTypeTeamBotSettings:
   611  		ret = SigchainV2TypeTeamBotSettings
   612  	default:
   613  		return SigchainV2TypeNone, ChainLinkError{fmt.Sprintf("Unknown team sig v1 type: %s", s)}
   614  	}
   615  
   616  	return ret, err
   617  }
   618  
   619  func mismatchError(format string, arg ...interface{}) error {
   620  	return SigchainV2MismatchedFieldError{fmt.Sprintf(format, arg...)}
   621  }
   622  
   623  func (o OuterLinkV2) AssertFields(
   624  	version SigVersion,
   625  	seqno keybase1.Seqno,
   626  	prev LinkID,
   627  	curr LinkID,
   628  	linkType SigchainV2Type,
   629  	seqType keybase1.SeqType,
   630  	ignoreIfUnsupported SigIgnoreIfUnsupported,
   631  	highSkip *HighSkip,
   632  ) (err error) {
   633  	if o.Version != version {
   634  		return mismatchError("version field (%d != %d)", o.Version, version)
   635  	}
   636  	if o.Seqno != seqno {
   637  		return mismatchError("seqno field: (%d != %d)", o.Seqno, seqno)
   638  	}
   639  	if !o.Prev.Eq(prev) {
   640  		return mismatchError("prev pointer: (%s != !%s)", o.Prev, prev)
   641  	}
   642  	if !o.Curr.Eq(curr) {
   643  		return mismatchError("curr pointer: (%s != %s)", o.Curr, curr)
   644  	}
   645  	if !(linkType == SigchainV2TypeNone && ignoreIfUnsupported) && o.LinkType != linkType {
   646  		return mismatchError("link type: (%d != %d)", o.LinkType, linkType)
   647  	}
   648  	if o.SeqType != seqType {
   649  		return mismatchError("seq type: (%d != %d)", o.SeqType, seqType)
   650  	}
   651  	if o.IgnoreIfUnsupported != ignoreIfUnsupported {
   652  		return mismatchError("ignore_if_unsupported: (%v != %v)", o.IgnoreIfUnsupported, ignoreIfUnsupported)
   653  	}
   654  
   655  	err = o.assertHighSkip(highSkip)
   656  	if err != nil {
   657  		return err
   658  	}
   659  
   660  	return nil
   661  }
   662  
   663  func (o OuterLinkV2) assertHighSkip(highSkip *HighSkip) error {
   664  	if highSkip == nil && o.HighSkipSeqno != nil {
   665  		return mismatchError("provided HighSkipSeqno (%d) in outer link but not in inner link", o.HighSkipSeqno)
   666  	}
   667  	if highSkip == nil && o.HighSkipHash != nil {
   668  		return mismatchError("provided HighSkipHash (%v) in outer link but not in inner link", o.HighSkipHash)
   669  	}
   670  
   671  	// o.HighSkipHash may be nil even if highSkip is not, so we don't check it
   672  	if highSkip != nil && o.HighSkipSeqno == nil {
   673  		return mismatchError("provided HighSkip in inner link but not HighSkipSeqno in outer link")
   674  	}
   675  
   676  	if highSkip == nil {
   677  		return nil
   678  	}
   679  
   680  	if *o.HighSkipSeqno != highSkip.Seqno {
   681  		return mismatchError("highSkip.Seqno field outer (%d)/inner (%d) mismatch", *o.HighSkipSeqno, highSkip.Seqno)
   682  	}
   683  
   684  	if o.HighSkipHash == nil && highSkip.Hash != nil {
   685  		return mismatchError("Provided HighSkip.Hash in outer link but not inner.")
   686  	}
   687  	if o.HighSkipHash != nil && highSkip.Hash == nil {
   688  		return mismatchError("Provided HighSkip.Hash in inner link but not outer.")
   689  	}
   690  
   691  	if o.HighSkipHash != nil && !o.HighSkipHash.Eq(highSkip.Hash) {
   692  		return mismatchError("highSkip.Hash field outer (%v)/inner (%v) mismatch", o.HighSkipHash, highSkip.Hash)
   693  	}
   694  
   695  	return nil
   696  }
   697  
   698  func (o OuterLinkV2) AssertSomeFields(
   699  	version SigVersion,
   700  	seqno keybase1.Seqno,
   701  ) (err error) {
   702  	if o.Version != version {
   703  		return mismatchError("version field (%d != %d)", o.Version, version)
   704  	}
   705  	if o.Seqno != seqno {
   706  		return mismatchError("seqno field: (%d != %d)", o.Seqno, seqno)
   707  	}
   708  	return nil
   709  }