github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/hidden_loader_test.go (about) 1 package teams 2 3 import ( 4 "testing" 5 6 "github.com/keybase/client/go/engine" 7 "github.com/keybase/client/go/teams/hidden" 8 9 "github.com/keybase/clockwork" 10 "golang.org/x/net/context" 11 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/keybase1" 14 jsonw "github.com/keybase/go-jsonw" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func makeHiddenRotation(t *testing.T, userContext *libkb.GlobalContext, teamName keybase1.TeamName) { 19 team, err := GetForTestByStringName(context.TODO(), userContext, teamName.String()) 20 require.NoError(t, err) 21 err = team.Rotate(context.TODO(), keybase1.RotationType_HIDDEN) 22 require.NoError(t, err) 23 } 24 25 func loadTeamAndAssertCommittedAndUncommittedSeqnos(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, uncommittedSeqno keybase1.Seqno) { 26 // since polling does not take into account hidden chain updates, manually update the merkle root. 27 _, err := tc.G.GetMerkleClient().FetchRootFromServer(libkb.NewMetaContextForTest(*tc), 0) 28 require.NoError(t, err) 29 _, teamHiddenChain, err := tc.G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 30 ID: teamID, 31 ForceRepoll: true, 32 }) 33 require.NoError(t, err) 34 require.Equal(t, uncommittedSeqno, teamHiddenChain.Last, "committed seqno") 35 } 36 37 func assertHiddenMerkleErrorType(t *testing.T, err error, expType libkb.HiddenMerkleErrorType) { 38 require.Error(t, err) 39 require.IsType(t, libkb.HiddenMerkleError{}, err) 40 require.Equal(t, err.(libkb.HiddenMerkleError).ErrorType(), expType) 41 } 42 43 func loadTeamAndCheckCommittedAndUncommittedSeqnos(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, uncommittedSeqno keybase1.Seqno) bool { 44 _, teamHiddenChain, err := tc.G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 45 ID: teamID, 46 ForceRepoll: true, 47 }) 48 require.NoError(t, err) 49 if uncommittedSeqno != teamHiddenChain.Last { 50 t.Logf("Error: uncommittedSeqno != teamHiddenChain.Last: %v != %v ", uncommittedSeqno, teamHiddenChain.Last) 51 return false 52 } 53 return true 54 } 55 56 func loadTeamAndAssertUncommittedSeqno(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, uncommittedSeqno keybase1.Seqno) { 57 _, teamHiddenChain, err := tc.G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 58 ID: teamID, 59 ForceRepoll: true, 60 }) 61 require.NoError(t, err) 62 require.Equal(t, uncommittedSeqno, teamHiddenChain.Last) 63 } 64 65 func loadTeamAndAssertNoHiddenChainExists(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID) { 66 teamChain, teamHiddenChain, err := tc.G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 67 ID: teamID, 68 ForceRepoll: true, 69 }) 70 require.NoError(t, err) 71 require.NotNil(t, teamChain) 72 require.Nil(t, teamHiddenChain) 73 } 74 75 func getCurrentBlindRootHashFromMerkleRoot(t *testing.T, tc libkb.TestContext) string { 76 apiRes, err := tc.G.API.Get(libkb.NewMetaContextForTest(tc), libkb.APIArg{ 77 Endpoint: "merkle/root", 78 }) 79 require.NoError(t, err) 80 payloadStr, err := apiRes.Body.AtKey("payload_json").GetString() 81 require.NoError(t, err) 82 payload, err := jsonw.Unmarshal([]byte(payloadStr)) 83 require.NoError(t, err) 84 blindRoot, err := payload.AtKey("body").AtKey("blind_merkle_root_hash").GetString() 85 require.NoError(t, err) 86 87 return blindRoot 88 } 89 90 func makePaperKey(t *testing.T, uTc *libkb.TestContext) { 91 uis := libkb.UIs{ 92 LogUI: uTc.G.UI.GetLogUI(), 93 LoginUI: &libkb.TestLoginUI{}, 94 SecretUI: &libkb.TestSecretUI{}, 95 } 96 eng := engine.NewPaperKey(uTc.G) 97 err := engine.RunEngine2(libkb.NewMetaContextForTest(*uTc).WithUIs(uis), eng) 98 require.NoError(t, err) 99 } 100 101 func requestNewBlindTreeFromArchitectAndWaitUntilDone(t *testing.T, uTc *libkb.TestContext) { 102 oldBlindRoot := getCurrentBlindRootHashFromMerkleRoot(t, *uTc) 103 104 // make the architect run. This returns when the architect has finished a round 105 _, err := uTc.G.API.Get(libkb.NewMetaContextForTest(*uTc), libkb.APIArg{ 106 Endpoint: "test/build_blind_tree", 107 }) 108 require.NoError(t, err) 109 110 // the user adds a paper key to make new main merkle tree version. 111 makePaperKey(t, uTc) 112 113 // ensure the architect actually updated 114 newBlindRoot := getCurrentBlindRootHashFromMerkleRoot(t, *uTc) 115 require.NotEqual(t, oldBlindRoot, newBlindRoot) 116 } 117 118 func retryTestNTimes(t *testing.T, n int, f func(t *testing.T) bool) { 119 for i := 0; i < n; i++ { 120 succeeded := f(t) 121 if succeeded { 122 t.Logf("Succeeded!") 123 return 124 } 125 } 126 t.Errorf("Test did not succeed any of the %v times", n) 127 } 128 func TestHiddenLoadSucceedsIfServerDoesntCommitLinks(t *testing.T) { 129 retryTestNTimes(t, 5, testHiddenLoadSucceedsIfServerDoesntCommitLinks) 130 } 131 132 func testHiddenLoadSucceedsIfServerDoesntCommitLinks(t *testing.T) bool { 133 fus, tcs, cleanup := setupNTests(t, 2) 134 defer cleanup() 135 136 clock := clockwork.NewFakeClock() 137 tcs[1].G.SetClock(clock) 138 139 t.Logf("create team") 140 teamName, teamID := createTeam2(*tcs[0]) 141 142 t.Logf("add B to the team so they can load it") 143 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 144 require.NoError(t, err) 145 146 // There have been no hidden rotations yet. 147 loadTeamAndAssertNoHiddenChainExists(t, tcs[1], teamID) 148 149 makeHiddenRotation(t, tcs[0].G, teamName) 150 151 loadTeamAndAssertUncommittedSeqno(t, tcs[1], teamID, 1) 152 153 // make the architect run 154 requestNewBlindTreeFromArchitectAndWaitUntilDone(t, tcs[0]) 155 156 loadTeamAndAssertCommittedAndUncommittedSeqnos(t, tcs[1], teamID, 1) 157 158 // make another hidden rotation 159 makeHiddenRotation(t, tcs[0].G, teamName) 160 161 // This has the potential to flake, if the architect runs concurrently and does make a new blind tree version. 162 if !loadTeamAndCheckCommittedAndUncommittedSeqnos(t, tcs[1], teamID, 2) { 163 return false 164 } 165 166 // now, move the clock forward and reload. The hidden loader should complain about seqno 2 not being committed 167 clock.Advance(2 * hidden.MaxDelayInCommittingHiddenLinks) 168 tcs[1].G.SetClock(clock) 169 _, _, err = tcs[1].G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 170 ID: teamID, 171 ForceRepoll: true, 172 }) 173 174 return err == nil 175 } 176 177 type CorruptingMockLoaderContext struct { 178 LoaderContext 179 180 merkleCorruptorFunc func(r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) (keybase1.Seqno, keybase1.LinkID, *libkb.MerkleHiddenResponse, *libkb.MerkleRoot, error) 181 } 182 183 func (c CorruptingMockLoaderContext) merkleLookupWithHidden(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) { 184 return c.merkleCorruptorFunc(c.LoaderContext.merkleLookupWithHidden(ctx, teamID, public)) 185 } 186 187 var _ LoaderContext = CorruptingMockLoaderContext{} 188 189 func TestHiddenLoadFailsIfServerRollsbackUncommittedSeqno(t *testing.T) { 190 fus, tcs, cleanup := setupNTests(t, 2) 191 defer cleanup() 192 193 t.Logf("create team") 194 teamName, teamID := createTeam2(*tcs[0]) 195 196 t.Logf("add B to the team so they can load it") 197 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 198 require.NoError(t, err) 199 200 // There have been no hidden rotations yet. 201 loadTeamAndAssertNoHiddenChainExists(t, tcs[1], teamID) 202 203 makeHiddenRotation(t, tcs[0].G, teamName) 204 205 loadTeamAndAssertUncommittedSeqno(t, tcs[1], teamID, 1) 206 207 // now load the team again, but this time we change the response of the server to rollback the number of committed sequence numbers 208 newLoader := tcs[1].G.GetTeamLoader() 209 newLoader.(*TeamLoader).world = CorruptingMockLoaderContext{ 210 LoaderContext: newLoader.(*TeamLoader).world, 211 merkleCorruptorFunc: func(r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) (keybase1.Seqno, keybase1.LinkID, *libkb.MerkleHiddenResponse, *libkb.MerkleRoot, error) { 212 if hiddenResp != nil && hiddenResp.UncommittedSeqno >= 1 { 213 hiddenResp.UncommittedSeqno-- 214 t.Logf("Simulating malicious server: updating hiddenResp.UncommittedSeqno (new value %v)", hiddenResp.UncommittedSeqno) 215 } 216 return r1, r2, hiddenResp, lastMerkleRoot, err 217 }, 218 } 219 tcs[1].G.SetTeamLoader(newLoader) 220 221 _, _, err = tcs[1].G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 222 ID: teamID, 223 ForceRepoll: true, 224 }) 225 assertHiddenMerkleErrorType(t, err, libkb.HiddenMerkleErrorRollbackUncommittedSeqno) 226 } 227 228 func TestHiddenLoadFailsIfServerDoesNotReturnPromisedLinks(t *testing.T) { 229 fus, tcs, cleanup := setupNTests(t, 2) 230 defer cleanup() 231 232 t.Logf("create team") 233 teamName, teamID := createTeam2(*tcs[0]) 234 235 t.Logf("add B to the team so they can load it") 236 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 237 require.NoError(t, err) 238 239 // There have been no hidden rotations yet. 240 loadTeamAndAssertNoHiddenChainExists(t, tcs[1], teamID) 241 242 makeHiddenRotation(t, tcs[0].G, teamName) 243 244 loadTeamAndAssertUncommittedSeqno(t, tcs[1], teamID, 1) 245 246 // now load the team again, but this time we change the response of the server as if there were more hidden links 247 newLoader := tcs[1].G.GetTeamLoader() 248 newLoader.(*TeamLoader).world = CorruptingMockLoaderContext{ 249 LoaderContext: newLoader.(*TeamLoader).world, 250 merkleCorruptorFunc: func(r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) (keybase1.Seqno, keybase1.LinkID, *libkb.MerkleHiddenResponse, *libkb.MerkleRoot, error) { 251 if hiddenResp != nil && hiddenResp.UncommittedSeqno >= 1 { 252 hiddenResp.UncommittedSeqno += 5 253 t.Logf("Simulating malicious server: updating hiddenResp.UncommittedSeqno (new value %v)", hiddenResp.UncommittedSeqno) 254 } 255 return r1, r2, hiddenResp, lastMerkleRoot, err 256 }, 257 } 258 tcs[1].G.SetTeamLoader(newLoader) 259 260 _, _, err = tcs[1].G.GetTeamLoader().Load(context.TODO(), keybase1.LoadTeamArg{ 261 ID: teamID, 262 ForceRepoll: true, 263 }) 264 assertHiddenMerkleErrorType(t, err, libkb.HiddenMerkleErrorServerWitholdingLinks) 265 } 266 267 func loadTeamFTLAndAssertName(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, teamName keybase1.TeamName) { 268 res, err := tc.G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tc), keybase1.FastTeamLoadArg{ 269 ID: teamID, 270 ForceRefresh: true, 271 }) 272 require.NoError(t, err) 273 require.Equal(t, res.Name.String(), teamName.String()) 274 } 275 276 func loadTeamFTLAndAssertGeneration(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, teamName keybase1.TeamName, perTeamKeyGeneration int) { 277 res, err := tc.G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tc), keybase1.FastTeamLoadArg{ 278 ID: teamID, 279 ForceRefresh: true, 280 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 281 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(perTeamKeyGeneration)}, 282 }) 283 require.NoError(t, err) 284 require.Equal(t, res.Name.String(), teamName.String()) 285 require.Equal(t, 1, len(res.ApplicationKeys)) 286 require.EqualValues(t, perTeamKeyGeneration, res.ApplicationKeys[0].KeyGeneration) 287 } 288 289 func loadTeamFTLAndAssertGenerationUnavailable(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, perTeamKeyGeneration int) { 290 _, err := tc.G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tc), keybase1.FastTeamLoadArg{ 291 ID: teamID, 292 ForceRefresh: true, 293 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 294 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(perTeamKeyGeneration)}, 295 }) 296 require.Error(t, err) 297 require.IsType(t, FTLMissingSeedError{}, err) 298 } 299 300 func loadTeamFTLAndAssertMaxGeneration(t *testing.T, tc *libkb.TestContext, teamID keybase1.TeamID, teamName keybase1.TeamName, perTeamKeyGeneration int) { 301 _, err := tc.G.GetMerkleClient().FetchRootFromServer(libkb.NewMetaContextForTest(*tc), 0) 302 require.NoError(t, err) 303 loadTeamFTLAndAssertGeneration(t, tc, teamID, teamName, perTeamKeyGeneration) 304 loadTeamFTLAndAssertGenerationUnavailable(t, tc, teamID, perTeamKeyGeneration+1) 305 } 306 307 func TestFTLSucceedsIfServerDoesntCommitLinks(t *testing.T) { 308 retryTestNTimes(t, 5, testFTLSucceedsIfServerDoesntCommitLinks) 309 } 310 311 func testFTLSucceedsIfServerDoesntCommitLinks(t *testing.T) bool { 312 fus, tcs, cleanup := setupNTests(t, 2) 313 defer cleanup() 314 315 clock := clockwork.NewFakeClock() 316 tcs[1].G.SetClock(clock) 317 318 t.Logf("create team") 319 teamName, teamID := createTeam2(*tcs[0]) 320 321 t.Logf("add B to the team so they can load it") 322 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 323 require.NoError(t, err) 324 325 loadTeamFTLAndAssertName(t, tcs[1], teamID, teamName) 326 327 makeHiddenRotation(t, tcs[0].G, teamName) 328 329 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 2) 330 331 requestNewBlindTreeFromArchitectAndWaitUntilDone(t, tcs[0]) 332 333 // make another hidden rotation 334 makeHiddenRotation(t, tcs[0].G, teamName) 335 336 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 3) 337 338 // now, move the clock forward and reload. The hidden loader should complain about hidden seqno 2 not being committed 339 clock.Advance(2 * hidden.MaxDelayInCommittingHiddenLinks) 340 tcs[1].G.SetClock(clock) 341 _, err = tcs[1].G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tcs[1]), keybase1.FastTeamLoadArg{ 342 ID: teamID, 343 ForceRefresh: true, 344 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 345 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(3)}, 346 }) 347 348 // This has the potential to flake, if the architect runs concurrently and does make a new blind tree version. 349 return err == nil 350 } 351 352 func TestFTLFailsIfServerRollsbackUncommittedSeqno(t *testing.T) { 353 fus, tcs, cleanup := setupNTests(t, 2) 354 defer cleanup() 355 356 t.Logf("create team") 357 teamName, teamID := createTeam2(*tcs[0]) 358 359 t.Logf("add B to the team so they can load it") 360 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 361 require.NoError(t, err) 362 363 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 1) 364 365 makeHiddenRotation(t, tcs[0].G, teamName) 366 367 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 2) 368 369 // now load the team again, but this time we change the response of the server to rollback the number of committed sequence numbers 370 newLoader := tcs[1].G.GetFastTeamLoader() 371 newLoader.(*FastTeamChainLoader).world = CorruptingMockLoaderContext{ 372 LoaderContext: newLoader.(*FastTeamChainLoader).world, 373 merkleCorruptorFunc: func(r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) (keybase1.Seqno, keybase1.LinkID, *libkb.MerkleHiddenResponse, *libkb.MerkleRoot, error) { 374 if hiddenResp != nil && hiddenResp.UncommittedSeqno >= 1 { 375 hiddenResp.UncommittedSeqno-- 376 t.Logf("Simulating malicious server: updating hiddenResp.UncommittedSeqno (new value %v)", hiddenResp.UncommittedSeqno) 377 } 378 return r1, r2, hiddenResp, lastMerkleRoot, err 379 }, 380 } 381 tcs[1].G.SetFastTeamLoader(newLoader) 382 383 _, err = tcs[1].G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tcs[1]), keybase1.FastTeamLoadArg{ 384 ID: teamID, 385 ForceRefresh: true, 386 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 387 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(2)}, 388 }) 389 assertHiddenMerkleErrorType(t, err, libkb.HiddenMerkleErrorRollbackUncommittedSeqno) 390 } 391 392 func TestFTLFailsIfServerDoesNotReturnPromisedLinks(t *testing.T) { 393 fus, tcs, cleanup := setupNTests(t, 2) 394 defer cleanup() 395 396 t.Logf("create team") 397 teamName, teamID := createTeam2(*tcs[0]) 398 399 t.Logf("add B to the team so they can load it") 400 _, err := AddMember(context.TODO(), tcs[0].G, teamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 401 require.NoError(t, err) 402 403 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 1) 404 405 makeHiddenRotation(t, tcs[0].G, teamName) 406 407 loadTeamFTLAndAssertMaxGeneration(t, tcs[1], teamID, teamName, 2) 408 409 makeHiddenRotation(t, tcs[0].G, teamName) 410 411 // now load the team again, but this time we change the response of the server as if there were more hidden links 412 newLoader := tcs[1].G.GetFastTeamLoader() 413 newLoader.(*FastTeamChainLoader).world = CorruptingMockLoaderContext{ 414 LoaderContext: newLoader.(*FastTeamChainLoader).world, 415 merkleCorruptorFunc: func(r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) (keybase1.Seqno, keybase1.LinkID, *libkb.MerkleHiddenResponse, *libkb.MerkleRoot, error) { 416 if hiddenResp != nil && hiddenResp.UncommittedSeqno >= 1 { 417 hiddenResp.UncommittedSeqno += 5 418 t.Logf("Simulating malicious server: updating hiddenResp.UncommittedSeqno (new value %v)", hiddenResp.UncommittedSeqno) 419 } 420 return r1, r2, hiddenResp, lastMerkleRoot, err 421 }, 422 } 423 tcs[1].G.SetFastTeamLoader(newLoader) 424 425 _, err = tcs[1].G.GetFastTeamLoader().Load(libkb.NewMetaContextForTest(*tcs[1]), keybase1.FastTeamLoadArg{ 426 ID: teamID, 427 ForceRefresh: true, 428 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 429 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(3)}, 430 }) 431 assertHiddenMerkleErrorType(t, err, libkb.HiddenMerkleErrorServerWitholdingLinks) 432 } 433 434 func TestSubteamReaderFTL(t *testing.T) { 435 fus, tcs, cleanup := setupNTests(t, 3) 436 defer cleanup() 437 438 t.Logf("create team") 439 teamName, teamID := createTeam2(*tcs[0]) 440 for i := 0; i < 4; i++ { 441 makeHiddenRotation(t, tcs[0].G, teamName) 442 } 443 createSubteam(tcs[0], teamName, "unused") 444 subteamName, subteamID := createSubteam(tcs[0], teamName, "sub") 445 _, err := AddMember(context.TODO(), tcs[0].G, subteamName.String(), fus[1].Username, keybase1.TeamRole_WRITER, nil) 446 require.NoError(t, err) 447 mctx := libkb.NewMetaContextForTest(*tcs[1]) 448 _, err = mctx.G().GetFastTeamLoader().Load(mctx, keybase1.FastTeamLoadArg{ 449 ID: subteamID, 450 ForceRefresh: true, 451 Applications: []keybase1.TeamApplication{keybase1.TeamApplication_CHAT}, 452 KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{keybase1.PerTeamKeyGeneration(1)}, 453 }) 454 require.NoError(t, err) 455 456 // Check that U1's hidden team data for the parent team is still stored, and that 457 // the ratchets persist (even though there's no other data there). 458 var htc *keybase1.HiddenTeamChain 459 htc, err = mctx.G().GetHiddenTeamChainManager().Load(mctx, teamID) 460 require.NoError(t, err) 461 require.NotNil(t, htc) 462 ratchet, ok := htc.RatchetSet.Ratchets[keybase1.RatchetType_MAIN] 463 require.True(t, ok) 464 require.Equal(t, ratchet.Triple.Seqno, keybase1.Seqno(4)) 465 require.Equal(t, htc.Last, keybase1.Seqno(0)) 466 require.Equal(t, len(htc.Outer), 0) 467 }