aboutsummaryrefslogtreecommitdiff
path: root/internal/db/trillian.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db/trillian.go')
-rw-r--r--internal/db/trillian.go232
1 files changed, 232 insertions, 0 deletions
diff --git a/internal/db/trillian.go b/internal/db/trillian.go
new file mode 100644
index 0000000..e8a9945
--- /dev/null
+++ b/internal/db/trillian.go
@@ -0,0 +1,232 @@
+package db
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "git.sigsum.org/sigsum-go/pkg/log"
+ "git.sigsum.org/sigsum-go/pkg/merkle"
+ "git.sigsum.org/sigsum-go/pkg/requests"
+ "git.sigsum.org/sigsum-go/pkg/types"
+ "github.com/google/trillian"
+ trillianTypes "github.com/google/trillian/types"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+// TrillianClient implements the Client interface for Trillian's gRPC backend
+type TrillianClient struct {
+ // TreeID is a Merkle tree identifier that Trillian uses
+ TreeID int64
+
+ // GRPC is a Trillian gRPC client
+ GRPC trillian.TrillianLogClient
+}
+
+// AddLeaf adds a leaf to the tree and returns true if the leaf has
+// been sequenced into the tree of size treeSize.
+func (c *TrillianClient) AddLeaf(ctx context.Context, req *requests.Leaf, treeSize uint64) (bool, error) {
+ leaf := types.Leaf{
+ Statement: types.Statement{
+ ShardHint: req.ShardHint,
+ Checksum: *merkle.HashFn(req.Message[:]),
+ },
+ Signature: req.Signature,
+ KeyHash: *merkle.HashFn(req.PublicKey[:]),
+ }
+ serialized := leaf.ToBinary()
+
+ log.Debug("queueing leaf request: %x", merkle.HashLeafNode(serialized))
+ _, err := c.GRPC.QueueLeaf(ctx, &trillian.QueueLeafRequest{
+ LogId: c.TreeID,
+ Leaf: &trillian.LogLeaf{
+ LeafValue: serialized,
+ },
+ })
+ switch status.Code(err) {
+ case codes.OK:
+ case codes.AlreadyExists:
+ default:
+ log.Warning("gRPC error: %v", err)
+ return false, fmt.Errorf("back-end failure")
+ }
+ _, err = c.GetInclusionProof(ctx, &requests.InclusionProof{treeSize, *merkle.HashLeafNode(serialized)})
+ return err == nil, nil
+}
+
+// AddSequencedLeaves adds a set of already sequenced leaves to the tree.
+func (c *TrillianClient) AddSequencedLeaves(ctx context.Context, leaves types.Leaves, index int64) error {
+ trilLeaves := make([]*trillian.LogLeaf, len(leaves))
+ for i, leaf := range leaves {
+ trilLeaves[i] = &trillian.LogLeaf{
+ LeafValue: leaf.ToBinary(),
+ LeafIndex: index + int64(i),
+ }
+ }
+
+ req := trillian.AddSequencedLeavesRequest{
+ LogId: c.TreeID,
+ Leaves: trilLeaves,
+ }
+ log.Debug("adding sequenced leaves: count %d", len(trilLeaves))
+ var err error
+ for wait := 1; wait < 30; wait *= 2 {
+ var rsp *trillian.AddSequencedLeavesResponse
+ rsp, err = c.GRPC.AddSequencedLeaves(ctx, &req)
+ switch status.Code(err) {
+ case codes.ResourceExhausted:
+ log.Info("waiting %d seconds before retrying to add %d leaves, reason: %v", wait, len(trilLeaves), err)
+ time.Sleep(time.Second * time.Duration(wait))
+ continue
+ case codes.OK:
+ if rsp == nil {
+ return fmt.Errorf("GRPC.AddSequencedLeaves no response")
+ }
+ // FIXME: check rsp.Results.QueuedLogLeaf
+ return nil
+ default:
+ return fmt.Errorf("GRPC.AddSequencedLeaves error: %v", err)
+ }
+ }
+
+ return fmt.Errorf("giving up on adding %d leaves", len(trilLeaves))
+}
+
+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 trillianTypes.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) != merkle.HashSize {
+ return nil, fmt.Errorf("unexpected hash length: %d", len(r.RootHash))
+ }
+ return treeHeadFromLogRoot(&r), nil
+}
+
+func (c *TrillianClient) GetConsistencyProof(ctx context.Context, req *requests.ConsistencyProof) (*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 *requests.InclusionProof) (*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 *requests.Leaves) (*types.Leaves, 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.Leaves = make([]types.Leaf, 0, len(rsp.Leaves))
+ 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.FromBinary(leaf.LeafValue); err != nil {
+ return nil, fmt.Errorf("unexpected leaf(%d): %v", leafIndex, err)
+ }
+ list = append(list[:], l)
+ }
+ return &list, nil
+}
+
+func treeHeadFromLogRoot(lr *trillianTypes.LogRootV1) *types.TreeHead {
+ th := types.TreeHead{
+ Timestamp: uint64(time.Now().Unix()),
+ TreeSize: uint64(lr.TreeSize),
+ }
+ copy(th.RootHash[:], lr.RootHash)
+ return &th
+}
+
+func nodePathFromHashes(hashes [][]byte) ([]merkle.Hash, error) {
+ path := make([]merkle.Hash, len(hashes))
+ for i := 0; i < len(hashes); i++ {
+ if len(hashes[i]) != merkle.HashSize {
+ return nil, fmt.Errorf("unexpected hash length: %v", len(hashes[i]))
+ }
+
+ copy(path[i][:], hashes[i])
+ }
+ return path, nil
+}