1package libkb
2
3import (
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
16type SigchainV2Type int
17
18// These values must match constants.iced in the proofs library.
19const (
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.
71func (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.
91func (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
116func (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.
122func (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
145func (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
164func (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
185type 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
194type 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
204type 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.
216type 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
240func (o OuterLinkV2) Encode() ([]byte, error) {
241	return msgpack.Encode(o)
242}
243
244type 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
256var _ codec.Selfer = (*OuterLinkV2WithMetadata)(nil)
257
258var errCodecEncodeSelf = errors.New("Unexpected call to OuterLinkV2WithMetadata.CodecEncodeSelf")
259var errCodecDecodeSelf = errors.New("Unexpected call to OuterLinkV2WithMetadata.CodecDecodeSelf")
260
261func (o *OuterLinkV2WithMetadata) CodecEncodeSelf(e *codec.Encoder) {
262	panic(errCodecEncodeSelf)
263}
264
265func (o *OuterLinkV2WithMetadata) CodecDecodeSelf(d *codec.Decoder) {
266	panic(errCodecDecodeSelf)
267}
268
269type SigIgnoreIfUnsupported bool
270type SigHasRevokes bool
271
272func (b SigIgnoreIfUnsupported) Bool() bool { return bool(b) }
273
274func 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
307func 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
360func 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
390func (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
394func (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
441func 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
456func (o OuterLinkV2WithMetadata) EncodeStubbed() string {
457	return base64.StdEncoding.EncodeToString(o.raw)
458}
459
460func (o OuterLinkV2WithMetadata) LinkID() LinkID {
461	return ComputeLinkID(o.raw)
462}
463
464func (o OuterLinkV2WithMetadata) SigID() keybase1.SigID {
465	return o.sigID
466}
467
468func (o OuterLinkV2WithMetadata) Raw() []byte {
469	return o.raw
470}
471
472func (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
484func (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
492func 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
516func 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
580func 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
619func mismatchError(format string, arg ...interface{}) error {
620	return SigchainV2MismatchedFieldError{fmt.Sprintf(format, arg...)}
621}
622
623func (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
663func (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
698func (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}
710