Skip to content

Commit

Permalink
Witness properly handles ratcheting forward from tree size 0 (#266)
Browse files Browse the repository at this point in the history
* Added tests for witnesses updating from tree size 0

The tests show that this currently works, providing that the proof is empty. When the merkle library is updated to pull in transparency-dev/merkle#140, this will fail too. In both of the new test cases, the witness should ratchet forward when a new checkpoint is provided.

As a separate note, I recommend rewriting these tests at some near point in the future. They are currently quite brittle as they rely on hard coded test data with no obvious script to update them. An in-memory test log would be a great way to generate the test data in a flexible way.

* Support ratcheting forward from checkpoints for size 0

The only way to handle this is via a special case.
  • Loading branch information
mhutchinson authored Sep 19, 2024
1 parent da70907 commit 472156f
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 45 deletions.
14 changes: 14 additions & 0 deletions internal/witness/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ func (w *Witness) Update(ctx context.Context, logID string, nextRaw []byte, cPro
counterUpdateSuccess.Inc(logID)
return prevRaw, nil
}
if prev.Size == 0 {
// Checkpoints of size 0 are really placeholders and consistency proofs can't be performed.
// If we initialized on a tree size of 0, then we simply ratchet forward and effectively TOFU the new checkpoint.
signed, err := w.signChkpt(nextNote)
if err != nil {
return nil, status.Errorf(codes.Internal, "couldn't sign input checkpoint: %v", err)
}
if err := setInitChkptData(write, logInfo, next, signed, cProof); err != nil {
return nil, status.Errorf(codes.Internal, "couldn't set first non-zero checkpoint: %v", err)
}
counterUpdateSuccess.Inc(logID)
return signed, nil
}

// The only remaining option is next.Size > prev.Size. This might be
// valid so we use either plain consistency proofs or compact ranges to
// verify, depending on the log.
Expand Down
119 changes: 74 additions & 45 deletions internal/witness/witness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

_ "github.com/mattn/go-sqlite3" // Load drivers for sqlite3
"github.com/transparency-dev/formats/log"
f_note "github.com/transparency-dev/formats/note"
"github.com/transparency-dev/merkle/rfc6962"
"github.com/transparency-dev/witness/internal/persistence/inmemory"
Expand Down Expand Up @@ -259,47 +260,77 @@ func TestGetChkpt(t *testing.T) {
}
}

func mustCreateCheckpoint(t *testing.T, sk string, size uint64, rootHash []byte) []byte {
t.Helper()
cp := log.Checkpoint{
Origin: logOrigin,
Size: size,
Hash: rootHash,
}
signer, err := note.NewSigner(sk)
if err != nil {
t.Fatal(err)
}

msg, err := note.Sign(&note.Note{Text: string(cp.Marshal())}, signer)
if err != nil {
t.Fatal(err)
}
return msg
}

func TestUpdate(t *testing.T) {
for _, test := range []struct {
desc string
initC []byte
initSize uint64
newC []byte
pf [][]byte
useCR bool
initCR [][]byte
isGood bool
desc string
initC []byte
newC []byte
pf [][]byte
useCR bool
initCR [][]byte
isGood bool
}{
{
desc: "vanilla consistency happy path",
initC: mInit,
initSize: 5,
newC: mNext,
pf: consProof,
useCR: false,
isGood: true,
desc: "vanilla consistency happy path",
initC: mustCreateCheckpoint(t, mSK, 5, dh("e35b268c1522014ef412d2a54fa94838862d453631617b0307e5c77dcbeefc11", 32)),
newC: mNext,
pf: consProof,
useCR: false,
isGood: true,
},
{
desc: "vanilla path, but the first line changed",
initC: mInit,
initSize: 5,
newC: []byte("Frog Checkpoint v0\n8\nV8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=\n\n— monkeys 202ffgCVdfZmrroccRdQoEfn2TfmXHez4R++GvVrFvFiaI85O12aTV5GpNOvWsuQW77eNxQ2b7ggYeglzF/QSy/EBws=\n"),
pf: consProof,
useCR: false,
isGood: false,
desc: "vanilla consistency starting from tree size 0 with proof",
initC: mustCreateCheckpoint(t, mSK, 0, rfc6962.DefaultHasher.EmptyRoot()),
newC: mustCreateCheckpoint(t, mSK, 5, dh("e35b268c1522014ef412d2a54fa94838862d453631617b0307e5c77dcbeefc11", 32)),
pf: consProof,
useCR: false,
isGood: true,
},
{
desc: "vanilla consistency starting from tree size 0 without proof",
initC: mustCreateCheckpoint(t, mSK, 0, rfc6962.DefaultHasher.EmptyRoot()),
newC: mustCreateCheckpoint(t, mSK, 5, dh("e35b268c1522014ef412d2a54fa94838862d453631617b0307e5c77dcbeefc11", 32)),
pf: [][]byte{},
useCR: false,
isGood: true,
},
{
desc: "vanilla path, but the first line changed",
initC: mInit,
newC: []byte("Frog Checkpoint v0\n8\nV8K9aklZ4EPB+RMOk1/8VsJUdFZR77GDtZUQq84vSbo=\n\n— monkeys 202ffgCVdfZmrroccRdQoEfn2TfmXHez4R++GvVrFvFiaI85O12aTV5GpNOvWsuQW77eNxQ2b7ggYeglzF/QSy/EBws=\n"),
pf: consProof,
useCR: false,
isGood: false,
}, {
desc: "vanilla consistency smaller checkpoint",
initC: mNext,
initSize: 8,
newC: mInit,
pf: consProof,
useCR: false,
isGood: false,
desc: "vanilla consistency smaller checkpoint",
initC: mNext,
newC: mInit,
pf: consProof,
useCR: false,
isGood: false,
}, {
desc: "vanilla consistency garbage proof",
initC: mInit,
initSize: 5,
newC: mNext,
desc: "vanilla consistency garbage proof",
initC: mInit,
newC: mNext,
pf: [][]byte{
dh("aaaa", 2),
dh("bbbb", 2),
Expand All @@ -308,19 +339,17 @@ func TestUpdate(t *testing.T) {
},
isGood: false,
}, {
desc: "compact range happy path",
initC: crInit,
initSize: 10,
newC: crNext,
pf: crProof,
useCR: true,
initCR: crInitRange,
isGood: true,
desc: "compact range happy path",
initC: crInit,
newC: crNext,
pf: crProof,
useCR: true,
initCR: crInitRange,
isGood: true,
}, {
desc: "compact range garbage proof",
initC: crInit,
initSize: 10,
newC: crNext,
desc: "compact range garbage proof",
initC: crInit,
newC: crNext,
pf: [][]byte{
dh("aaaa", 2),
dh("bbbb", 2),
Expand Down

0 comments on commit 472156f

Please sign in to comment.