|
|
@@ -524,3 +524,131 @@ func TestAuthorityCompact(t *testing.T) {
|
|
|
t.Errorf("ancestor = %v, want %v", anc, c.AUMHashes["C"])
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func TestFindParentForRewrite(t *testing.T) {
|
|
|
+ pub, _ := testingKey25519(t, 1)
|
|
|
+ k1 := Key{Kind: Key25519, Public: pub, Votes: 1}
|
|
|
+
|
|
|
+ pub2, _ := testingKey25519(t, 2)
|
|
|
+ k2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
|
|
+ k2ID, _ := k2.ID()
|
|
|
+ pub3, _ := testingKey25519(t, 3)
|
|
|
+ k3 := Key{Kind: Key25519, Public: pub3, Votes: 1}
|
|
|
+
|
|
|
+ c := newTestchain(t, `
|
|
|
+ A -> B -> C -> D -> E
|
|
|
+ A.template = genesis
|
|
|
+ B.template = add2
|
|
|
+ C.template = add3
|
|
|
+ D.template = remove2
|
|
|
+ `,
|
|
|
+ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
|
|
|
+ Keys: []Key{k1},
|
|
|
+ DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})},
|
|
|
+ }}),
|
|
|
+ optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}),
|
|
|
+ optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3}),
|
|
|
+ optTemplate("remove2", AUM{MessageKind: AUMRemoveKey, KeyID: k2ID}))
|
|
|
+
|
|
|
+ a, err := Open(c.Chonk())
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // k1 was trusted at genesis, so there's no better rewrite parent
|
|
|
+ // than the genesis.
|
|
|
+ k1ID, _ := k1.ID()
|
|
|
+ k1P, err := a.findParentForRewrite(c.Chonk(), []tkatype.KeyID{k1ID}, k1ID)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("FindParentForRewrite(k1) failed: %v", err)
|
|
|
+ }
|
|
|
+ if k1P != a.oldestAncestor.Hash() {
|
|
|
+ t.Errorf("FindParentForRewrite(k1) = %v, want %v", k1P, a.oldestAncestor.Hash())
|
|
|
+ }
|
|
|
+
|
|
|
+ // k3 was trusted at C, so B would be an ideal rewrite point.
|
|
|
+ k3ID, _ := k3.ID()
|
|
|
+ k3P, err := a.findParentForRewrite(c.Chonk(), []tkatype.KeyID{k3ID}, k1ID)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("FindParentForRewrite(k3) failed: %v", err)
|
|
|
+ }
|
|
|
+ if k3P != c.AUMHashes["B"] {
|
|
|
+ t.Errorf("FindParentForRewrite(k3) = %v, want %v", k3P, c.AUMHashes["B"])
|
|
|
+ }
|
|
|
+
|
|
|
+ // k2 was added but then removed, so HEAD is an appropriate rewrite point.
|
|
|
+ k2P, err := a.findParentForRewrite(c.Chonk(), []tkatype.KeyID{k2ID}, k1ID)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("FindParentForRewrite(k2) failed: %v", err)
|
|
|
+ }
|
|
|
+ if k3P != c.AUMHashes["B"] {
|
|
|
+ t.Errorf("FindParentForRewrite(k2) = %v, want %v", k2P, a.Head())
|
|
|
+ }
|
|
|
+
|
|
|
+ // There's no appropriate point where both k2 and k3 are simultaneously not trusted,
|
|
|
+ // so the best rewrite point is the genesis AUM.
|
|
|
+ doubleP, err := a.findParentForRewrite(c.Chonk(), []tkatype.KeyID{k2ID, k3ID}, k1ID)
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("FindParentForRewrite({k2, k3}) failed: %v", err)
|
|
|
+ }
|
|
|
+ if doubleP != a.oldestAncestor.Hash() {
|
|
|
+ t.Errorf("FindParentForRewrite({k2, k3}) = %v, want %v", doubleP, a.oldestAncestor.Hash())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMakeRetroactiveRevocation(t *testing.T) {
|
|
|
+ pub, _ := testingKey25519(t, 1)
|
|
|
+ k1 := Key{Kind: Key25519, Public: pub, Votes: 1}
|
|
|
+
|
|
|
+ pub2, _ := testingKey25519(t, 2)
|
|
|
+ k2 := Key{Kind: Key25519, Public: pub2, Votes: 1}
|
|
|
+ pub3, _ := testingKey25519(t, 3)
|
|
|
+ k3 := Key{Kind: Key25519, Public: pub3, Votes: 1}
|
|
|
+
|
|
|
+ c := newTestchain(t, `
|
|
|
+ A -> B -> C -> D
|
|
|
+ A.template = genesis
|
|
|
+ C.template = add2
|
|
|
+ D.template = add3
|
|
|
+ `,
|
|
|
+ optTemplate("genesis", AUM{MessageKind: AUMCheckpoint, State: &State{
|
|
|
+ Keys: []Key{k1},
|
|
|
+ DisablementSecrets: [][]byte{DisablementKDF([]byte{1, 2, 3})},
|
|
|
+ }}),
|
|
|
+ optTemplate("add2", AUM{MessageKind: AUMAddKey, Key: &k2}),
|
|
|
+ optTemplate("add3", AUM{MessageKind: AUMAddKey, Key: &k3}))
|
|
|
+
|
|
|
+ a, err := Open(c.Chonk())
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // k2 was added by C, so a forking revocation should:
|
|
|
+ // - have B as a parent
|
|
|
+ // - trust the remaining keys at the time, k1 & k3.
|
|
|
+ k1ID, _ := k1.ID()
|
|
|
+ k2ID, _ := k2.ID()
|
|
|
+ k3ID, _ := k3.ID()
|
|
|
+ forkingAUM, err := a.MakeRetroactiveRevocation(c.Chonk(), []tkatype.KeyID{k2ID}, k1ID, AUMHash{})
|
|
|
+ if err != nil {
|
|
|
+ t.Fatalf("MakeRetroactiveRevocation(k2) failed: %v", err)
|
|
|
+ }
|
|
|
+ if bHash := c.AUMHashes["B"]; !bytes.Equal(forkingAUM.PrevAUMHash, bHash[:]) {
|
|
|
+ t.Errorf("forking AUM has parent %v, want %v", forkingAUM.PrevAUMHash, bHash[:])
|
|
|
+ }
|
|
|
+ if _, err := forkingAUM.State.GetKey(k1ID); err != nil {
|
|
|
+ t.Error("Forked state did not trust k1")
|
|
|
+ }
|
|
|
+ if _, err := forkingAUM.State.GetKey(k3ID); err != nil {
|
|
|
+ t.Error("Forked state did not trust k3")
|
|
|
+ }
|
|
|
+ if _, err := forkingAUM.State.GetKey(k2ID); err == nil {
|
|
|
+ t.Error("Forked state trusted removed-key k2")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test that removing all trusted keys results in an error.
|
|
|
+ _, err = a.MakeRetroactiveRevocation(c.Chonk(), []tkatype.KeyID{k1ID, k2ID, k3ID}, k1ID, AUMHash{})
|
|
|
+ if wantErr := "cannot revoke all trusted keys"; err == nil || err.Error() != wantErr {
|
|
|
+ t.Fatalf("MakeRetroactiveRevocation({k1, k2, k3}) returned %v, expected %q", err, wantErr)
|
|
|
+ }
|
|
|
+}
|