diff --git a/trees/mirror/mirror.go b/trees/mirror/mirror.go index f70900ef0fc..1be9f7df16f 100644 --- a/trees/mirror/mirror.go +++ b/trees/mirror/mirror.go @@ -55,6 +55,33 @@ func AddCheckpointRequest(oldSize int64, proof []tlog.Hash, signedCheckpoint []b return b.Bytes(), nil } +// SignSubtreeRequest builds the sign-subtree request body per +// c2sp.org/tlog-witness. The proof must be a Subtree Consistency Proof from the +// subtree to the checkpoint, empty when the subtree is the whole tree. +func SignSubtreeRequest(start, end int64, subtreeHash tlog.Hash, proof []tlog.Hash, signedCheckpoint []byte) ([]byte, error) { + if start < 0 { + return nil, fmt.Errorf("negative subtree start %d", start) + } + if end <= start { + return nil, fmt.Errorf("subtree end %d not after start %d", end, start) + } + if len(proof) > maxProofLines { + return nil, fmt.Errorf("consistency proof has %d lines, want at most %d", len(proof), maxProofLines) + } + if len(signedCheckpoint) == 0 { + return nil, errors.New("empty checkpoint") + } + var b bytes.Buffer + fmt.Fprintf(&b, "subtree %d %d\n%s\n", start, end, subtreeHash) + for _, h := range proof { + b.WriteString(h.String()) + b.WriteByte('\n') + } + b.WriteByte('\n') + b.Write(signedCheckpoint) + return b.Bytes(), nil +} + // parseDecimal parses an ASCII decimal string, accepting leading zeroes since // the specs require canonical decimals only in request bodies. func parseDecimal(s string) (int64, error) { diff --git a/trees/mirror/mirror_test.go b/trees/mirror/mirror_test.go index 4130d85ae32..bfb4c31b857 100644 --- a/trees/mirror/mirror_test.go +++ b/trees/mirror/mirror_test.go @@ -196,6 +196,42 @@ func TestEntryPackage(t *testing.T) { } } +func TestSignSubtreeRequest(t *testing.T) { + hash := mustHash(t, "CsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2I=") + proof := []tlog.Hash{mustHash(t, "PlRNCrwHpqhGrupue0L7gxbjbMiKA9temvuZZDDpkaw=")} + note := []byte("example.com/log\n512\n" + hash.String() + "\n\n— example.com/log AAAA\n") + + body, err := SignSubtreeRequest(256, 512, hash, proof, note) + if err != nil { + t.Fatalf("SignSubtreeRequest: %s", err) + } + expect := "subtree 256 512\n" + hash.String() + "\n" + proof[0].String() + "\n\n" + string(note) + if string(body) != expect { + t.Errorf("SignSubtreeRequest = %q, want %q", body, expect) + } + + _, err = SignSubtreeRequest(-1, 512, hash, nil, note) + if err == nil { + t.Error("SignSubtreeRequest with a negative start = nil error, want error") + } + _, err = SignSubtreeRequest(512, 256, hash, nil, note) + if err == nil { + t.Error("SignSubtreeRequest with end before start = nil error, want error") + } + _, err = SignSubtreeRequest(512, 512, hash, nil, note) + if err == nil { + t.Error("SignSubtreeRequest with an empty subtree = nil error, want error") + } + _, err = SignSubtreeRequest(0, 512, hash, make([]tlog.Hash, 64), note) + if err == nil { + t.Error("SignSubtreeRequest with 64 proof lines = nil error, want error") + } + _, err = SignSubtreeRequest(0, 512, hash, nil, nil) + if err == nil { + t.Error("SignSubtreeRequest with an empty checkpoint = nil error, want error") + } +} + func TestAddEntriesRequest(t *testing.T) { origin := "oid/1.3.6.1.4.1.44947.4.1.0.44" pkg := []byte{0, 1, 'x', 0}