Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/feedwitness/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"maps"

logfmt "github.com/transparency-dev/formats/log"
"github.com/transparency-dev/witness/omniwitness"
"github.com/transparency-dev/witness/config"
"github.com/transparency-dev/formats/note"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -56,7 +56,7 @@ func newStaticFeederConfig(yamlCfg []byte) (*staticFeederConfig, error) {
if err != nil {
return nil, fmt.Errorf("failed to create signature verifier: %v", err)
}
logCfg := omniwitness.Log{
logCfg := config.Log{
VKey: log.PublicKey,
Verifier: logV,
Origin: log.Origin,
Expand Down
4 changes: 2 additions & 2 deletions cmd/feedwitness/run_feeders.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"github.com/transparency-dev/witness/internal/feeder/sumdb"
"github.com/transparency-dev/witness/internal/feeder/tiles"
"github.com/transparency-dev/witness/witness"
"github.com/transparency-dev/witness/omniwitness"
"github.com/transparency-dev/witness/config"
"go.opentelemetry.io/otel/metric"
"golang.org/x/mod/sumdb/note"
"golang.org/x/sync/errgroup"
Expand All @@ -58,7 +58,7 @@ func init() {
}

type feederConfig struct {
Log omniwitness.Log
Log config.Log
Feeder logFeeder
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/omniwitness/monolith.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

"github.com/prometheus/client_golang/prometheus/promhttp"
f_note "github.com/transparency-dev/formats/note"
"github.com/transparency-dev/witness/config"
"github.com/transparency-dev/witness/omniwitness"
"github.com/transparency-dev/witness/persistence/inmemory"
psql "github.com/transparency-dev/witness/persistence/sqlite"
Expand Down Expand Up @@ -160,7 +161,7 @@ func main() {
}
// Merge embedded configs into persisted configs
{
lc := []omniwitness.Log{}
lc := []config.Log{}
for c, err := range l.Logs(ctx) {
if err != nil {
klog.Exitf("Failed to read embedded log config: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion cmd/omniwitness_gcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"time"

"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/transparency-dev/witness/config"
"github.com/transparency-dev/witness/omniwitness"
"github.com/transparency-dev/witness/persistence/spanner"
"go.opentelemetry.io/otel"
Expand Down Expand Up @@ -131,7 +132,7 @@ func mustUpdateLogs(ctx context.Context, y []byte, p *spanner.Persistence) {
if err != nil {
klog.Exitf("Failed to parse YAML logs config: %v", err)
}
logs := []omniwitness.Log{}
logs := []config.Log{}
for log, err := range l.Logs(ctx) {
if err != nil {
klog.Exitf("Error iterating over logs: %v", err)
Expand Down
34 changes: 34 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2026 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package config holds code related to configuring witnesses.
package config

import "golang.org/x/mod/sumdb/note"

// Log describes a verifiable log.
type Log struct {
// VKey is the serialised note-compliant vkey for the log.
VKey string
// Verifier is a signature verifier for log checkpoints.
Verifier note.Verifier
// Origin is the expected first line of checkpoints from the log.
Origin string
// QPD is the expected number of witness requests per day from the log.
QPD float64
// Contact is an arbitrary string with contact information for the log operator.
Contact string
// URL is the URL of the root of the log.
URL string
}
172 changes: 172 additions & 0 deletions config/public_witness_network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2026 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package config holds code related to configuring witnesses.
package config

import (
"bufio"
"context"
"fmt"
"io"
"iter"
"net/http"
"strconv"
"strings"

"github.com/transparency-dev/formats/note"
)

// PublicFetchOpts holds options to be used when fetching the public witness network config.
type PublicFetchOpts struct {
// Client is the HTTP client to be used, if unset uses http.DefaultClient.
Client *http.Client
// URL is the URL of the config file.
URL string
}

func FetchPublicConfig(ctx context.Context, opts PublicFetchOpts) ([]Log, error) {
if opts.Client == nil {
opts.Client = http.DefaultClient
}

req, err := http.NewRequest(http.MethodGet, opts.URL, nil)
if err != nil {
return nil, fmt.Errorf("http.NewRequest: %w", err)
}
req = req.WithContext(ctx)
resp, err := opts.Client.Do(req)
if err != nil {
return nil, fmt.Errorf("http.Do: %w", err)
}
Comment on lines +49 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A non-2xx status code doesn't cause an error. The handling is missing here as the resp.Body could be empty.

https://pkg.go.dev/net/http#Client.Do

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("got status %q", resp.Status)
}
defer func() {
_, _ = io.ReadAll(resp.Body)
_ = resp.Body.Close()
}()

return ParsePublicWitnessConfig(resp.Body)
}

// ParsePublicWitnessConfig implements a parser for the public witness config format.
//
// The format is described here: https://github.com/transparency-dev/witness-network/blob/main/log-list-format.md
func ParsePublicWitnessConfig(r io.Reader) ([]Log, error) {
ret := []Log{}
foundHeader := false
var candidate *Log
scanner := bufio.NewScanner(r)
for l := range filteringScan(scanner) {
bits := strings.SplitN(l, " ", 2)
keyword := strings.ToLower(strings.TrimSpace(bits[0]))
if !foundHeader && keyword != "logs/v0" {
return nil, fmt.Errorf("invalid config, 'logs/v0' header should be the first non-comment line")
}
switch keyword {
case "logs/v0":
if foundHeader {
return nil, fmt.Errorf("invalid config, multiple 'logs/v0' headers found")
}
foundHeader = true
continue
case "vkey":
// vkey introduces a new log in the config file.
// The argument is a note-compliant vkey string.
//
// Since vkey is always the first keyword in a new log, we can use this as a trigger to "flush" the
// previous log config, if any, and start a new one.
if candidate != nil {
ret = append(ret, *candidate)
}
candidate = &Log{}
if len(bits) != 2 {
return nil, fmt.Errorf("invalid vkey line %q", l)
}
candidate.VKey = strings.TrimSpace(bits[1])
v, err := note.NewVerifier(candidate.VKey)
if err != nil {
return nil, fmt.Errorf("note.NewVerifier: %v", err)
}
candidate.Verifier = v
candidate.Origin = v.Name()
case "origin":
// origin is an optional keyword which can be used to set a log origin which is different to the log's
// vkey name.
if candidate == nil {
return nil, fmt.Errorf("invalid config")
}
if len(bits) != 2 {
return nil, fmt.Errorf("invalid origin line %q", l)
}
candidate.Origin = strings.TrimSpace(bits[1])
case "qpd":
// qpd is the number of queries the log is permitted to send to the witness per day.
// The value is interpreted as a float64.
if candidate == nil {
return nil, fmt.Errorf("invalid config")
}
if len(bits) != 2 {
return nil, fmt.Errorf("invalid qpd line %q", l)
}
v := strings.TrimSpace(bits[1])
qpd, err := strconv.ParseFloat(v, 64)
if err != nil {
return nil, fmt.Errorf("invalid qpd value %q: %v", v, err)
}
candidate.QPD = qpd
case "contact":
// contact is a free-form text field which contains some sort of contact information for the log operator.
if candidate == nil {
return nil, fmt.Errorf("invalid config")
}
if len(bits) != 2 {
return nil, fmt.Errorf("invalid contact line %q", l)
}
candidate.Contact = strings.TrimSpace(bits[1])
default:
return nil, fmt.Errorf("unexpected keyword %q", keyword)
}
}
// Include the final entry, if any.
if candidate != nil {
ret = append(ret, *candidate)
candidate = nil
}
if !foundHeader {
return nil, fmt.Errorf("invalid config, no 'logs/v0' header found")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

foundHeader is true even the logs/v0 is not placed at the top line.

https://github.com/transparency-dev/witness-network/blob/main/log-list-format.md

if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading config: %v", err)
}
return ret, nil
}

func filteringScan(s *bufio.Scanner) iter.Seq[string] {
return func(yield func(string) bool) {
for s.Scan() {
l := strings.TrimSpace(s.Text())
if l == "" {
continue
}
if l[0] == '#' {
continue
}
if !yield(l) {
return
}
}
}
}
Loading
Loading