github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/helloworld/personality/personality.go (about)

     1  // Copyright 2021 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package personality runs a simple Trillian personality.
    16  package personality
    17  
    18  import (
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/google/trillian"
    25  	tt "github.com/google/trillian/types"
    26  	"github.com/transparency-dev/formats/log"
    27  	"github.com/transparency-dev/merkle/rfc6962"
    28  	"golang.org/x/mod/sumdb/note"
    29  	"google.golang.org/grpc"
    30  	"google.golang.org/grpc/credentials/insecure"
    31  )
    32  
    33  var (
    34  	connectTimeout = flag.Duration("connect_timeout", 5*time.Second, "the timeout for connecting to the backend")
    35  )
    36  
    37  // SignedCheckpoint is a serialised form of a checkpoint+signatures.
    38  type SignedCheckpoint []byte
    39  
    40  // TrillianP is a personality backed by a trillian log.
    41  type TrillianP struct {
    42  	l      trillian.TrillianLogClient
    43  	treeID int64
    44  	signer note.Signer
    45  }
    46  
    47  // NewPersonality creates a new Trillian personality from the flags.
    48  func NewPersonality(logAddr string, treeID int64, s note.Signer) (*TrillianP, error) {
    49  	if treeID <= 0 {
    50  		return nil, fmt.Errorf("tree_id must be provided and positive, got %d", treeID)
    51  	}
    52  
    53  	ctx, cancel := context.WithTimeout(context.Background(), *connectTimeout)
    54  	defer cancel()
    55  	conn, err := grpc.DialContext(ctx, logAddr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock())
    56  	if err != nil {
    57  		return nil, fmt.Errorf("did not connect to trillian on %v: %v", logAddr, err)
    58  	}
    59  
    60  	log := trillian.NewTrillianLogClient(conn)
    61  
    62  	return &TrillianP{
    63  		l:      log,
    64  		treeID: treeID,
    65  		signer: s,
    66  	}, nil
    67  }
    68  
    69  // formLeaf creates a Trillian log leaf from an entry.
    70  func (p *TrillianP) formLeaf(entry []byte) *trillian.LogLeaf {
    71  	leafHash := rfc6962.DefaultHasher.HashLeaf(entry)
    72  	return &trillian.LogLeaf{
    73  		LeafValue:      entry,
    74  		MerkleLeafHash: leafHash,
    75  	}
    76  }
    77  
    78  // getCheckpoint fetches the latest Trillian root and creates a checkpoint from it.
    79  func (p *TrillianP) getCheckpoint(ctx context.Context) (*log.Checkpoint, error) {
    80  	req := trillian.GetLatestSignedLogRootRequest{LogId: p.treeID}
    81  	resp, err := p.l.GetLatestSignedLogRoot(ctx, &req)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	// Unpack the response and convert it to the local Checkpoint
    86  	// representation.
    87  	root := resp.GetSignedLogRoot()
    88  	var logRoot tt.LogRootV1
    89  	if err := logRoot.UnmarshalBinary(root.LogRoot); err != nil {
    90  		return nil, err
    91  	}
    92  	return &log.Checkpoint{
    93  		Origin: "Hello World Log",
    94  		Hash:   logRoot.RootHash,
    95  		Size:   logRoot.TreeSize,
    96  	}, nil
    97  }
    98  
    99  // GetChkpt gets the latest checkpoint.
   100  func (p *TrillianP) GetChkpt(ctx context.Context) (SignedCheckpoint, error) {
   101  	cp, err := p.getCheckpoint(ctx)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("failed to fetch Trillian checkpoint: %w", err)
   104  	}
   105  	s, err := note.Sign(&note.Note{Text: string(cp.Marshal())}, p.signer)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	return s, nil
   110  }
   111  
   112  // Append adds an entry to the Trillian log and waits to return the new checkpoint.
   113  func (p *TrillianP) Append(ctx context.Context, entry []byte) (SignedCheckpoint, error) {
   114  	// First get the latest checkpoint.
   115  	chkpt, err := p.getCheckpoint(ctx)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	leaf := p.formLeaf(entry)
   120  	req := trillian.QueueLeafRequest{LogId: p.treeID, Leaf: leaf}
   121  	if _, err := p.l.QueueLeaf(ctx, &req); err != nil {
   122  		return nil, err
   123  	}
   124  	// Now fetch the new checkpoint, keep going until it's there and
   125  	// return an error at some point if it isn't.
   126  	for start := time.Now(); time.Since(start) < 5*time.Second; {
   127  		chkptNew, err := p.getCheckpoint(ctx)
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  		// TODO(meiklejohn): should probably verify that the specific entry was
   132  		// incorporated into the tree too.
   133  		if chkpt.Size < chkptNew.Size {
   134  			s, err := note.Sign(&note.Note{Text: string(chkptNew.Marshal())}, p.signer)
   135  			if err != nil {
   136  				return nil, err
   137  			}
   138  			return s, nil
   139  		}
   140  	}
   141  	return nil, fmt.Errorf("did not get an updated checkpoint")
   142  }
   143  
   144  // ProveIncl returns an inclusion proof for a given checkpoint and entry.
   145  func (p *TrillianP) ProveIncl(ctx context.Context, chkptSize uint64, entry []byte) (*trillian.Proof, error) {
   146  	// Form the leaf from the entry.
   147  	leaf := p.formLeaf(entry)
   148  	// Form the request according to the Trillian API.
   149  	req := trillian.GetInclusionProofByHashRequest{
   150  		LogId:    p.treeID,
   151  		LeafHash: leaf.MerkleLeafHash,
   152  		TreeSize: int64(chkptSize),
   153  	}
   154  	// Process the response.
   155  	resp, err := p.l.GetInclusionProofByHash(ctx, &req)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	return resp.GetProof()[0], nil
   160  }
   161  
   162  // UpdateChkpt gets the latest checkpoint for the Trillian log and proves its
   163  // consistency with a provided one.
   164  func (p *TrillianP) UpdateChkpt(ctx context.Context, chkptSize uint64) (SignedCheckpoint, *trillian.Proof, error) {
   165  	// First get the latest checkpoint
   166  	chkptNew, err := p.getCheckpoint(ctx)
   167  	if err != nil {
   168  		return nil, nil, err
   169  	}
   170  	// Now get a consistency proof if one is needed.
   171  	var pf *trillian.Proof
   172  	if chkptNew.Size > chkptSize {
   173  		req := trillian.GetConsistencyProofRequest{
   174  			LogId:          p.treeID,
   175  			FirstTreeSize:  int64(chkptSize),
   176  			SecondTreeSize: int64(chkptNew.Size),
   177  		}
   178  		resp, err := p.l.GetConsistencyProof(ctx, &req)
   179  		if err != nil {
   180  			return nil, nil, err
   181  		}
   182  		pf = resp.GetProof()
   183  	}
   184  	s, err := note.Sign(&note.Note{Text: string(chkptNew.Marshal())}, p.signer)
   185  	if err != nil {
   186  		return nil, nil, err
   187  	}
   188  	return s, pf, nil
   189  }