diff options
Diffstat (limited to 'pkg/trillian')
-rw-r--r-- | pkg/trillian/client.go | 178 | ||||
-rw-r--r-- | pkg/trillian/client_test.go | 533 | ||||
-rw-r--r-- | pkg/trillian/util.go | 33 |
3 files changed, 0 insertions, 744 deletions
diff --git a/pkg/trillian/client.go b/pkg/trillian/client.go deleted file mode 100644 index 6fe8ce4..0000000 --- a/pkg/trillian/client.go +++ /dev/null @@ -1,178 +0,0 @@ -package trillian - -import ( - "context" - "fmt" - - "github.com/golang/glog" - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "git.sigsum.org/sigsum-log-go/pkg/types" - "google.golang.org/grpc/codes" -) - -type Client interface { - AddLeaf(context.Context, *types.LeafRequest) error - GetConsistencyProof(context.Context, *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) - GetTreeHead(context.Context) (*types.TreeHead, error) - GetInclusionProof(context.Context, *types.InclusionProofRequest) (*types.InclusionProof, error) - GetLeaves(context.Context, *types.LeavesRequest) (*types.LeafList, error) -} - -// TrillianClient is a wrapper around the Trillian gRPC client. -type TrillianClient struct { - // TreeID is a Merkle tree identifier that Trillian uses - TreeID int64 - - // GRPC is a Trillian gRPC client - GRPC trillian.TrillianLogClient -} - -func (c *TrillianClient) AddLeaf(ctx context.Context, req *types.LeafRequest) error { - leaf := types.Leaf{ - Message: req.Message, - SigIdent: types.SigIdent{ - Signature: req.Signature, - KeyHash: types.Hash(req.VerificationKey[:]), - }, - } - serialized := leaf.Marshal() - - glog.V(3).Infof("queueing leaf request: %x", types.HashLeaf(serialized)) - rsp, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{ - LogId: c.TreeID, - Leaf: &trillian.LogLeaf{ - LeafValue: serialized, - }, - }) - if err != nil { - return fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return fmt.Errorf("no response") - } - if rsp.QueuedLeaf == nil { - return fmt.Errorf("no queued leaf") - } - if codes.Code(rsp.QueuedLeaf.GetStatus().GetCode()) == codes.AlreadyExists { - return fmt.Errorf("leaf is already queued or included") - } - return nil -} - -func (c *TrillianClient) GetTreeHead(ctx context.Context) (*types.TreeHead, error) { - rsp, err := c.GRPC.GetLatestSignedLogRoot(ctx, &trillian.GetLatestSignedLogRootRequest{ - LogId: c.TreeID, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if rsp.SignedLogRoot == nil { - return nil, fmt.Errorf("no signed log root") - } - if rsp.SignedLogRoot.LogRoot == nil { - return nil, fmt.Errorf("no log root") - } - var r ttypes.LogRootV1 - if err := r.UnmarshalBinary(rsp.SignedLogRoot.LogRoot); err != nil { - return nil, fmt.Errorf("no log root: unmarshal failed: %v", err) - } - if len(r.RootHash) != types.HashSize { - return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash)) - } - return treeHeadFromLogRoot(&r), nil -} - -func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *types.ConsistencyProofRequest) (*types.ConsistencyProof, error) { - rsp, err := c.GRPC.GetConsistencyProof(ctx, &trillian.GetConsistencyProofRequest{ - LogId: c.TreeID, - FirstTreeSize: int64(req.OldSize), - SecondTreeSize: int64(req.NewSize), - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if rsp.Proof == nil { - return nil, fmt.Errorf("no consistency proof") - } - if len(rsp.Proof.Hashes) == 0 { - return nil, fmt.Errorf("not a consistency proof: empty") - } - path, err := nodePathFromHashes(rsp.Proof.Hashes) - if err != nil { - return nil, fmt.Errorf("not a consistency proof: %v", err) - } - return &types.ConsistencyProof{ - OldSize: req.OldSize, - NewSize: req.NewSize, - Path: path, - }, nil -} - -func (c *TrillianClient) GetInclusionProof(ctx context.Context, req *types.InclusionProofRequest) (*types.InclusionProof, error) { - rsp, err := c.GRPC.GetInclusionProofByHash(ctx, &trillian.GetInclusionProofByHashRequest{ - LogId: c.TreeID, - LeafHash: req.LeafHash[:], - TreeSize: int64(req.TreeSize), - OrderBySequence: true, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if len(rsp.Proof) != 1 { - return nil, fmt.Errorf("bad proof count: %d", len(rsp.Proof)) - } - proof := rsp.Proof[0] - if len(proof.Hashes) == 0 { - return nil, fmt.Errorf("not an inclusion proof: empty") - } - path, err := nodePathFromHashes(proof.Hashes) - if err != nil { - return nil, fmt.Errorf("not an inclusion proof: %v", err) - } - return &types.InclusionProof{ - TreeSize: req.TreeSize, - LeafIndex: uint64(proof.LeafIndex), - Path: path, - }, nil -} - -func (c *TrillianClient) GetLeaves(ctx context.Context, req *types.LeavesRequest) (*types.LeafList, error) { - rsp, err := c.GRPC.GetLeavesByRange(ctx, &trillian.GetLeavesByRangeRequest{ - LogId: c.TreeID, - StartIndex: int64(req.StartSize), - Count: int64(req.EndSize-req.StartSize) + 1, - }) - if err != nil { - return nil, fmt.Errorf("backend failure: %v", err) - } - if rsp == nil { - return nil, fmt.Errorf("no response") - } - if got, want := len(rsp.Leaves), int(req.EndSize-req.StartSize+1); got != want { - return nil, fmt.Errorf("unexpected number of leaves: %d", got) - } - var list types.LeafList - for i, leaf := range rsp.Leaves { - leafIndex := int64(req.StartSize + uint64(i)) - if leafIndex != leaf.LeafIndex { - return nil, fmt.Errorf("unexpected leaf(%d): got index %d", leafIndex, leaf.LeafIndex) - } - - var l types.Leaf - if err := l.Unmarshal(leaf.LeafValue); err != nil { - return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err) - } - list = append(list[:], &l) - } - return &list, nil -} diff --git a/pkg/trillian/client_test.go b/pkg/trillian/client_test.go deleted file mode 100644 index 143c411..0000000 --- a/pkg/trillian/client_test.go +++ /dev/null @@ -1,533 +0,0 @@ -package trillian - -import ( - "context" - "fmt" - "reflect" - "testing" - - "github.com/golang/mock/gomock" - "github.com/google/trillian" - ttypes "github.com/google/trillian/types" - "git.sigsum.org/sigsum-log-go/pkg/mocks" - "git.sigsum.org/sigsum-log-go/pkg/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func TestAddLeaf(t *testing.T) { - req := &types.LeafRequest{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - Signature: &[types.SignatureSize]byte{}, - VerificationKey: &[types.VerificationKeySize]byte{}, - DomainHint: "example.com", - } - for _, table := range []struct { - description string - req *types.LeafRequest - rsp *trillian.QueueLeafResponse - err error - wantErr bool - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: no queued leaf", - req: req, - rsp: &trillian.QueueLeafResponse{}, - wantErr: true, - }, - { - description: "invalid: leaf is already queued or included", - req: req, - rsp: &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - LeafValue: req.Message.Marshal(), - }, - Status: status.New(codes.AlreadyExists, "duplicate").Proto(), - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.QueueLeafResponse{ - QueuedLeaf: &trillian.QueuedLogLeaf{ - Leaf: &trillian.LogLeaf{ - LeafValue: req.Message.Marshal(), - }, - Status: status.New(codes.OK, "ok").Proto(), - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().QueueLeaf(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - err := client.AddLeaf(context.Background(), table.req) - if got, want := err != nil, table.wantErr; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err) - } - }() - } -} - -func TestGetTreeHead(t *testing.T) { - // valid root - root := &ttypes.LogRootV1{ - TreeSize: 0, - RootHash: make([]byte, types.HashSize), - TimestampNanos: 1622585623133599429, - } - buf, err := root.MarshalBinary() - if err != nil { - t.Fatalf("must marshal log root: %v", err) - } - // invalid root - root.RootHash = make([]byte, types.HashSize+1) - bufBadHash, err := root.MarshalBinary() - if err != nil { - t.Fatalf("must marshal log root: %v", err) - } - - for _, table := range []struct { - description string - rsp *trillian.GetLatestSignedLogRootResponse - err error - wantErr bool - wantTh *types.TreeHead - }{ - { - description: "invalid: backend failure", - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - wantErr: true, - }, - { - description: "invalid: no signed log root", - rsp: &trillian.GetLatestSignedLogRootResponse{}, - wantErr: true, - }, - { - description: "invalid: no log root", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{}, - }, - wantErr: true, - }, - { - description: "invalid: no log root: unmarshal failed", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: buf[1:], - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected hash length", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: bufBadHash, - }, - }, - wantErr: true, - }, - { - description: "valid", - rsp: &trillian.GetLatestSignedLogRootResponse{ - SignedLogRoot: &trillian.SignedLogRoot{ - LogRoot: buf, - }, - }, - wantTh: &types.TreeHead{ - Timestamp: 1622585623, - TreeSize: 0, - RootHash: &[types.HashSize]byte{}, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetLatestSignedLogRoot(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - th, err := client.GetTreeHead(context.Background()) - if got, want := err != nil, table.wantErr; 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 := th, table.wantTh; !reflect.DeepEqual(got, want) { - t.Errorf("got tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetConsistencyProof(t *testing.T) { - req := &types.ConsistencyProofRequest{ - OldSize: 1, - NewSize: 3, - } - for _, table := range []struct { - description string - req *types.ConsistencyProofRequest - rsp *trillian.GetConsistencyProofResponse - err error - wantErr bool - wantProof *types.ConsistencyProof - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: no consistency proof", - req: req, - rsp: &trillian.GetConsistencyProofResponse{}, - wantErr: true, - }, - { - description: "invalid: not a consistency proof (1/2)", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{}, - }, - }, - wantErr: true, - }, - { - description: "invalid: not a consistency proof (2/2)", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize+1), - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetConsistencyProofResponse{ - Proof: &trillian.Proof{ - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize), - }, - }, - }, - wantProof: &types.ConsistencyProof{ - OldSize: 1, - NewSize: 3, - Path: []*[types.HashSize]byte{ - &[types.HashSize]byte{}, - &[types.HashSize]byte{}, - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetConsistencyProof(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - proof, err := client.GetConsistencyProof(context.Background(), table.req) - if got, want := err != nil, table.wantErr; 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 := proof, table.wantProof; !reflect.DeepEqual(got, want) { - t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetInclusionProof(t *testing.T) { - req := &types.InclusionProofRequest{ - TreeSize: 4, - LeafHash: &[types.HashSize]byte{}, - } - for _, table := range []struct { - description string - req *types.InclusionProofRequest - rsp *trillian.GetInclusionProofByHashResponse - err error - wantErr bool - wantProof *types.InclusionProof - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: bad proof count", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{}, - &trillian.Proof{}, - }, - }, - wantErr: true, - }, - { - description: "invalid: not an inclusion proof (1/2)", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{}, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: not an inclusion proof (2/2)", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize+1), - }, - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetInclusionProofByHashResponse{ - Proof: []*trillian.Proof{ - &trillian.Proof{ - LeafIndex: 1, - Hashes: [][]byte{ - make([]byte, types.HashSize), - make([]byte, types.HashSize), - }, - }, - }, - }, - wantProof: &types.InclusionProof{ - TreeSize: 4, - LeafIndex: 1, - Path: []*[types.HashSize]byte{ - &[types.HashSize]byte{}, - &[types.HashSize]byte{}, - }, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetInclusionProofByHash(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - proof, err := client.GetInclusionProof(context.Background(), table.req) - if got, want := err != nil, table.wantErr; 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 := proof, table.wantProof; !reflect.DeepEqual(got, want) { - t.Errorf("got proof\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} - -func TestGetLeaves(t *testing.T) { - req := &types.LeavesRequest{ - StartSize: 1, - EndSize: 2, - } - firstLeaf := &types.Leaf{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - SigIdent: types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, - KeyHash: &[types.HashSize]byte{}, - }, - } - secondLeaf := &types.Leaf{ - Message: types.Message{ - ShardHint: 0, - Checksum: &[types.HashSize]byte{}, - }, - SigIdent: types.SigIdent{ - Signature: &[types.SignatureSize]byte{}, - KeyHash: &[types.HashSize]byte{}, - }, - } - - for _, table := range []struct { - description string - req *types.LeavesRequest - rsp *trillian.GetLeavesByRangeResponse - err error - wantErr bool - wantLeaves *types.LeafList - }{ - { - description: "invalid: backend failure", - req: req, - err: fmt.Errorf("something went wrong"), - wantErr: true, - }, - { - description: "invalid: no response", - req: req, - wantErr: true, - }, - { - description: "invalid: unexpected number of leaves", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected leaf (1/2)", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal(), - LeafIndex: 3, - }, - }, - }, - wantErr: true, - }, - { - description: "invalid: unexpected leaf (2/2)", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal()[1:], - LeafIndex: 2, - }, - }, - }, - wantErr: true, - }, - { - description: "valid", - req: req, - rsp: &trillian.GetLeavesByRangeResponse{ - Leaves: []*trillian.LogLeaf{ - &trillian.LogLeaf{ - LeafValue: firstLeaf.Marshal(), - LeafIndex: 1, - }, - &trillian.LogLeaf{ - LeafValue: secondLeaf.Marshal(), - LeafIndex: 2, - }, - }, - }, - wantLeaves: &types.LeafList{ - firstLeaf, - secondLeaf, - }, - }, - } { - // Run deferred functions at the end of each iteration - func() { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - grpc := mocks.NewMockTrillianLogClient(ctrl) - grpc.EXPECT().GetLeavesByRange(gomock.Any(), gomock.Any()).Return(table.rsp, table.err) - client := TrillianClient{GRPC: grpc} - - leaves, err := client.GetLeaves(context.Background(), table.req) - if got, want := err != nil, table.wantErr; 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 := leaves, table.wantLeaves; !reflect.DeepEqual(got, want) { - t.Errorf("got leaves\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description) - } - }() - } -} diff --git a/pkg/trillian/util.go b/pkg/trillian/util.go deleted file mode 100644 index fbd0aea..0000000 --- a/pkg/trillian/util.go +++ /dev/null @@ -1,33 +0,0 @@ -package trillian - -import ( - "fmt" - - trillian "github.com/google/trillian/types" - sigsum "git.sigsum.org/sigsum-log-go/pkg/types" -) - -func treeHeadFromLogRoot(lr *trillian.LogRootV1) *sigsum.TreeHead { - var hash [sigsum.HashSize]byte - th := sigsum.TreeHead{ - Timestamp: uint64(lr.TimestampNanos / 1000 / 1000 / 1000), - TreeSize: uint64(lr.TreeSize), - RootHash: &hash, - } - copy(th.RootHash[:], lr.RootHash) - return &th -} - -func nodePathFromHashes(hashes [][]byte) ([]*[sigsum.HashSize]byte, error) { - var path []*[sigsum.HashSize]byte - for _, hash := range hashes { - if len(hash) != sigsum.HashSize { - return nil, fmt.Errorf("unexpected hash length: %v", len(hash)) - } - - var h [sigsum.HashSize]byte - copy(h[:], hash) - path = append(path, &h) - } - return path, nil -} |