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

     1  // Copyright 2021 Google LLC. All Rights Reserved.
     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  //     http://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 helloworld
    16  
    17  import (
    18  	"context"
    19  	"crypto/rand"
    20  	"flag"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	p "github.com/google/trillian-examples/helloworld/personality"
    26  	"github.com/transparency-dev/formats/log"
    27  	"golang.org/x/mod/sumdb/note"
    28  )
    29  
    30  const (
    31  	// testPrivateKey is the personality's key for signing its checkpoints.
    32  	testPrivateKey = "PRIVATE+KEY+helloworld+b51acf1b+ASW28PXJDCV8klh7JeacIgfJR3/Q60dklasmgnv4c9I7"
    33  	// testPublicKey is used for verifying the signatures on the checkpoints from
    34  	// the personality.
    35  	testPublicKey = "helloworld+b51acf1b+AZ2ZM0ZQ69GwDUyO7/x0JyLo09y3geyufyN1mFFMeUH3"
    36  )
    37  
    38  var (
    39  	seed         = flag.String("seed", time.Now().Format(time.UnixDate), "Seed for leaf randomness")
    40  	trillianAddr = flag.String("trillian", "localhost:50054", "Host:port of Trillian Log RPC server")
    41  	treeID       = flag.Int64("tree_id", 0, "Tree ID")
    42  )
    43  
    44  func mustGetSigner(t *testing.T) note.Signer {
    45  	t.Helper()
    46  	s, err := note.NewSigner(testPrivateKey)
    47  	if err != nil {
    48  		t.Fatalf("Failed to create signer: %q", err)
    49  	}
    50  	return s
    51  }
    52  
    53  func mustGetVerifier(t *testing.T) note.Verifier {
    54  	t.Helper()
    55  	v, err := note.NewVerifier(testPublicKey)
    56  	if err != nil {
    57  		t.Fatalf("Failed to create verifier: %q", err)
    58  	}
    59  	return v
    60  }
    61  
    62  func mustOpenCheckpoint(t *testing.T, cRaw []byte) *log.Checkpoint {
    63  	t.Helper()
    64  	cp, _, _, err := log.ParseCheckpoint(cRaw, "Hello World Log", mustGetVerifier(t))
    65  	if err != nil {
    66  		t.Fatalf("Failed to open checkpoint: %q", err)
    67  	}
    68  	return cp
    69  }
    70  
    71  // TestAppend appends a random entry to the log and ensures that the
    72  // checkpoint updates properly (locally on the personality's side).
    73  func TestAppend(t *testing.T) {
    74  	if *treeID == 0 {
    75  		t.Skip("--tree_id flag unset, skipping test")
    76  	}
    77  
    78  	name := "testAppend"
    79  	t.Run(name, func(t *testing.T) {
    80  		ctx := context.Background()
    81  		personality, err := p.NewPersonality(*trillianAddr, *treeID, mustGetSigner(t))
    82  		if err != nil {
    83  			t.Fatalf(err.Error())
    84  		}
    85  		chkptOldRaw, err := personality.GetChkpt(ctx)
    86  		if err != nil {
    87  			t.Fatalf(err.Error())
    88  		}
    89  		chkptOld := mustOpenCheckpoint(t, chkptOldRaw)
    90  		// Add a random entry so we can be sure it's new.
    91  		entry := make([]byte, 10)
    92  		if _, err := rand.Read(entry); err != nil {
    93  			t.Error(err)
    94  		}
    95  		chkptNewRaw, err := personality.Append(ctx, entry)
    96  		if err != nil {
    97  			t.Fatalf(err.Error())
    98  		}
    99  		chkptNew := mustOpenCheckpoint(t, chkptNewRaw)
   100  		if chkptNew.Size <= chkptOld.Size {
   101  			t.Errorf("the log didn't grow properly in %v", name)
   102  		}
   103  		fmt.Printf("success in %v, new log size is %v\n", name, chkptNew.Size)
   104  	})
   105  }
   106  
   107  // TestUpdate appends a random entry to the log and ensures that the
   108  // checkpoint updates properly for both the personality and the verifier.
   109  func TestUpdate(t *testing.T) {
   110  	if *treeID == 0 {
   111  		t.Skip("--tree_id flag unset, skipping test")
   112  	}
   113  
   114  	name := "testUpdate"
   115  	t.Run(name, func(t *testing.T) {
   116  		ctx := context.Background()
   117  		personality, err := p.NewPersonality(*trillianAddr, *treeID, mustGetSigner(t))
   118  		if err != nil {
   119  			t.Fatalf(err.Error())
   120  		}
   121  		client := NewClient(personality, mustGetVerifier(t))
   122  		chkptRaw, err := personality.GetChkpt(ctx)
   123  		if err != nil {
   124  			t.Fatalf(err.Error())
   125  		}
   126  		client.chkpt = mustOpenCheckpoint(t, chkptRaw)
   127  		entry := make([]byte, 10)
   128  		if _, err := rand.Read(entry); err != nil {
   129  			t.Error(err)
   130  		}
   131  		if _, err := personality.Append(ctx, entry); err != nil {
   132  			t.Error(err)
   133  		}
   134  		chkptNewRaw, pf, err := personality.UpdateChkpt(ctx, client.chkpt.Size)
   135  		if err != nil {
   136  			t.Fatalf(err.Error())
   137  		}
   138  		got := client.UpdateChkpt(chkptNewRaw, pf)
   139  		if got != nil {
   140  			t.Errorf("verifier failed to update checkpoint: %q", err)
   141  		}
   142  		chkptNew := mustOpenCheckpoint(t, chkptNewRaw)
   143  		fmt.Printf("success in %v, new log size is %v\n", name, chkptNew.Size)
   144  	})
   145  }
   146  
   147  // TestIncl tests inclusion proof checking for entries that both are and
   148  // aren't in the log.
   149  func TestIncl(t *testing.T) {
   150  	if *treeID == 0 {
   151  		t.Skip("--tree_id flag unset, skipping test")
   152  	}
   153  
   154  	tests := []struct {
   155  		name         string
   156  		addEntries   []string
   157  		checkEntries []string
   158  		wants        []bool
   159  	}{
   160  		{
   161  			name:         "all there",
   162  			addEntries:   []string{"a", "b", "c", "d"},
   163  			checkEntries: []string{"a", "b", "c", "d"},
   164  			wants:        []bool{true, true, true, true},
   165  		},
   166  		{
   167  			name:         "all missing",
   168  			addEntries:   []string{"e", "f", "g", "h"},
   169  			checkEntries: []string{"w", "x", "y", "z"},
   170  			wants:        []bool{false, false, false, false},
   171  		},
   172  		{
   173  			name:         "mixed bag",
   174  			addEntries:   []string{"i", "j", "k", "l"},
   175  			checkEntries: []string{"i", "j", "y", "z"},
   176  			wants:        []bool{true, true, false, false},
   177  		},
   178  	}
   179  
   180  	for _, test := range tests {
   181  		test := test
   182  		t.Run(test.name, func(t *testing.T) {
   183  			ctx := context.Background()
   184  			personality, err := p.NewPersonality(*trillianAddr, *treeID, mustGetSigner(t))
   185  			if err != nil {
   186  				t.Fatalf(err.Error())
   187  			}
   188  			var chkptRaw []byte
   189  			// Append all the entries we plan to add, folding in
   190  			// the seed to avoid duplication of entries across tests.
   191  			for _, entry := range test.addEntries {
   192  				entry = entry + *seed
   193  				bs := []byte(entry)
   194  				chkptRaw, err = personality.Append(ctx, bs)
   195  				if err != nil {
   196  					// If the checkpoint didn't update that's a problem.
   197  					t.Fatalf(err.Error())
   198  				}
   199  			}
   200  			client := NewClient(personality, mustGetVerifier(t))
   201  			// For the purposes of the test let's skip having the
   202  			// verifier update the right way and just assign their checkpoint.
   203  			client.chkpt = mustOpenCheckpoint(t, chkptRaw)
   204  			// Then prove and check inclusion of the other entries.
   205  			for i := range test.checkEntries {
   206  				entry := test.checkEntries[i] + *seed
   207  				bs := []byte(entry)
   208  				// Ignore error here since it's okay if we
   209  				// don't have a valid inclusion proof (testing
   210  				// on entries that aren't there).
   211  				pf, err := personality.ProveIncl(ctx, client.chkpt.Size, bs)
   212  				got := false
   213  				if err == nil {
   214  					got = client.VerIncl(bs, pf)
   215  				}
   216  				if got != test.wants[i] {
   217  					t.Errorf("%v: got %v, want %v", test.name, got, test.wants[i])
   218  				}
   219  			}
   220  			fmt.Printf("testIncl: all good for the %v test!\n", test.name)
   221  		})
   222  	}
   223  }