diff options
Diffstat (limited to 'internal/state/single_test.go')
-rw-r--r-- | internal/state/single_test.go | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/internal/state/single_test.go b/internal/state/single_test.go new file mode 100644 index 0000000..9442fdc --- /dev/null +++ b/internal/state/single_test.go @@ -0,0 +1,233 @@ +package state + +import ( + "bytes" + "context" + "crypto" + "crypto/ed25519" + "crypto/rand" + "fmt" + "io" + "reflect" + "testing" + "time" + + mocksClient "git.sigsum.org/log-go/internal/mocks/client" + mocksDB "git.sigsum.org/log-go/internal/mocks/db" + "git.sigsum.org/sigsum-go/pkg/hex" + "git.sigsum.org/sigsum-go/pkg/merkle" + "git.sigsum.org/sigsum-go/pkg/types" + "github.com/golang/mock/gomock" +) + +// TestSigner implements the signer interface. It can be used to mock +// an Ed25519 signer that always return the same public key, +// signature, and error. +// NOTE: Code duplication with internal/node/secondary/endpoint_internal_test.go +type TestSigner struct { + PublicKey [ed25519.PublicKeySize]byte + Signature [ed25519.SignatureSize]byte + Error error +} + +func (ts *TestSigner) Public() crypto.PublicKey { + return ed25519.PublicKey(ts.PublicKey[:]) +} + +func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return ts.Signature[:], ts.Error +} + +func TestNewStateManagerSingle(t *testing.T) { + signerOk := &TestSigner{types.PublicKey{}, types.Signature{}, nil} + signerErr := &TestSigner{types.PublicKey{}, types.Signature{}, fmt.Errorf("err")} + for _, table := range []struct { + description string + signer crypto.Signer + thExp bool + thErr error + th types.TreeHead + secExp bool + wantErr bool + }{ + {"invalid: signer failure", signerErr, false, nil, types.TreeHead{}, false, true}, + {"valid", signerOk, true, nil, types.TreeHead{Timestamp: now(t)}, true, false}, + } { + func() { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + trillianClient := mocksDB.NewMockClient(ctrl) + if table.thExp { + trillianClient.EXPECT().GetTreeHead(gomock.Any()).Return(&table.th, table.thErr) + } + secondary := mocksClient.NewMockClient(ctrl) + if table.secExp { + secondary.EXPECT().Initiated().Return(false) + } + + sm, err := NewStateManagerSingle(trillianClient, table.signer, time.Duration(0), time.Duration(0), secondary) + if got, want := err != nil, table.description != "valid"; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) + } + if err != nil { + return + } + + if got, want := sm.signedTreeHead.TreeSize, table.th.TreeSize; got != want { + t.Errorf("%q: got tree size %d but wanted %d", table.description, got, want) + } + if got, want := sm.signedTreeHead.RootHash[:], table.th.RootHash[:]; !bytes.Equal(got, want) { + t.Errorf("%q: got tree size %v but wanted %v", table.description, got, want) + } + if got, want := sm.signedTreeHead.Timestamp, table.th.Timestamp; got < want { + t.Errorf("%q: got timestamp %d but wanted at least %d", table.description, got, want) + } + if got := sm.cosignedTreeHead; got != nil { + t.Errorf("%q: got cosigned tree head but should have none", table.description) + } + }() + } +} + +func TestToCosignTreeHead(t *testing.T) { + want := &types.SignedTreeHead{} + sm := StateManagerSingle{ + signedTreeHead: want, + } + sth := sm.ToCosignTreeHead() + if got := sth; !reflect.DeepEqual(got, want) { + t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v", got, want) + } +} + +func TestCosignedTreeHead(t *testing.T) { + want := &types.CosignedTreeHead{ + Cosignature: make([]types.Signature, 1), + KeyHash: make([]merkle.Hash, 1), + } + sm := StateManagerSingle{ + cosignedTreeHead: want, + } + cth, err := sm.CosignedTreeHead(context.Background()) + if err != nil { + t.Errorf("should not fail with error: %v", err) + return + } + if got := cth; !reflect.DeepEqual(got, want) { + t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v", got, want) + } + + sm.cosignedTreeHead = nil + cth, err = sm.CosignedTreeHead(context.Background()) + if err == nil { + t.Errorf("should fail without a cosigned tree head") + return + } +} + +func TestAddCosignature(t *testing.T) { + secret, public := mustKeyPair(t) + for _, table := range []struct { + desc string + signer crypto.Signer + vk types.PublicKey + wantErr bool + }{ + { + desc: "invalid: wrong public key", + signer: secret, + vk: types.PublicKey{}, + wantErr: true, + }, + { + desc: "valid", + signer: secret, + vk: public, + }, + } { + sm := &StateManagerSingle{ + namespace: *merkle.HashFn(nil), + signedTreeHead: &types.SignedTreeHead{}, + events: make(chan *event, 1), + } + defer close(sm.events) + + sth := mustSign(t, table.signer, &sm.signedTreeHead.TreeHead, &sm.namespace) + ctx := context.Background() + err := sm.AddCosignature(ctx, &table.vk, &sth.Signature) + if got, want := err != nil, table.wantErr; got != want { + t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) + } + if err != nil { + continue + } + + ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond) + defer cancel() + if err := sm.AddCosignature(ctx, &table.vk, &sth.Signature); err == nil { + t.Errorf("expected full channel in test %q", table.desc) + } + if got, want := len(sm.events), 1; got != want { + t.Errorf("wanted %d cosignatures but got %d in test %q", want, got, table.desc) + } + } +} + +func mustKeyPair(t *testing.T) (crypto.Signer, types.PublicKey) { + t.Helper() + vk, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + var pub types.PublicKey + copy(pub[:], vk[:]) + return sk, pub +} + +func mustSign(t *testing.T, s crypto.Signer, th *types.TreeHead, kh *merkle.Hash) *types.SignedTreeHead { + t.Helper() + sth, err := th.Sign(s, kh) + if err != nil { + t.Fatal(err) + } + return sth +} + +func newHashBufferInc(t *testing.T) *merkle.Hash { + t.Helper() + + var buf merkle.Hash + for i := 0; i < len(buf); i++ { + buf[i] = byte(i) + } + return &buf +} +func validConsistencyProof_5_10(t *testing.T) *types.ConsistencyProof { + t.Helper() + // # old tree head + // tree_size=5 + // root_hash=c8e73a8c09e44c344d515eb717e248c5dbf12420908a6d29568197fae7751803 + // # new tree head + // tree_size=10 + // root_hash=2a40f11563b45522ca9eccf993c934238a8fbadcf7d7d65be3583ab2584838aa + r := bytes.NewReader([]byte("consistency_path=fadca95ab8ca34f17c5f3fa719183fe0e5c194a44c25324745388964a743ecce\nconsistency_path=6366fc0c20f9b8a8c089ed210191e401da6c995592eba78125f0ba0ba142ebaf\nconsistency_path=72b8d4f990b555a72d76fb8da075a65234519070cfa42e082026a8c686160349\nconsistency_path=d92714be792598ff55560298cd3ff099dfe5724646282578531c0d0063437c00\nconsistency_path=4b20d58bbae723755304fb179aef6d5f04d755a601884828c62c07929f6bd84a\n")) + var proof types.ConsistencyProof + if err := proof.FromASCII(r, 5, 10); err != nil { + t.Fatal(err) + } + return &proof +} + +func hashFromString(t *testing.T, s string) (h merkle.Hash) { + b, err := hex.Deserialize(s) + if err != nil { + t.Fatal(err) + } + copy(h[:], b) + return h +} + +func now(t *testing.T) uint64 { + t.Helper() + return uint64(time.Now().Unix()) +} |