From e4bc604a2ae876a45433c5a0083e7b297707c5dc Mon Sep 17 00:00:00 2001 From: Samantha Date: Tue, 30 Jun 2026 16:37:10 -0400 Subject: [PATCH 1/2] trees/checkpoint: Add checkpoint.Checkpoint --- go.mod | 4 +- go.sum | 8 +- trees/checkpoint/checkpoint.go | 140 ++++ trees/checkpoint/checkpoint_test.go | 223 ++++++ vendor/golang.org/x/mod/sumdb/note/note.go | 667 ++++++++++++++++++ vendor/golang.org/x/mod/sumdb/tlog/note.go | 138 ++++ vendor/golang.org/x/mod/sumdb/tlog/tile.go | 433 ++++++++++++ vendor/golang.org/x/mod/sumdb/tlog/tlog.go | 605 ++++++++++++++++ vendor/golang.org/x/tools/go/ast/edge/edge.go | 24 +- .../golang.org/x/tools/go/packages/golist.go | 17 +- .../x/tools/go/packages/packages.go | 16 +- .../x/tools/go/types/objectpath/objectpath.go | 563 +++++++++------ .../x/tools/internal/gcimporter/ureader.go | 42 +- .../x/tools/internal/gocommand/version.go | 5 +- vendor/modules.txt | 6 +- 15 files changed, 2640 insertions(+), 251 deletions(-) create mode 100644 trees/checkpoint/checkpoint.go create mode 100644 trees/checkpoint/checkpoint_test.go create mode 100644 vendor/golang.org/x/mod/sumdb/note/note.go create mode 100644 vendor/golang.org/x/mod/sumdb/tlog/note.go create mode 100644 vendor/golang.org/x/mod/sumdb/tlog/tile.go create mode 100644 vendor/golang.org/x/mod/sumdb/tlog/tlog.go diff --git a/go.mod b/go.mod index 232a52ee5f6..8c9c4abd69e 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.52.0 + golang.org/x/mod v0.37.0 golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 golang.org/x/text v0.37.0 @@ -84,9 +85,8 @@ require ( go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/mod v0.35.0 // indirect golang.org/x/sys v0.45.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/tools v0.45.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect diff --git a/go.sum b/go.sum index 17410dc5145..4de581c3356 100644 --- a/go.sum +++ b/go.sum @@ -284,8 +284,8 @@ golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGb golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -326,8 +326,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/trees/checkpoint/checkpoint.go b/trees/checkpoint/checkpoint.go new file mode 100644 index 00000000000..aae4f5e2abf --- /dev/null +++ b/trees/checkpoint/checkpoint.go @@ -0,0 +1,140 @@ +package checkpoint + +import ( + "encoding/base64" + "errors" + "fmt" + "strconv" + "strings" + "unicode/utf8" + + "golang.org/x/mod/sumdb/note" + "golang.org/x/mod/sumdb/tlog" +) + +// Checkpoint represents a tlog-checkpoint note body. +// +// https://c2sp.org/tlog-checkpoint +type Checkpoint struct { + Origin string + Tree tlog.Tree + Extensions []string +} + +// String returns the note body of the checkpoint. Unlike Marshal, it does not +// validate the checkpoint fields. Call Marshal to validate the fields before +// serializing. +// +// https://c2sp.org/tlog-checkpoint +func (c Checkpoint) String() string { + var b strings.Builder + fmt.Fprintf(&b, "%s\n%d\n%s\n", c.Origin, c.Tree.N, c.Tree.Hash) + for _, ext := range c.Extensions { + b.WriteString(ext) + b.WriteByte('\n') + } + return b.String() +} + +// validNoteLine reports whether s is a valid signed-note line: UTF-8 with no +// control character below U+0020. +func validNoteLine(s string) bool { + if !utf8.ValidString(s) { + return false + } + for _, r := range s { + if r < 0x20 { + return false + } + } + return true +} + +// Marshal returns the note body, first checking the checkpoint against the +// tlog-checkpoint rules. It returns an error if the checkpoint is invalid. +// +// - https://c2sp.org/tlog-checkpoint +// - https://c2sp.org/signed-note +func (c Checkpoint) Marshal() (string, error) { + if c.Origin == "" { + return "", errors.New("empty checkpoint origin") + } + if !validNoteLine(c.Origin) { + return "", errors.New("checkpoint origin contains a control character or invalid UTF-8") + } + if c.Tree.N < 0 { + return "", fmt.Errorf("negative checkpoint tree size %d", c.Tree.N) + } + for _, ext := range c.Extensions { + if ext == "" { + return "", errors.New("empty checkpoint extension line") + } + if !validNoteLine(ext) { + return "", errors.New("checkpoint extension line contains a control character or invalid UTF-8") + } + } + return c.String(), nil +} + +// Parse parses a checkpoint note body. It must not have any signature lines. +// For a signed note, use Open. +// +// - https://c2sp.org/tlog-checkpoint +// - https://c2sp.org/signed-note +func Parse(text string) (Checkpoint, error) { + if !strings.HasSuffix(text, "\n") { + return Checkpoint{}, errors.New("checkpoint does not end in newline") + } + lines := strings.Split(strings.TrimSuffix(text, "\n"), "\n") + if len(lines) < 3 { + return Checkpoint{}, errors.New("checkpoint has too few lines") + } + + origin := lines[0] + if origin == "" { + return Checkpoint{}, errors.New("empty checkpoint origin") + } + if !validNoteLine(origin) { + return Checkpoint{}, errors.New("checkpoint origin contains a control character or invalid UTF-8") + } + + size, err := strconv.ParseInt(lines[1], 10, 64) + if err != nil || size < 0 || strconv.FormatInt(size, 10) != lines[1] { + return Checkpoint{}, errors.New("malformed checkpoint tree size") + } + + hashBytes, err := base64.StdEncoding.DecodeString(lines[2]) + if err != nil || len(hashBytes) != tlog.HashSize || base64.StdEncoding.EncodeToString(hashBytes) != lines[2] { + return Checkpoint{}, errors.New("malformed checkpoint root hash") + } + var hash tlog.Hash + copy(hash[:], hashBytes) + + extensions := lines[3:] + for _, ext := range extensions { + if ext == "" { + return Checkpoint{}, errors.New("empty checkpoint extension line") + } + if !validNoteLine(ext) { + return Checkpoint{}, errors.New("checkpoint extension line contains a control character or invalid UTF-8") + } + } + return Checkpoint{Origin: origin, Tree: tlog.Tree{N: size, Hash: hash}, Extensions: extensions}, nil +} + +// Open opens a signed checkpoint note and parses its body. An error is returned +// if the note is not valid or the signature is not verified by one of the +// verifiers. +// +// https://c2sp.org/signed-note +func Open(signedNote []byte, verifiers note.Verifiers) (Checkpoint, *note.Note, error) { + n, err := note.Open(signedNote, verifiers) + if err != nil { + return Checkpoint{}, nil, err + } + c, err := Parse(n.Text) + if err != nil { + return Checkpoint{}, nil, err + } + return c, n, nil +} diff --git a/trees/checkpoint/checkpoint_test.go b/trees/checkpoint/checkpoint_test.go new file mode 100644 index 00000000000..176b9207488 --- /dev/null +++ b/trees/checkpoint/checkpoint_test.go @@ -0,0 +1,223 @@ +package checkpoint + +import ( + "crypto/rand" + "slices" + "testing" + + "golang.org/x/mod/sumdb/note" +) + +const exampleHashB64 = "CsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2I=" + +// exampleCheckpoint is a tlog-checkpoint note body, including the trailing +// newline and no signature lines. +const exampleCheckpoint = "example.com/behind-the-sofa\n20852163\n" + exampleHashB64 + "\n" + +func TestParseCheckpointRoundTrip(t *testing.T) { + cases := []struct { + name string + text string + origin string + size int64 + extensions []string + }{ + { + name: "No extensions", + text: "example.com/log\n20852163\n" + exampleHashB64 + "\n", + origin: "example.com/log", + size: 20852163, + }, + { + name: "With extensions", + text: "example.com/log\n20852163\n" + exampleHashB64 + "\nfoo extension\nbar extension\n", + origin: "example.com/log", + size: 20852163, + extensions: []string{"foo extension", "bar extension"}, + }, + { + name: "Zero size", + text: "example.com/log\n0\n" + exampleHashB64 + "\n", + origin: "example.com/log", + size: 0, + }, + { + // tlog-checkpoint: clients MUST NOT assume the origin follows the + // schema-less-URL recommendation, so spaces and plus signs (banned + // only in signature-line key names) must be accepted here. + name: "Origin not following the URL recommendation", + text: "a space and + plus/log\n20852163\n" + exampleHashB64 + "\n", + origin: "a space and + plus/log", + size: 20852163, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + c, err := Parse(tc.text) + if err != nil { + t.Fatalf("Parse: %s", err) + } + if c.Origin != tc.origin { + t.Errorf("Origin = %q, want %q", c.Origin, tc.origin) + } + if c.Tree.N != tc.size { + t.Errorf("Tree.N = %d, want %d", c.Tree.N, tc.size) + } + if !slices.Equal(c.Extensions, tc.extensions) { + t.Errorf("Extensions = %v, want %v", c.Extensions, tc.extensions) + } + got := c.String() + if got != tc.text { + t.Errorf("String() = %q, want %q", got, tc.text) + } + }) + } +} + +func TestParseCheckpointRejects(t *testing.T) { + cases := []struct { + name string + text string + }{ + {"No trailing newline", "example.com/log\n1\n" + exampleHashB64}, + {"Too few lines", "example.com/log\n1\n"}, + {"Empty origin", "\n1\n" + exampleHashB64 + "\n"}, + {"Leading zero size", "example.com/log\n01\n" + exampleHashB64 + "\n"}, + {"Negative size", "example.com/log\n-1\n" + exampleHashB64 + "\n"}, + {"Non-numeric size", "example.com/log\nx\n" + exampleHashB64 + "\n"}, + {"Bad base64 hash", "example.com/log\n1\n!!!notbase64!!!\n"}, + {"Non-canonical base64 hash", "example.com/log\n1\nCsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2J=\n"}, + {"Short hash", "example.com/log\n1\nAAAA\n"}, + {"Empty extension line", "example.com/log\n1\n" + exampleHashB64 + "\n\n"}, + // signed-note bans ASCII control characters other than newline. Parse + // must enforce it itself because it gates Sign on raw bodies that never + // pass through note.Open. + {"Carriage return in origin", "example.com/log\r\n1\n" + exampleHashB64 + "\n"}, + {"Invalid UTF-8 in origin", "example.com/\xff\n1\n" + exampleHashB64 + "\n"}, + {"Control character in extension", "example.com/log\n1\n" + exampleHashB64 + "\next\x01ension\n"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := Parse(tc.text) + if err == nil { + t.Error("Parse = nil error, want error") + } + }) + } +} + +// TestCheckpointMarshal covers the validating serialization path for +// hand-constructed Checkpoints, which String deliberately does not validate. +func TestCheckpointMarshal(t *testing.T) { + valid, err := Parse(exampleCheckpoint) + if err != nil { + t.Fatalf("Parse: %s", err) + } + + t.Run("Valid", func(t *testing.T) { + got, err := valid.Marshal() + if err != nil { + t.Fatalf("Marshal: %s", err) + } + if got != valid.String() { + t.Errorf("Marshal = %q, want %q", got, valid.String()) + } + }) + + cases := []struct { + name string + mutate func(Checkpoint) Checkpoint + }{ + {"Empty origin", func(c Checkpoint) Checkpoint { c.Origin = ""; return c }}, + {"Newline in origin", func(c Checkpoint) Checkpoint { c.Origin = "two\nlines"; return c }}, + {"Invalid UTF-8 in origin", func(c Checkpoint) Checkpoint { c.Origin = "bad\xff"; return c }}, + {"Negative tree size", func(c Checkpoint) Checkpoint { c.Tree.N = -1; return c }}, + {"Empty extension", func(c Checkpoint) Checkpoint { c.Extensions = []string{""}; return c }}, + {"Newline in extension", func(c Checkpoint) Checkpoint { c.Extensions = []string{"two\nlines"}; return c }}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := tc.mutate(valid).Marshal() + if err == nil { + t.Error("Marshal = nil error, want error") + } + }) + } +} + +// TestOpenCheckpoint covers Open's happy path and its rejection of a note no +// trusted key signed, using a generic note signer. +func TestOpenCheckpoint(t *testing.T) { + skey, vkey, err := note.GenerateKey(rand.Reader, "example.com/behind-the-sofa") + if err != nil { + t.Fatalf("GenerateKey: %s", err) + } + signer, err := note.NewSigner(skey) + if err != nil { + t.Fatalf("NewSigner: %s", err) + } + signed, err := note.Sign(¬e.Note{Text: exampleCheckpoint}, signer) + if err != nil { + t.Fatalf("note.Sign: %s", err) + } + verifier, err := note.NewVerifier(vkey) + if err != nil { + t.Fatalf("NewVerifier: %s", err) + } + + t.Run("Valid", func(t *testing.T) { + cp, n, err := Open(signed, note.VerifierList(verifier)) + if err != nil { + t.Fatalf("Open: %s", err) + } + if cp.Origin != "example.com/behind-the-sofa" { + t.Errorf("Origin = %q, want %q", cp.Origin, "example.com/behind-the-sofa") + } + if cp.Tree.N != 20852163 { + t.Errorf("Tree.N = %d, want %d", cp.Tree.N, 20852163) + } + if len(n.Sigs) != 1 { + t.Errorf("len(Sigs) = %d, want 1", len(n.Sigs)) + } + }) + + t.Run("Wrong key", func(t *testing.T) { + _, otherVkey, err := note.GenerateKey(rand.Reader, "example.com/behind-the-sofa") + if err != nil { + t.Fatalf("GenerateKey: %s", err) + } + otherV, err := note.NewVerifier(otherVkey) + if err != nil { + t.Fatalf("NewVerifier: %s", err) + } + _, _, err = Open(signed, note.VerifierList(otherV)) + if err == nil { + t.Error("Open with wrong key = nil error, want error") + } + }) +} + +// TestOpenRejectsNonCheckpointBody covers the branch where the note signature +// verifies but its body does not parse as a checkpoint. +func TestOpenRejectsNonCheckpointBody(t *testing.T) { + skey, vkey, err := note.GenerateKey(rand.Reader, "log.example") + if err != nil { + t.Fatalf("GenerateKey: %s", err) + } + signer, err := note.NewSigner(skey) + if err != nil { + t.Fatalf("NewSigner: %s", err) + } + signed, err := note.Sign(¬e.Note{Text: "not a checkpoint\n"}, signer) + if err != nil { + t.Fatalf("note.Sign: %s", err) + } + verifier, err := note.NewVerifier(vkey) + if err != nil { + t.Fatalf("NewVerifier: %s", err) + } + _, _, err = Open(signed, note.VerifierList(verifier)) + if err == nil { + t.Error("Open of a verified non-checkpoint note = nil error, want error") + } +} diff --git a/vendor/golang.org/x/mod/sumdb/note/note.go b/vendor/golang.org/x/mod/sumdb/note/note.go new file mode 100644 index 00000000000..c95777f5e85 --- /dev/null +++ b/vendor/golang.org/x/mod/sumdb/note/note.go @@ -0,0 +1,667 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package note defines the notes signed by the Go module database server. +// +// A note is text signed by one or more server keys. +// The text should be ignored unless the note is signed by +// a trusted server key and the signature has been verified +// using the server's public key. +// +// A server's public key is identified by a name, typically the "host[/path]" +// giving the base URL of the server's transparency log. +// The syntactic restrictions on a name are that it be non-empty, +// well-formed UTF-8 containing neither Unicode spaces nor plus (U+002B). +// +// A Go module database server signs texts using public key cryptography. +// A given server may have multiple public keys, each +// identified by a 32-bit hash of the public key. +// +// # Verifying Notes +// +// A [Verifier] allows verification of signatures by one server public key. +// It can report the name of the server and the uint32 hash of the key, +// and it can verify a purported signature by that key. +// +// The standard implementation of a Verifier is constructed +// by [NewVerifier] starting from a verifier key, which is a +// plain text string of the form "++". +// +// A [Verifiers] allows looking up a Verifier by the combination +// of server name and key hash. +// +// The standard implementation of a Verifiers is constructed +// by VerifierList from a list of known verifiers. +// +// A [Note] represents a text with one or more signatures. +// An implementation can reject a note with too many signatures +// (for example, more than 100 signatures). +// +// A [Signature] represents a signature on a note, verified or not. +// +// The [Open] function takes as input a signed message +// and a set of known verifiers. It decodes and verifies +// the message signatures and returns a [Note] structure +// containing the message text and (verified or unverified) signatures. +// +// # Signing Notes +// +// A [Signer] allows signing a text with a given key. +// It can report the name of the server and the hash of the key +// and can sign a raw text using that key. +// +// The standard implementation of a Signer is constructed +// by [NewSigner] starting from an encoded signer key, which is a +// plain text string of the form "PRIVATE+KEY+++". +// Anyone with an encoded signer key can sign messages using that key, +// so it must be kept secret. The encoding begins with the literal text +// "PRIVATE+KEY" to avoid confusion with the public server key. +// +// The [Sign] function takes as input a Note and a list of Signers +// and returns an encoded, signed message. +// +// # Signed Note Format +// +// A signed note consists of a text ending in newline (U+000A), +// followed by a blank line (only a newline), +// followed by one or more signature lines of this form: +// em dash (U+2014), space (U+0020), +// server name, space, base64-encoded signature, newline. +// +// Signed notes must be valid UTF-8 and must not contain any +// ASCII control characters (those below U+0020) other than newline. +// +// A signature is a base64 encoding of 4+n bytes. +// +// The first four bytes in the signature are the uint32 key hash +// stored in big-endian order. +// +// The remaining n bytes are the result of using the specified key +// to sign the note text (including the final newline but not the +// separating blank line). +// +// # Generating Keys +// +// There is only one key type, Ed25519 with algorithm identifier 1. +// New key types may be introduced in the future as needed, +// although doing so will require deploying the new algorithms to all clients +// before starting to depend on them for signatures. +// +// The [GenerateKey] function generates and returns a new signer +// and corresponding verifier. +// +// # Example +// +// Here is a well-formed signed note: +// +// If you think cryptography is the answer to your problem, +// then you don't know what your problem is. +// +// — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM= +// +// It can be constructed and displayed using: +// +// skey := "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz" +// text := "If you think cryptography is the answer to your problem,\n" + +// "then you don't know what your problem is.\n" +// +// signer, err := note.NewSigner(skey) +// if err != nil { +// log.Fatal(err) +// } +// +// msg, err := note.Sign(¬e.Note{Text: text}, signer) +// if err != nil { +// log.Fatal(err) +// } +// os.Stdout.Write(msg) +// +// The note's text is two lines, including the final newline, +// and the text is purportedly signed by a server named +// "PeterNeumann". (Although server names are canonically +// base URLs, the only syntactic requirement is that they +// not contain spaces or newlines). +// +// If [Open] is given access to a [Verifiers] including the +// [Verifier] for this key, then it will succeed at verifying +// the encoded message and returning the parsed [Note]: +// +// vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW" +// msg := []byte("If you think cryptography is the answer to your problem,\n" + +// "then you don't know what your problem is.\n" + +// "\n" + +// "— PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n") +// +// verifier, err := note.NewVerifier(vkey) +// if err != nil { +// log.Fatal(err) +// } +// verifiers := note.VerifierList(verifier) +// +// n, err := note.Open([]byte(msg), verifiers) +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("%s (%08x):\n%s", n.Sigs[0].Name, n.Sigs[0].Hash, n.Text) +// +// You can add your own signature to this message by re-signing the note: +// +// skey, vkey, err := note.GenerateKey(rand.Reader, "EnochRoot") +// if err != nil { +// log.Fatal(err) +// } +// _ = vkey // give to verifiers +// +// me, err := note.NewSigner(skey) +// if err != nil { +// log.Fatal(err) +// } +// +// msg, err := note.Sign(n, me) +// if err != nil { +// log.Fatal(err) +// } +// os.Stdout.Write(msg) +// +// This will print a doubly-signed message, like: +// +// If you think cryptography is the answer to your problem, +// then you don't know what your problem is. +// +// — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM= +// — EnochRoot rwz+eBzmZa0SO3NbfRGzPCpDckykFXSdeX+MNtCOXm2/5n2tiOHp+vAF1aGrQ5ovTG01oOTGwnWLox33WWd1RvMc+QQ= +package note + +import ( + "bytes" + "crypto/ed25519" + "crypto/sha256" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// A Verifier verifies messages signed with a specific key. +type Verifier interface { + // Name returns the server name associated with the key. + Name() string + + // KeyHash returns the key hash. + KeyHash() uint32 + + // Verify reports whether sig is a valid signature of msg. + Verify(msg, sig []byte) bool +} + +// A Signer signs messages using a specific key. +type Signer interface { + // Name returns the server name associated with the key. + Name() string + + // KeyHash returns the key hash. + KeyHash() uint32 + + // Sign returns a signature for the given message. + Sign(msg []byte) ([]byte, error) +} + +// keyHash computes the key hash for the given server name and encoded public key. +func keyHash(name string, key []byte) uint32 { + h := sha256.New() + h.Write([]byte(name)) + h.Write([]byte("\n")) + h.Write(key) + sum := h.Sum(nil) + return binary.BigEndian.Uint32(sum) +} + +var ( + errVerifierID = errors.New("malformed verifier id") + errVerifierAlg = errors.New("unknown verifier algorithm") + errVerifierHash = errors.New("invalid verifier hash") +) + +const ( + algEd25519 = 1 +) + +// isValidName reports whether name is valid. +// It must be non-empty and not have any Unicode spaces or pluses. +func isValidName(name string) bool { + return name != "" && utf8.ValidString(name) && strings.IndexFunc(name, unicode.IsSpace) < 0 && !strings.Contains(name, "+") +} + +// NewVerifier construct a new [Verifier] from an encoded verifier key. +func NewVerifier(vkey string) (Verifier, error) { + name, vkey, _ := strings.Cut(vkey, "+") + hash16, key64, _ := strings.Cut(vkey, "+") + hash, err1 := strconv.ParseUint(hash16, 16, 32) + key, err2 := base64.StdEncoding.DecodeString(key64) + if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 { + return nil, errVerifierID + } + if uint32(hash) != keyHash(name, key) { + return nil, errVerifierHash + } + + v := &verifier{ + name: name, + hash: uint32(hash), + } + + alg, key := key[0], key[1:] + switch alg { + default: + return nil, errVerifierAlg + + case algEd25519: + if len(key) != 32 { + return nil, errVerifierID + } + v.verify = func(msg, sig []byte) bool { + return ed25519.Verify(key, msg, sig) + } + } + + return v, nil +} + +// verifier is a trivial Verifier implementation. +type verifier struct { + name string + hash uint32 + verify func([]byte, []byte) bool +} + +func (v *verifier) Name() string { return v.name } +func (v *verifier) KeyHash() uint32 { return v.hash } +func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) } + +// NewSigner constructs a new [Signer] from an encoded signer key. +func NewSigner(skey string) (Signer, error) { + priv1, skey, _ := strings.Cut(skey, "+") + priv2, skey, _ := strings.Cut(skey, "+") + name, skey, _ := strings.Cut(skey, "+") + hash16, key64, _ := strings.Cut(skey, "+") + hash, err1 := strconv.ParseUint(hash16, 16, 32) + key, err2 := base64.StdEncoding.DecodeString(key64) + if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 { + return nil, errSignerID + } + + // Note: hash is the hash of the public key and we have the private key. + // Must verify hash after deriving public key. + + s := &signer{ + name: name, + hash: uint32(hash), + } + + var pubkey []byte + + alg, key := key[0], key[1:] + switch alg { + default: + return nil, errSignerAlg + + case algEd25519: + if len(key) != 32 { + return nil, errSignerID + } + key = ed25519.NewKeyFromSeed(key) + pubkey = append([]byte{algEd25519}, key[32:]...) + s.sign = func(msg []byte) ([]byte, error) { + return ed25519.Sign(key, msg), nil + } + } + + if uint32(hash) != keyHash(name, pubkey) { + return nil, errSignerHash + } + + return s, nil +} + +var ( + errSignerID = errors.New("malformed verifier id") + errSignerAlg = errors.New("unknown verifier algorithm") + errSignerHash = errors.New("invalid verifier hash") +) + +// signer is a trivial Signer implementation. +type signer struct { + name string + hash uint32 + sign func([]byte) ([]byte, error) +} + +func (s *signer) Name() string { return s.name } +func (s *signer) KeyHash() uint32 { return s.hash } +func (s *signer) Sign(msg []byte) ([]byte, error) { return s.sign(msg) } + +// GenerateKey generates a signer and verifier key pair for a named server. +// The signer key skey is private and must be kept secret. +func GenerateKey(rand io.Reader, name string) (skey, vkey string, err error) { + pub, priv, err := ed25519.GenerateKey(rand) + if err != nil { + return "", "", err + } + pubkey := append([]byte{algEd25519}, pub...) + privkey := append([]byte{algEd25519}, priv.Seed()...) + h := keyHash(name, pubkey) + + skey = fmt.Sprintf("PRIVATE+KEY+%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(privkey)) + vkey = fmt.Sprintf("%s+%08x+%s", name, h, base64.StdEncoding.EncodeToString(pubkey)) + return skey, vkey, nil +} + +// NewEd25519VerifierKey returns an encoded verifier key using the given name +// and Ed25519 public key. +func NewEd25519VerifierKey(name string, key ed25519.PublicKey) (string, error) { + if len(key) != ed25519.PublicKeySize { + return "", fmt.Errorf("invalid public key size %d, expected %d", len(key), ed25519.PublicKeySize) + } + + pubkey := append([]byte{algEd25519}, key...) + hash := keyHash(name, pubkey) + + b64Key := base64.StdEncoding.EncodeToString(pubkey) + return fmt.Sprintf("%s+%08x+%s", name, hash, b64Key), nil +} + +// A Verifiers is a collection of known verifier keys. +type Verifiers interface { + // Verifier returns the Verifier associated with the key + // identified by the name and hash. + // If the name, hash pair is unknown, Verifier should return + // an UnknownVerifierError. + Verifier(name string, hash uint32) (Verifier, error) +} + +// An UnknownVerifierError indicates that the given key is not known. +// The Open function records signatures without associated verifiers as +// unverified signatures. +type UnknownVerifierError struct { + Name string + KeyHash uint32 +} + +func (e *UnknownVerifierError) Error() string { + return fmt.Sprintf("unknown key %s+%08x", e.Name, e.KeyHash) +} + +// An ambiguousVerifierError indicates that the given name and hash +// match multiple keys passed to [VerifierList]. +// (If this happens, some malicious actor has taken control of the +// verifier list, at which point we may as well give up entirely, +// but we diagnose the problem instead.) +type ambiguousVerifierError struct { + name string + hash uint32 +} + +func (e *ambiguousVerifierError) Error() string { + return fmt.Sprintf("ambiguous key %s+%08x", e.name, e.hash) +} + +// VerifierList returns a [Verifiers] implementation that uses the given list of verifiers. +func VerifierList(list ...Verifier) Verifiers { + m := make(verifierMap) + for _, v := range list { + k := nameHash{v.Name(), v.KeyHash()} + m[k] = append(m[k], v) + } + return m +} + +type nameHash struct { + name string + hash uint32 +} + +type verifierMap map[nameHash][]Verifier + +func (m verifierMap) Verifier(name string, hash uint32) (Verifier, error) { + v, ok := m[nameHash{name, hash}] + if !ok { + return nil, &UnknownVerifierError{name, hash} + } + if len(v) > 1 { + return nil, &ambiguousVerifierError{name, hash} + } + return v[0], nil +} + +// A Note is a text and signatures. +type Note struct { + Text string // text of note + Sigs []Signature // verified signatures + UnverifiedSigs []Signature // unverified signatures +} + +// A Signature is a single signature found in a note. +type Signature struct { + // Name and Hash give the name and key hash + // for the key that generated the signature. + Name string + Hash uint32 + + // Base64 records the base64-encoded signature bytes. + Base64 string +} + +// An UnverifiedNoteError indicates that the note +// successfully parsed but had no verifiable signatures. +type UnverifiedNoteError struct { + Note *Note +} + +func (e *UnverifiedNoteError) Error() string { + return "note has no verifiable signatures" +} + +// An InvalidSignatureError indicates that the given key was known +// and the associated Verifier rejected the signature. +type InvalidSignatureError struct { + Name string + Hash uint32 +} + +func (e *InvalidSignatureError) Error() string { + return fmt.Sprintf("invalid signature for key %s+%08x", e.Name, e.Hash) +} + +var ( + errMalformedNote = errors.New("malformed note") + errInvalidSigner = errors.New("invalid signer") + errMismatchedVerifier = errors.New("verifier name or hash doesn't match signature") + + sigSplit = []byte("\n\n") + sigPrefix = []byte("— ") +) + +// Open opens and parses the message msg, checking signatures from the known verifiers. +// +// For each signature in the message, Open calls known.Verifier to find a verifier. +// If known.Verifier returns a verifier and the verifier accepts the signature, +// Open records the signature in the returned note's Sigs field. +// If known.Verifier returns a verifier but the verifier rejects the signature, +// Open returns an InvalidSignatureError. +// If known.Verifier returns an UnknownVerifierError, +// Open records the signature in the returned note's UnverifiedSigs field. +// If known.Verifier returns any other error, Open returns that error. +// +// If no known verifier has signed an otherwise valid note, +// Open returns an [UnverifiedNoteError]. +// In this case, the unverified note can be fetched from inside the error. +func Open(msg []byte, known Verifiers) (*Note, error) { + if known == nil { + // Treat nil Verifiers as empty list, to produce useful error instead of crash. + known = VerifierList() + } + + // Must have valid UTF-8 with no non-newline ASCII control characters. + for i := 0; i < len(msg); { + r, size := utf8.DecodeRune(msg[i:]) + if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 { + return nil, errMalformedNote + } + i += size + } + + // Must end with signature block preceded by blank line. + split := bytes.LastIndex(msg, sigSplit) + if split < 0 { + return nil, errMalformedNote + } + text, sigs := msg[:split+1], msg[split+2:] + if len(sigs) == 0 || sigs[len(sigs)-1] != '\n' { + return nil, errMalformedNote + } + + n := &Note{ + Text: string(text), + } + + // Parse and verify signatures. + // Ignore duplicate signatures. + seen := make(map[nameHash]bool) + seenUnverified := make(map[string]bool) + numSig := 0 + for len(sigs) > 0 { + // Pull out next signature line. + // We know sigs[len(sigs)-1] == '\n', so IndexByte always finds one. + i := bytes.IndexByte(sigs, '\n') + line := sigs[:i] + sigs = sigs[i+1:] + + if !bytes.HasPrefix(line, sigPrefix) { + return nil, errMalformedNote + } + line = line[len(sigPrefix):] + name, b64, _ := strings.Cut(string(line), " ") + sig, err := base64.StdEncoding.DecodeString(b64) + if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 { + return nil, errMalformedNote + } + hash := binary.BigEndian.Uint32(sig[0:4]) + sig = sig[4:] + + if numSig++; numSig > 100 { + // Avoid spending forever parsing a note with many signatures. + return nil, errMalformedNote + } + + v, err := known.Verifier(name, hash) + if _, ok := err.(*UnknownVerifierError); ok { + // Drop repeated identical unverified signatures. + if seenUnverified[string(line)] { + continue + } + seenUnverified[string(line)] = true + n.UnverifiedSigs = append(n.UnverifiedSigs, Signature{Name: name, Hash: hash, Base64: b64}) + continue + } + if err != nil { + return nil, err + } + + // Check that known.Verifier returned the right verifier. + if v.Name() != name || v.KeyHash() != hash { + return nil, errMismatchedVerifier + } + + // Drop repeated signatures by a single verifier. + if seen[nameHash{name, hash}] { + continue + } + seen[nameHash{name, hash}] = true + + ok := v.Verify(text, sig) + if !ok { + return nil, &InvalidSignatureError{name, hash} + } + + n.Sigs = append(n.Sigs, Signature{Name: name, Hash: hash, Base64: b64}) + } + + // Parsed and verified all the signatures. + if len(n.Sigs) == 0 { + return nil, &UnverifiedNoteError{n} + } + return n, nil +} + +// Sign signs the note with the given signers and returns the encoded message. +// The new signatures from signers are listed in the encoded message after +// the existing signatures already present in n.Sigs. +// If any signer uses the same key as an existing signature, +// the existing signature is elided from the output. +func Sign(n *Note, signers ...Signer) ([]byte, error) { + var buf bytes.Buffer + if !strings.HasSuffix(n.Text, "\n") { + return nil, errMalformedNote + } + buf.WriteString(n.Text) + + // Prepare signatures. + var sigs bytes.Buffer + have := make(map[nameHash]bool) + for _, s := range signers { + name := s.Name() + hash := s.KeyHash() + have[nameHash{name, hash}] = true + if !isValidName(name) { + return nil, errInvalidSigner + } + + sig, err := s.Sign(buf.Bytes()) // buf holds n.Text + if err != nil { + return nil, err + } + + var hbuf [4]byte + binary.BigEndian.PutUint32(hbuf[:], hash) + b64 := base64.StdEncoding.EncodeToString(append(hbuf[:], sig...)) + sigs.WriteString("— ") + sigs.WriteString(name) + sigs.WriteString(" ") + sigs.WriteString(b64) + sigs.WriteString("\n") + } + + buf.WriteString("\n") + + // Emit existing signatures not replaced by new ones. + for _, list := range [][]Signature{n.Sigs, n.UnverifiedSigs} { + for _, sig := range list { + name, hash := sig.Name, sig.Hash + if !isValidName(name) { + return nil, errMalformedNote + } + if have[nameHash{name, hash}] { + continue + } + // Double-check hash against base64. + raw, err := base64.StdEncoding.DecodeString(sig.Base64) + if err != nil || len(raw) < 4 || binary.BigEndian.Uint32(raw) != hash { + return nil, errMalformedNote + } + buf.WriteString("— ") + buf.WriteString(sig.Name) + buf.WriteString(" ") + buf.WriteString(sig.Base64) + buf.WriteString("\n") + } + } + buf.Write(sigs.Bytes()) + + return buf.Bytes(), nil +} diff --git a/vendor/golang.org/x/mod/sumdb/tlog/note.go b/vendor/golang.org/x/mod/sumdb/tlog/note.go new file mode 100644 index 00000000000..1ea765a54f8 --- /dev/null +++ b/vendor/golang.org/x/mod/sumdb/tlog/note.go @@ -0,0 +1,138 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tlog + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +// A Tree is a tree description, to be signed by a go.sum database server. +type Tree struct { + N int64 + Hash Hash +} + +// FormatTree formats a tree description for inclusion in a note. +// +// The encoded form is three lines, each ending in a newline (U+000A): +// +// go.sum database tree +// N +// Hash +// +// where N is in decimal and Hash is in base64. +// +// A future backwards-compatible encoding may add additional lines, +// which the parser can ignore. +// A future backwards-incompatible encoding would use a different +// first line (for example, "go.sum database tree v2"). +func FormatTree(tree Tree) []byte { + return fmt.Appendf(nil, "go.sum database tree\n%d\n%s\n", tree.N, tree.Hash) +} + +var errMalformedTree = errors.New("malformed tree note") +var treePrefix = []byte("go.sum database tree\n") + +// ParseTree parses a formatted tree root description. +func ParseTree(text []byte) (tree Tree, err error) { + // The message looks like: + // + // go.sum database tree + // 2 + // nND/nri/U0xuHUrYSy0HtMeal2vzD9V4k/BO79C+QeI= + // + // For forwards compatibility, extra text lines after the encoding are ignored. + if !bytes.HasPrefix(text, treePrefix) || bytes.Count(text, []byte("\n")) < 3 || len(text) > 1e6 { + return Tree{}, errMalformedTree + } + + lines := strings.SplitN(string(text), "\n", 4) + n, err := strconv.ParseInt(lines[1], 10, 64) + if err != nil || n < 0 || lines[1] != strconv.FormatInt(n, 10) { + return Tree{}, errMalformedTree + } + + h, err := base64.StdEncoding.DecodeString(lines[2]) + if err != nil || len(h) != HashSize { + return Tree{}, errMalformedTree + } + + var hash Hash + copy(hash[:], h) + return Tree{n, hash}, nil +} + +var errMalformedRecord = errors.New("malformed record data") + +// FormatRecord formats a record for serving to a client +// in a lookup response. +// +// The encoded form is the record ID as a single number, +// then the text of the record, and then a terminating blank line. +// Record text must be valid UTF-8 and must not contain any ASCII control +// characters (those below U+0020) other than newline (U+000A). +// It must end in a terminating newline and not contain any blank lines. +// +// Responses to data tiles consist of concatenated formatted records from each of +// which the first line, with the record ID, is removed. +func FormatRecord(id int64, text []byte) (msg []byte, err error) { + if !isValidRecordText(text) { + return nil, errMalformedRecord + } + msg = fmt.Appendf(nil, "%d\n", id) + msg = append(msg, text...) + msg = append(msg, '\n') + return msg, nil +} + +// isValidRecordText reports whether text is syntactically valid record text. +func isValidRecordText(text []byte) bool { + var last rune + for i := 0; i < len(text); { + r, size := utf8.DecodeRune(text[i:]) + if r < 0x20 && r != '\n' || r == utf8.RuneError && size == 1 || last == '\n' && r == '\n' { + return false + } + i += size + last = r + } + if last != '\n' { + return false + } + return true +} + +// ParseRecord parses a record description at the start of text, +// stopping immediately after the terminating blank line. +// It returns the record id, the record text, and the remainder of text. +func ParseRecord(msg []byte) (id int64, text, rest []byte, err error) { + // Leading record id. + i := bytes.IndexByte(msg, '\n') + if i < 0 { + return 0, nil, nil, errMalformedRecord + } + id, err = strconv.ParseInt(string(msg[:i]), 10, 64) + if err != nil { + return 0, nil, nil, errMalformedRecord + } + msg = msg[i+1:] + + // Record text. + i = bytes.Index(msg, []byte("\n\n")) + if i < 0 { + return 0, nil, nil, errMalformedRecord + } + text, rest = msg[:i+1], msg[i+2:] + if !isValidRecordText(text) { + return 0, nil, nil, errMalformedRecord + } + return id, text, rest, nil +} diff --git a/vendor/golang.org/x/mod/sumdb/tlog/tile.go b/vendor/golang.org/x/mod/sumdb/tlog/tile.go new file mode 100644 index 00000000000..37771c53101 --- /dev/null +++ b/vendor/golang.org/x/mod/sumdb/tlog/tile.go @@ -0,0 +1,433 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tlog + +import ( + "fmt" + "strconv" + "strings" +) + +// A Tile is a description of a transparency log tile. +// A tile of height H at level L offset N lists W consecutive hashes +// at level H*L of the tree starting at offset N*(2**H). +// A complete tile lists 2**H hashes; a partial tile lists fewer. +// Note that a tile represents the entire subtree of height H +// with those hashes as the leaves. The levels above H*L +// can be reconstructed by hashing the leaves. +// +// Each Tile can be encoded as a “tile coordinate path” +// of the form tile/H/L/NNN[.p/W]. +// The .p/W suffix is present only for partial tiles, meaning W < 2**H. +// The NNN element is an encoding of N into 3-digit path elements. +// All but the last path element begins with an "x". +// For example, +// Tile{H: 3, L: 4, N: 1234067, W: 1}'s path +// is tile/3/4/x001/x234/067.p/1, and +// Tile{H: 3, L: 4, N: 1234067, W: 8}'s path +// is tile/3/4/x001/x234/067. +// See the [Tile.Path] method and the [ParseTilePath] function. +// +// The special level L=-1 holds raw record data instead of hashes. +// In this case, the level encodes into a tile path as the path element +// "data" instead of "-1". +// +// See also https://golang.org/design/25530-sumdb#checksum-database +// and https://research.swtch.com/tlog#tiling_a_log. +type Tile struct { + H int // height of tile (1 ≤ H ≤ 30) + L int // level in tiling (-1 ≤ L ≤ 63) + N int64 // number within level (0 ≤ N, unbounded) + W int // width of tile (1 ≤ W ≤ 2**H; 2**H is complete tile) +} + +// TileForIndex returns the tile of fixed height h ≥ 1 +// and least width storing the given hash storage index. +// +// If h ≤ 0, [TileForIndex] panics. +func TileForIndex(h int, index int64) Tile { + if h <= 0 { + panic(fmt.Sprintf("TileForIndex: invalid height %d", h)) + } + t, _, _ := tileForIndex(h, index) + return t +} + +// tileForIndex returns the tile of height h ≥ 1 +// storing the given hash index, which can be +// reconstructed using tileHash(data[start:end]). +func tileForIndex(h int, index int64) (t Tile, start, end int) { + level, n := SplitStoredHashIndex(index) + t.H = h + t.L = level / h + level -= t.L * h // now level within tile + t.N = n << uint(level) >> uint(t.H) + n -= t.N << uint(t.H) >> uint(level) // now n within tile at level + t.W = int((n + 1) << uint(level)) + return t, int(n< 30 || t.L < 0 || t.L >= 64 || t.W < 1 || t.W > 1<>(H*level) > 0; level++ { + oldN := oldTreeSize >> (H * level) + newN := newTreeSize >> (H * level) + if oldN == newN { + continue + } + for n := oldN >> H; n < newN>>H; n++ { + tiles = append(tiles, Tile{H: h, L: int(level), N: n, W: 1 << H}) + } + n := newN >> H + if w := int(newN - n< 0 { + tiles = append(tiles, Tile{H: h, L: int(level), N: n, W: w}) + } + } + return tiles +} + +// ReadTileData reads the hashes for tile t from r +// and returns the corresponding tile data. +func ReadTileData(t Tile, r HashReader) ([]byte, error) { + size := t.W + if size == 0 { + size = 1 << uint(t.H) + } + start := t.N << uint(t.H) + indexes := make([]int64, size) + for i := 0; i < size; i++ { + indexes[i] = StoredHashIndex(t.H*t.L, start+int64(i)) + } + + hashes, err := r.ReadHashes(indexes) + if err != nil { + return nil, err + } + if len(hashes) != len(indexes) { + return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes)) + } + + tile := make([]byte, size*HashSize) + for i := 0; i < size; i++ { + copy(tile[i*HashSize:], hashes[i][:]) + } + return tile, nil +} + +// To limit the size of any particular directory listing, +// we encode the (possibly very large) number N +// by encoding three digits at a time. +// For example, 123456789 encodes as x123/x456/789. +// Each directory has at most 1000 each xNNN, NNN, and NNN.p children, +// so there are at most 3000 entries in any one directory. +const pathBase = 1000 + +// Path returns a tile coordinate path describing t. +func (t Tile) Path() string { + n := t.N + nStr := fmt.Sprintf("%03d", n%pathBase) + for n >= pathBase { + n /= pathBase + nStr = fmt.Sprintf("x%03d/%s", n%pathBase, nStr) + } + pStr := "" + if t.W != 1< 30 { + return Tile{}, &badPathError{path} + } + w := 1 << uint(h) + if dotP := f[len(f)-2]; strings.HasSuffix(dotP, ".p") { + ww, err := strconv.Atoi(f[len(f)-1]) + if err != nil || ww <= 0 || ww >= w { + return Tile{}, &badPathError{path} + } + w = ww + f[len(f)-2] = dotP[:len(dotP)-len(".p")] + f = f[:len(f)-1] + } + f = f[3:] + n := int64(0) + for _, s := range f { + nn, err := strconv.Atoi(strings.TrimPrefix(s, "x")) + if err != nil || nn < 0 || nn >= pathBase { + return Tile{}, &badPathError{path} + } + n = n*pathBase + int64(nn) + } + if isData { + l = -1 + } + t := Tile{H: h, L: l, N: n, W: w} + if path != t.Path() { + return Tile{}, &badPathError{path} + } + return t, nil +} + +type badPathError struct { + path string +} + +func (e *badPathError) Error() string { + return fmt.Sprintf("malformed tile path %q", e.path) +} + +// A TileReader reads tiles from a go.sum database log. +type TileReader interface { + // Height returns the height of the available tiles. + Height() int + + // ReadTiles returns the data for each requested tile. + // If ReadTiles returns err == nil, it must also return + // a data record for each tile (len(data) == len(tiles)) + // and each data record must be the correct length + // (len(data[i]) == tiles[i].W*HashSize). + // + // An implementation of ReadTiles typically reads + // them from an on-disk cache or else from a remote + // tile server. Tile data downloaded from a server should + // be considered suspect and not saved into a persistent + // on-disk cache before returning from ReadTiles. + // When the client confirms the validity of the tile data, + // it will call SaveTiles to signal that they can be safely + // written to persistent storage. + // See also https://research.swtch.com/tlog#authenticating_tiles. + ReadTiles(tiles []Tile) (data [][]byte, err error) + + // SaveTiles informs the TileReader that the tile data + // returned by ReadTiles has been confirmed as valid + // and can be saved in persistent storage (on disk). + SaveTiles(tiles []Tile, data [][]byte) +} + +// TileHashReader returns a HashReader that satisfies requests +// by loading tiles of the given tree. +// +// The returned [HashReader] checks that loaded tiles are +// valid for the given tree. Therefore, any hashes returned +// by the HashReader are already proven to be in the tree. +func TileHashReader(tree Tree, tr TileReader) HashReader { + return &tileHashReader{tree: tree, tr: tr} +} + +type tileHashReader struct { + tree Tree + tr TileReader +} + +// tileParent returns t's k'th tile parent in the tiles for a tree of size n. +// If there is no such parent, tileParent returns Tile{}. +func tileParent(t Tile, k int, n int64) Tile { + t.L += k + t.N >>= uint(k * t.H) + t.W = 1 << uint(t.H) + if max := n >> uint(t.L*t.H); t.N<= max { + if t.N<= max { + return Tile{} + } + t.W = int(max - t.N<= StoredHashIndex(0, r.tree.N) { + return nil, fmt.Errorf("indexes not in tree") + } + + tile, _, _ := tileForIndex(h, x) + + // Walk up parent tiles until we find one we've requested. + // That one will be authenticated. + k := 0 + for ; ; k++ { + p := tileParent(tile, k, r.tree.N) + if j, ok := tileOrder[p]; ok { + if k == 0 { + indexTileOrder[i] = j + } + break + } + } + + // Walk back down recording child tiles after parents. + // This loop ends by revisiting the tile for this index + // (tileParent(tile, 0, r.tree.N)) unless k == 0, in which + // case the previous loop did it. + for k--; k >= 0; k-- { + p := tileParent(tile, k, r.tree.N) + if p.W != 1<= 0; i-- { + h, err := HashFromTile(tiles[stxTileOrder[i]], data[stxTileOrder[i]], stx[i]) + if err != nil { + return nil, err + } + th = NodeHash(h, th) + } + if th != r.tree.Hash { + // The tiles do not support the tree hash. + // We know at least one is wrong, but not which one. + return nil, fmt.Errorf("downloaded inconsistent tile") + } + + // Authenticate full tiles against their parents. + for i := len(stx); i < len(tiles); i++ { + tile := tiles[i] + p := tileParent(tile, 1, r.tree.N) + j, ok := tileOrder[p] + if !ok { + return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost parent of %v", r.tree.N, indexes, tile) + } + h, err := HashFromTile(p, data[j], StoredHashIndex(p.L*p.H, tile.N)) + if err != nil { + return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost hash of %v: %v", r.tree.N, indexes, tile, err) + } + if h != tileHash(data[i]) { + return nil, fmt.Errorf("downloaded inconsistent tile") + } + } + + // Now we have all the tiles needed for the requested hashes, + // and we've authenticated the full tile set against the trusted tree hash. + r.tr.SaveTiles(tiles, data) + + // Pull out the requested hashes. + hashes := make([]Hash, len(indexes)) + for i, x := range indexes { + j := indexTileOrder[i] + h, err := HashFromTile(tiles[j], data[j], x) + if err != nil { + return nil, fmt.Errorf("bad math in tileHashReader %d %v: lost hash %v: %v", r.tree.N, indexes, x, err) + } + hashes[i] = h + } + + return hashes, nil +} diff --git a/vendor/golang.org/x/mod/sumdb/tlog/tlog.go b/vendor/golang.org/x/mod/sumdb/tlog/tlog.go new file mode 100644 index 00000000000..480b5eff5af --- /dev/null +++ b/vendor/golang.org/x/mod/sumdb/tlog/tlog.go @@ -0,0 +1,605 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tlog implements a tamper-evident log +// used in the Go module go.sum database server. +// +// This package follows the design of Certificate Transparency (RFC 6962) +// and its proofs are compatible with that system. +// See TestCertificateTransparency. +package tlog + +import ( + "crypto/sha256" + "encoding/base64" + "errors" + "fmt" + "math/bits" +) + +// A Hash is a hash identifying a log record or tree root. +type Hash [HashSize]byte + +// HashSize is the size of a Hash in bytes. +const HashSize = 32 + +// String returns a base64 representation of the hash for printing. +func (h Hash) String() string { + return base64.StdEncoding.EncodeToString(h[:]) +} + +// MarshalJSON marshals the hash as a JSON string containing the base64-encoded hash. +func (h Hash) MarshalJSON() ([]byte, error) { + return []byte(`"` + h.String() + `"`), nil +} + +// UnmarshalJSON unmarshals a hash from JSON string containing the a base64-encoded hash. +func (h *Hash) UnmarshalJSON(data []byte) error { + if len(data) != 1+44+1 || data[0] != '"' || data[len(data)-2] != '=' || data[len(data)-1] != '"' { + return errors.New("cannot decode hash") + } + + // As of Go 1.12, base64.StdEncoding.Decode insists on + // slicing into target[33:] even when it only writes 32 bytes. + // Since we already checked that the hash ends in = above, + // we can use base64.RawStdEncoding with the = removed; + // RawStdEncoding does not exhibit the same bug. + // We decode into a temporary to avoid writing anything to *h + // unless the entire input is well-formed. + var tmp Hash + n, err := base64.RawStdEncoding.Decode(tmp[:], data[1:len(data)-2]) + if err != nil || n != HashSize { + return errors.New("cannot decode hash") + } + *h = tmp + return nil +} + +// ParseHash parses the base64-encoded string form of a hash. +func ParseHash(s string) (Hash, error) { + data, err := base64.StdEncoding.DecodeString(s) + if err != nil || len(data) != HashSize { + return Hash{}, fmt.Errorf("malformed hash") + } + var h Hash + copy(h[:], data) + return h, nil +} + +// maxpow2 returns k, the maximum power of 2 smaller than n, +// as well as l = log₂ k (so k = 1< 0; l-- { + n = 2*n + 1 + } + + // Level 0's n'th hash is written at n+n/2+n/4+... (eventually n/2ⁱ hits zero). + i := int64(0) + for ; n > 0; n >>= 1 { + i += n + } + + return i + int64(level) +} + +// SplitStoredHashIndex is the inverse of [StoredHashIndex]. +// That is, SplitStoredHashIndex(StoredHashIndex(level, n)) == level, n. +func SplitStoredHashIndex(index int64) (level int, n int64) { + // Determine level 0 record before index. + // StoredHashIndex(0, n) < 2*n, + // so the n we want is in [index/2, index/2+log₂(index)]. + n = index / 2 + indexN := StoredHashIndex(0, n) + if indexN > index { + panic("bad math") + } + for { + // Each new record n adds 1 + trailingZeros(n) hashes. + x := indexN + 1 + int64(bits.TrailingZeros64(uint64(n+1))) + if x > index { + break + } + n++ + indexN = x + } + // The hash we want was committed with record n, + // meaning it is one of (0, n), (1, n/2), (2, n/4), ... + level = int(index - indexN) + return level, n >> uint(level) +} + +// StoredHashCount returns the number of stored hashes +// that are expected for a tree with n records. +func StoredHashCount(n int64) int64 { + if n == 0 { + return 0 + } + // The tree will have the hashes up to the last leaf hash. + numHash := StoredHashIndex(0, n-1) + 1 + // And it will have any hashes for subtrees completed by that leaf. + for i := uint64(n - 1); i&1 != 0; i >>= 1 { + numHash++ + } + return numHash +} + +// StoredHashes returns the hashes that must be stored when writing +// record n with the given data. The hashes should be stored starting +// at StoredHashIndex(0, n). The result will have at most 1 + log₂ n hashes, +// but it will average just under two per call for a sequence of calls for n=1..k. +// +// StoredHashes may read up to log n earlier hashes from r +// in order to compute hashes for completed subtrees. +func StoredHashes(n int64, data []byte, r HashReader) ([]Hash, error) { + return StoredHashesForRecordHash(n, RecordHash(data), r) +} + +// StoredHashesForRecordHash is like [StoredHashes] but takes +// as its second argument RecordHash(data) instead of data itself. +func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) { + // Start with the record hash. + hashes := []Hash{h} + + // Build list of indexes needed for hashes for completed subtrees. + // Each trailing 1 bit in the binary representation of n completes a subtree + // and consumes a hash from an adjacent subtree. + m := int(bits.TrailingZeros64(uint64(n + 1))) + indexes := make([]int64, m) + for i := range m { + // We arrange indexes in sorted order. + // Note that n>>i is always odd. + indexes[m-1-i] = StoredHashIndex(i, n>>uint(i)-1) + } + + // Fetch hashes. + old, err := r.ReadHashes(indexes) + if err != nil { + return nil, err + } + if len(old) != len(indexes) { + return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(old)) + } + + // Build new hashes. + for i := range m { + h = NodeHash(old[m-1-i], h) + hashes = append(hashes, h) + } + return hashes, nil +} + +// A HashReader can read hashes for nodes in the log's tree structure. +type HashReader interface { + // ReadHashes returns the hashes with the given stored hash indexes + // (see StoredHashIndex and SplitStoredHashIndex). + // ReadHashes must return a slice of hashes the same length as indexes, + // or else it must return a non-nil error. + // ReadHashes may run faster if indexes is sorted in increasing order. + ReadHashes(indexes []int64) ([]Hash, error) +} + +// A HashReaderFunc is a function implementing [HashReader]. +type HashReaderFunc func([]int64) ([]Hash, error) + +func (f HashReaderFunc) ReadHashes(indexes []int64) ([]Hash, error) { + return f(indexes) +} + +// emptyHash is the hash of the empty tree, per RFC 6962, Section 2.1. +// It is the hash of the empty string. +var emptyHash = Hash{ + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, + 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, + 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, + 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, +} + +// TreeHash computes the hash for the root of the tree with n records, +// using the HashReader to obtain previously stored hashes +// (those returned by StoredHashes during the writes of those n records). +// TreeHash makes a single call to ReadHash requesting at most 1 + log₂ n hashes. +func TreeHash(n int64, r HashReader) (Hash, error) { + if n == 0 { + return emptyHash, nil + } + indexes := subTreeIndex(0, n, nil) + hashes, err := r.ReadHashes(indexes) + if err != nil { + return Hash{}, err + } + if len(hashes) != len(indexes) { + return Hash{}, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes)) + } + hash, hashes := subTreeHash(0, n, hashes) + if len(hashes) != 0 { + panic("tlog: bad index math in TreeHash") + } + return hash, nil +} + +// subTreeIndex returns the storage indexes needed to compute +// the hash for the subtree containing records [lo, hi), +// appending them to need and returning the result. +// See https://tools.ietf.org/html/rfc6962#section-2.1 +func subTreeIndex(lo, hi int64, need []int64) []int64 { + // See subTreeHash below for commentary. + for lo < hi { + k, level := maxpow2(hi - lo + 1) + if lo&(k-1) != 0 { + panic("tlog: bad math in subTreeIndex") + } + need = append(need, StoredHashIndex(level, lo>>uint(level))) + lo += k + } + return need +} + +// subTreeHash computes the hash for the subtree containing records [lo, hi), +// assuming that hashes are the hashes corresponding to the indexes +// returned by subTreeIndex(lo, hi). +// It returns any leftover hashes. +func subTreeHash(lo, hi int64, hashes []Hash) (Hash, []Hash) { + // Repeatedly partition the tree into a left side with 2^level nodes, + // for as large a level as possible, and a right side with the fringe. + // The left hash is stored directly and can be read from storage. + // The right side needs further computation. + numTree := 0 + for lo < hi { + k, _ := maxpow2(hi - lo + 1) + if lo&(k-1) != 0 || lo >= hi { + panic("tlog: bad math in subTreeHash") + } + numTree++ + lo += k + } + + if len(hashes) < numTree { + panic("tlog: bad index math in subTreeHash") + } + + // Reconstruct hash. + h := hashes[numTree-1] + for i := numTree - 2; i >= 0; i-- { + h = NodeHash(hashes[i], h) + } + return h, hashes[numTree:] +} + +// A RecordProof is a verifiable proof that a particular log root contains a particular record. +// RFC 6962 calls this a “Merkle audit path.” +type RecordProof []Hash + +// ProveRecord returns the proof that the tree of size t contains the record with index n. +func ProveRecord(t, n int64, r HashReader) (RecordProof, error) { + if t < 0 || n < 0 || n >= t { + return nil, fmt.Errorf("tlog: invalid inputs in ProveRecord") + } + indexes := leafProofIndex(0, t, n, nil) + if len(indexes) == 0 { + return RecordProof{}, nil + } + hashes, err := r.ReadHashes(indexes) + if err != nil { + return nil, err + } + if len(hashes) != len(indexes) { + return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes)) + } + + p, hashes := leafProof(0, t, n, hashes) + if len(hashes) != 0 { + panic("tlog: bad index math in ProveRecord") + } + return p, nil +} + +// leafProofIndex builds the list of indexes needed to construct the proof +// that leaf n is contained in the subtree with leaves [lo, hi). +// It appends those indexes to need and returns the result. +// See https://tools.ietf.org/html/rfc6962#section-2.1.1 +func leafProofIndex(lo, hi, n int64, need []int64) []int64 { + // See leafProof below for commentary. + if !(lo <= n && n < hi) { + panic("tlog: bad math in leafProofIndex") + } + if lo+1 == hi { + return need + } + if k, _ := maxpow2(hi - lo); n < lo+k { + need = leafProofIndex(lo, lo+k, n, need) + need = subTreeIndex(lo+k, hi, need) + } else { + need = subTreeIndex(lo, lo+k, need) + need = leafProofIndex(lo+k, hi, n, need) + } + return need +} + +// leafProof constructs the proof that leaf n is contained in the subtree with leaves [lo, hi). +// It returns any leftover hashes as well. +// See https://tools.ietf.org/html/rfc6962#section-2.1.1 +func leafProof(lo, hi, n int64, hashes []Hash) (RecordProof, []Hash) { + // We must have lo <= n < hi or else the code here has a bug. + if !(lo <= n && n < hi) { + panic("tlog: bad math in leafProof") + } + + if lo+1 == hi { // n == lo + // Reached the leaf node. + // The verifier knows what the leaf hash is, so we don't need to send it. + return RecordProof{}, hashes + } + + // Walk down the tree toward n. + // Record the hash of the path not taken (needed for verifying the proof). + var p RecordProof + var th Hash + if k, _ := maxpow2(hi - lo); n < lo+k { + // n is on left side + p, hashes = leafProof(lo, lo+k, n, hashes) + th, hashes = subTreeHash(lo+k, hi, hashes) + } else { + // n is on right side + th, hashes = subTreeHash(lo, lo+k, hashes) + p, hashes = leafProof(lo+k, hi, n, hashes) + } + return append(p, th), hashes +} + +var errProofFailed = errors.New("invalid transparency proof") + +// CheckRecord verifies that p is a valid proof that the tree of size t +// with hash th has an n'th record with hash h. +func CheckRecord(p RecordProof, t int64, th Hash, n int64, h Hash) error { + if t < 0 || n < 0 || n >= t { + return fmt.Errorf("tlog: invalid inputs in CheckRecord") + } + th2, err := runRecordProof(p, 0, t, n, h) + if err != nil { + return err + } + if th2 == th { + return nil + } + return errProofFailed +} + +// runRecordProof runs the proof p that leaf n is contained in the subtree with leaves [lo, hi). +// Running the proof means constructing and returning the implied hash of that +// subtree. +func runRecordProof(p RecordProof, lo, hi, n int64, leafHash Hash) (Hash, error) { + // We must have lo <= n < hi or else the code here has a bug. + if !(lo <= n && n < hi) { + panic("tlog: bad math in runRecordProof") + } + + if lo+1 == hi { // m == lo + // Reached the leaf node. + // The proof must not have any unnecessary hashes. + if len(p) != 0 { + return Hash{}, errProofFailed + } + return leafHash, nil + } + + if len(p) == 0 { + return Hash{}, errProofFailed + } + + k, _ := maxpow2(hi - lo) + if n < lo+k { + th, err := runRecordProof(p[:len(p)-1], lo, lo+k, n, leafHash) + if err != nil { + return Hash{}, err + } + return NodeHash(th, p[len(p)-1]), nil + } else { + th, err := runRecordProof(p[:len(p)-1], lo+k, hi, n, leafHash) + if err != nil { + return Hash{}, err + } + return NodeHash(p[len(p)-1], th), nil + } +} + +// A TreeProof is a verifiable proof that a particular log tree contains +// as a prefix all records present in an earlier tree. +// RFC 6962 calls this a “Merkle consistency proof.” +type TreeProof []Hash + +// ProveTree returns the proof that the tree of size t contains +// as a prefix all the records from the tree of smaller size n. +func ProveTree(t, n int64, h HashReader) (TreeProof, error) { + if t < 1 || n < 1 || n > t { + return nil, fmt.Errorf("tlog: invalid inputs in ProveTree") + } + indexes := treeProofIndex(0, t, n, nil) + if len(indexes) == 0 { + return TreeProof{}, nil + } + hashes, err := h.ReadHashes(indexes) + if err != nil { + return nil, err + } + if len(hashes) != len(indexes) { + return nil, fmt.Errorf("tlog: ReadHashes(%d indexes) = %d hashes", len(indexes), len(hashes)) + } + + p, hashes := treeProof(0, t, n, hashes) + if len(hashes) != 0 { + panic("tlog: bad index math in ProveTree") + } + return p, nil +} + +// treeProofIndex builds the list of indexes needed to construct +// the sub-proof related to the subtree containing records [lo, hi). +// See https://tools.ietf.org/html/rfc6962#section-2.1.2. +func treeProofIndex(lo, hi, n int64, need []int64) []int64 { + // See treeProof below for commentary. + if !(lo < n && n <= hi) { + panic("tlog: bad math in treeProofIndex") + } + + if n == hi { + if lo == 0 { + return need + } + return subTreeIndex(lo, hi, need) + } + + if k, _ := maxpow2(hi - lo); n <= lo+k { + need = treeProofIndex(lo, lo+k, n, need) + need = subTreeIndex(lo+k, hi, need) + } else { + need = subTreeIndex(lo, lo+k, need) + need = treeProofIndex(lo+k, hi, n, need) + } + return need +} + +// treeProof constructs the sub-proof related to the subtree containing records [lo, hi). +// It returns any leftover hashes as well. +// See https://tools.ietf.org/html/rfc6962#section-2.1.2. +func treeProof(lo, hi, n int64, hashes []Hash) (TreeProof, []Hash) { + // We must have lo < n <= hi or else the code here has a bug. + if !(lo < n && n <= hi) { + panic("tlog: bad math in treeProof") + } + + // Reached common ground. + if n == hi { + if lo == 0 { + // This subtree corresponds exactly to the old tree. + // The verifier knows that hash, so we don't need to send it. + return TreeProof{}, hashes + } + th, hashes := subTreeHash(lo, hi, hashes) + return TreeProof{th}, hashes + } + + // Interior node for the proof. + // Decide whether to walk down the left or right side. + var p TreeProof + var th Hash + if k, _ := maxpow2(hi - lo); n <= lo+k { + // m is on left side + p, hashes = treeProof(lo, lo+k, n, hashes) + th, hashes = subTreeHash(lo+k, hi, hashes) + } else { + // m is on right side + th, hashes = subTreeHash(lo, lo+k, hashes) + p, hashes = treeProof(lo+k, hi, n, hashes) + } + return append(p, th), hashes +} + +// CheckTree verifies that p is a valid proof that the tree of size t with hash th +// contains as a prefix the tree of size n with hash h. +func CheckTree(p TreeProof, t int64, th Hash, n int64, h Hash) error { + if t < 1 || n < 1 || n > t { + return fmt.Errorf("tlog: invalid inputs in CheckTree") + } + h2, th2, err := runTreeProof(p, 0, t, n, h) + if err != nil { + return err + } + if th2 == th && h2 == h { + return nil + } + return errProofFailed +} + +// runTreeProof runs the sub-proof p related to the subtree containing records [lo, hi), +// where old is the hash of the old tree with n records. +// Running the proof means constructing and returning the implied hashes of that +// subtree in both the old and new tree. +func runTreeProof(p TreeProof, lo, hi, n int64, old Hash) (Hash, Hash, error) { + // We must have lo < n <= hi or else the code here has a bug. + if !(lo < n && n <= hi) { + panic("tlog: bad math in runTreeProof") + } + + // Reached common ground. + if n == hi { + if lo == 0 { + if len(p) != 0 { + return Hash{}, Hash{}, errProofFailed + } + return old, old, nil + } + if len(p) != 1 { + return Hash{}, Hash{}, errProofFailed + } + return p[0], p[0], nil + } + + if len(p) == 0 { + return Hash{}, Hash{}, errProofFailed + } + + // Interior node for the proof. + k, _ := maxpow2(hi - lo) + if n <= lo+k { + oh, th, err := runTreeProof(p[:len(p)-1], lo, lo+k, n, old) + if err != nil { + return Hash{}, Hash{}, err + } + return oh, NodeHash(th, p[len(p)-1]), nil + } else { + oh, th, err := runTreeProof(p[:len(p)-1], lo+k, hi, n, old) + if err != nil { + return Hash{}, Hash{}, err + } + return NodeHash(p[len(p)-1], oh), NodeHash(p[len(p)-1], th), nil + } +} diff --git a/vendor/golang.org/x/tools/go/ast/edge/edge.go b/vendor/golang.org/x/tools/go/ast/edge/edge.go index 4f6ccfd6e5e..8dc4dd1502b 100644 --- a/vendor/golang.org/x/tools/go/ast/edge/edge.go +++ b/vendor/golang.org/x/tools/go/ast/edge/edge.go @@ -12,7 +12,7 @@ import ( "reflect" ) -// A Kind describes a field of an ast.Node struct. +// A Kind describes a field of an [ast.Node] struct. type Kind uint8 // String returns a description of the edge kind. @@ -41,21 +41,25 @@ func (k Kind) Get(n ast.Node, idx int) ast.Node { panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n)) } v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index) - if idx != -1 { - v = v.Index(idx) // asserts valid index - } else { - // (The type assertion below asserts that v is not a slice.) + + if v.Kind() == reflect.Slice { + v = v.Index(idx) // asserts valid idx + } else if idx != -1 { + panic(fmt.Sprintf("%v, Get(%T, %d): cannot index non-slice", v, n, idx)) } - return v.Interface().(ast.Node) // may be nil + + out, _ := v.Interface().(ast.Node) // may be nil + return out } +// Each [Kind] is named Type_Field, where Type is the +// [ast.Node] struct type and Field is the name of the field const ( Invalid Kind = iota // for nodes at the root of the traversal - // Kinds are sorted alphabetically. - // Numbering is not stable. - // Each is named Type_Field, where Type is the - // ast.Node struct type and Field is the name of the field + // As of Go1.26 these kinds are sorted alphabetically, but + // numbering must be stable, so any new addition of const should + // use a new value (be added at the end of the list). ArrayType_Elt ArrayType_Len diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index a6c17cf6346..8e60cbbed7b 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -207,11 +207,10 @@ func goListDriver(cfg *Config, runner *gocommand.Runner, overlay string, pattern // doesn't exist. extractQueries: for _, pattern := range patterns { - eqidx := strings.Index(pattern, "=") - if eqidx < 0 { + query, value, ok := strings.Cut(pattern, "=") + if !ok { restPatterns = append(restPatterns, pattern) } else { - query, value := pattern[:eqidx], pattern[eqidx+len("="):] switch query { case "file": containFiles = append(containFiles, value) @@ -563,8 +562,18 @@ func (state *golistState) createDriverResponse(words ...string) (*DriverResponse } else { // golang/go#38990: go list silently fails to do cgo processing pkg.CompiledGoFiles = nil + + var msg strings.Builder + fmt.Fprintf(&msg, "go list failed to return CompiledGoFiles for %q.\n", p.Name) + + for _, err := range p.DepsErrors { + msg.WriteString(strings.TrimSpace(err.Err)) + msg.WriteByte('\n') + } + + msg.WriteString("This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.") pkg.Errors = append(pkg.Errors, Error{ - Msg: "go list failed to return CompiledGoFiles. This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.", + Msg: msg.String(), Kind: ListError, }) } diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 412ba06b56d..de683684ab1 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -539,6 +539,11 @@ type Package struct { // depsErrors is the DepsErrors field from the go list response, if any. depsErrors []*packagesinternal.PackageError + + // exportDataError is the error encountered reading export data, if any. + // Decoding export data should ordinarily be infallible, so this typically + // indicates a producer/consumer version skew. + exportDataError error } // Module provides module information for a package. @@ -1073,10 +1078,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { } // TODO(adonovan): this condition looks wrong: - // I think it should be lpkg.needtypes && !lpg.needsrc, + // I think it should be lpkg.needtypes && !lpkg.needsrc, // so that NeedSyntax without NeedTypes can be satisfied by export data. if !lpkg.needsrc { if err := ld.loadFromExportData(lpkg); err != nil { + lpkg.exportDataError = err lpkg.Errors = append(lpkg.Errors, Error{ Pos: "-", Msg: err.Error(), @@ -1215,7 +1221,13 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { if ipkg.Types != nil && ipkg.Types.Complete() { return ipkg.Types, nil } - log.Fatalf("internal error: package %q without types was imported from %q", path, lpkg) + + // If types are unavailable, there must be an export data error. + if ipkg.exportDataError != nil { + return nil, ipkg.exportDataError + } + + log.Fatalf("internal error: expected complete types for package %q", path) panic("unreachable") }) diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index 77aad553d5b..0d6d0bced0f 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -24,8 +24,10 @@ package objectpath import ( + "encoding/binary" "fmt" "go/types" + "slices" "strconv" "strings" @@ -124,7 +126,66 @@ func For(obj types.Object) (Path, error) { // An Encoder amortizes the cost of encoding the paths of multiple objects. // The zero value of an Encoder is ready to use. type Encoder struct { - scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects + pkgIndex map[*types.Package]*pkgIndex +} + +// A traversal encapsulates the state of a single traversal of the object/type graph. +type traversal struct { + pkg *types.Package + ix *pkgIndex // non-nil if we are building the index + + target types.Object // the sought symbol (if ix == nil) + found Path // the found path (if ix == nil) + + // These maps are used to short circuit cycles through + // interface methods, such as occur in the following example: + // + // type I interface { f() interface{I} } + // + // See golang/go#68046 for details. + seenTParamNames map[*types.TypeName]bool // global cycle breaking through type parameters + seenMethods map[*types.Func]bool // global cycle breaking through recursive interfaces +} + +// A pkgIndex holds a compressed index of objectpaths of all symbols +// (fields, methods, params) requiring search for an entire package. +// +// The first time a search for a given package is requested, we simply +// traverse the type graph for the target object, maintaining the +// current object path as a stack. If we find the target object, we +// save the path and terminate the main loop (but it's not worth +// breaking out of the current recursion). +// +// On the second search (a pkgIndex exists but its data is nil), we +// build an index of the traversal, which we use for all subsequent +// searches. +// +// The traversal index is encoded in the data field as a list of records, +// one per node, in preorder. Records are of two types: +// +// - A record for a package-level object consists of a pair +// (parent, nameIndex uvarint), where parent is zero and +// nameIndex is the index of the object's name in the sorted +// pkg.Scope().Names() slice. +// +// - A record for a nested node (a segment of an object path) +// consists of (parent uvarint, op byte, index uvarint), where +// parent is the index of the record for the parent node, +// op is the destructuring operator, and index (if op = [AFMTr]) +// is its integer operand. +// +// Since data[0] = 0 all nodes have positive offsets. In effect the +// encoding is a trie in which each node stores one path segment +// and points to the node for its prefix. +// +// TODO(adonovan): opt: evaluate an only 2-level tree with nodes for +// package-level objects and the-rest-of-the-path. One calculation +// suggested that it might be similar speed but 30% more compact. +type pkgIndex struct { + pkg *types.Package + data []byte // encoding of traversal; nil if not yet constructed + scopeNames []string // memo of pkg.Scope().Names() to avoid O(n) alloc/sort at lookup + offsets map[types.Object]uint32 // each object's node offset within encoded traversal data } // For returns the path to an object relative to its package, @@ -211,10 +272,9 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { if pkg == nil { return "", fmt.Errorf("predeclared %s has no path", obj) } - scope := pkg.Scope() // 2. package-level object? - if scope.Lookup(obj.Name()) == obj { + if pkg.Scope().Lookup(obj.Name()) == obj { // Only exported objects (and non-exported types) have a path. // Non-exported types may be referenced by other objects. if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() { @@ -232,19 +292,18 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { // have a path. return "", fmt.Errorf("no path for %v", obj) } + case *types.Const, // Only package-level constants have a path. *types.Label, // Labels are function-local. *types.PkgName: // PkgNames are file-local. return "", fmt.Errorf("no path for %v", obj) case *types.Var: - // Could be: - // - a field (obj.IsField()) - // - a func parameter or result - // - a local var. - // Sadly there is no way to distinguish - // a param/result from a local - // so we must proceed to the find. + // A var, if not package-level, must be a + // parameter (incl. receiver) or result, or a struct field. + if obj.Kind() == types.LocalVar { + return "", fmt.Errorf("no path for local %v", obj) + } case *types.Func: // A func, if not package-level, must be a method. @@ -261,89 +320,311 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { panic(obj) } - // 4. Search the API for the path to the var (field/param/result) or method. + // 4. Search the object/type graph for the path to + // the var (field/param/result) or method. + ix, ok := enc.pkgIndex[pkg] + if !ok { + // First search: don't build an index, just traverse. + // This avoids allocation in [For], whose Encoder + // lives for a single call. + ix = &pkgIndex{pkg: pkg} + + if enc.pkgIndex == nil { + enc.pkgIndex = make(map[*types.Package]*pkgIndex) + } + enc.pkgIndex[pkg] = ix // build the index next time + + f := traversal{pkg: pkg, target: obj} + f.traverse() + + if f.found != "" { + return f.found, nil + } + } else { + // Second search: build an index while traversing. + if ix.data == nil { + ix.offsets = make(map[types.Object]uint32) + ix.data = []byte{0} // offset 0 is sentinel + (&traversal{pkg: pkg, ix: ix}).traverse() + } + + // Second and later searches: consult the index. + if offset, ok := ix.offsets[obj]; ok { + return ix.path(offset), nil + } + } + + return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) +} + +// traverse performs a complete traversal of all symbols reachable from the package. +func (tr *traversal) traverse() { + scope := tr.pkg.Scope() + names := scope.Names() + if tr.ix != nil { + tr.ix.scopeNames = names + } + + empty := make([]byte, 0, 48) // initial space for stack (ix == nil) - // First inspect package-level named types. + // First inspect package-level type names. // In the presence of path aliases, these give // the best paths because non-types may // refer to types, but not the reverse. - empty := make([]byte, 0, 48) // initial space - objs := enc.scopeObjects(scope) - for _, o := range objs { - tname, ok := o.(*types.TypeName) - if !ok { - continue // handle non-types in second pass + for i, name := range names { + if tr.found != "" { + return // found (ix == nil) } - path := append(empty, o.Name()...) - path = append(path, opType) - - T := o.Type() - if alias, ok := T.(*types.Alias); ok { - if r := findTypeParam(obj, alias.TypeParams(), path, opTypeParam); r != nil { - return Path(r), nil - } - if r := find(obj, alias.Rhs(), append(path, opRhs)); r != nil { - return Path(r), nil - } + obj := scope.Lookup(name) + if _, ok := obj.(*types.TypeName); !ok { + continue // handle non-types in second pass + } - } else if tname.IsAlias() { - // legacy alias - if r := find(obj, T, path); r != nil { - return Path(r), nil - } + // emit (name, opType) + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + path = append(path, opType) + } else { + offset = tr.ix.emitPackageLevel(i) + tr.ix.offsets[obj] = offset + offset = tr.ix.emitPathSegment(offset, opType, -1) + } - } else if named, ok := T.(*types.Named); ok { - // defined (named) type - if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil { - return Path(r), nil - } - if r := find(obj, named.Underlying(), append(path, opUnderlying)); r != nil { - return Path(r), nil - } + // A TypeName (for Named or Alias) may have type parameters. + switch t := obj.Type().(type) { + case *types.Alias: + tr.tparams(t.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opRhs, -1, t.Rhs()) + case *types.Named: + tr.tparams(t.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opUnderlying, -1, t.Underlying()) } } // Then inspect everything else: - // non-types, and declared methods of defined types. - for _, o := range objs { - path := append(empty, o.Name()...) - if _, ok := o.(*types.TypeName); !ok { - if o.Exported() { + // exported non-types, and declared methods of defined types. + for i, name := range names { + if tr.found != "" { + return // found (ix == nil) + } + + obj := scope.Lookup(name) + + if tname, ok := obj.(*types.TypeName); !ok { + if obj.Exported() { // exported non-type (const, var, func) - if r := find(obj, o.Type(), append(path, opType)); r != nil { - return Path(r), nil + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + } else { + offset = tr.ix.emitPackageLevel(i) + tr.ix.offsets[obj] = offset } + tr.typ(path, offset, opType, -1, obj.Type()) } - continue - } - // Inspect declared methods of defined types. - if T, ok := types.Unalias(o.Type()).(*types.Named); ok { - path = append(path, opType) + } else if T, ok := types.Unalias(tname.Type()).(*types.Named); ok { + // defined type + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + path = append(path, opType) + } else { + // Inv: map entry for obj was populated in first pass. + offset = tr.ix.emitPathSegment(tr.ix.offsets[obj], opType, -1) + } + + // Inspect declared methods of defined types. + // // The method index here is always with respect // to the underlying go/types data structures, // which ultimately derives from source order // and must be preserved by export data. for i := 0; i < T.NumMethods(); i++ { m := T.Method(i) - path2 := appendOpArg(path, opMethod, i) - if m == obj { - return Path(path2), nil // found declared method - } - if r := find(obj, m.Type(), append(path2, opType)); r != nil { - return Path(r), nil + tr.object(path, offset, opMethod, i, m) + } + } + } +} + +func (tr *traversal) visitType(path []byte, offset uint32, T types.Type) { + switch T := T.(type) { + case *types.Alias: + tr.typ(path, offset, opRhs, -1, T.Rhs()) + + case *types.Basic, *types.Named: + // Named types belonging to pkg were handled already, + // so T must belong to another package. No path. + return + + case *types.Pointer, *types.Slice, *types.Array, *types.Chan: + type hasElem interface{ Elem() types.Type } // note: includes Map + tr.typ(path, offset, opElem, -1, T.(hasElem).Elem()) + + case *types.Map: + tr.typ(path, offset, opKey, -1, T.Key()) + tr.typ(path, offset, opElem, -1, T.Elem()) + + case *types.Signature: + tr.tparams(T.RecvTypeParams(), path, offset, opRecvTypeParam) + tr.tparams(T.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opParams, -1, T.Params()) + tr.typ(path, offset, opResults, -1, T.Results()) + + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + tr.object(path, offset, opField, i, T.Field(i)) + } + + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + tr.object(path, offset, opAt, i, T.At(i)) + } + + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + if m.Pkg() != nil && m.Pkg() != tr.pkg { + continue // embedded method from another package + } + if !tr.seenMethods[m] { + if tr.seenMethods == nil { + tr.seenMethods = make(map[*types.Func]bool) } + tr.seenMethods[m] = true + tr.object(path, offset, opMethod, i, m) } } + + case *types.TypeParam: + tname := T.Obj() + if tname.Pkg() != nil && tname.Pkg() != tr.pkg { + return // type parameter from another package + } + if !tr.seenTParamNames[tname] { + if tr.seenTParamNames == nil { + tr.seenTParamNames = make(map[*types.TypeName]bool) + } + tr.seenTParamNames[tname] = true + tr.object(path, offset, opObj, -1, tname) + tr.typ(path, offset, opConstraint, -1, T.Constraint()) + } } +} - return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) +func (tr *traversal) tparams(list *types.TypeParamList, path []byte, offset uint32, op byte) { + for i := 0; i < list.Len(); i++ { + tr.typ(path, offset, op, i, list.At(i)) + } +} + +// typ descends the type graph edge (op, index), then proceeds to traverse type t. +func (tr *traversal) typ(path []byte, offset uint32, op byte, index int, t types.Type) { + if tr.ix == nil { + path = appendOpArg(path, op, index) + } else { + offset = tr.ix.emitPathSegment(offset, op, index) + } + tr.visitType(path, offset, t) +} + +// object descends the type graph edge (op, index), records object +// obj, then proceeds to traverse its type. +func (tr *traversal) object(path []byte, offset uint32, op byte, index int, obj types.Object) { + if tr.ix == nil { + path = appendOpArg(path, op, index) + if obj == tr.target && tr.found == "" { + tr.found = Path(path) + } + path = append(path, opType) + } else { + offset = tr.ix.emitPathSegment(offset, op, index) + if _, ok := tr.ix.offsets[obj]; !ok { + tr.ix.offsets[obj] = offset + } + offset = tr.ix.emitPathSegment(offset, opType, -1) + } + tr.visitType(path, offset, obj.Type()) +} + +// emitPackageLevel encodes a record for a package-level symbol, +// identified by its index in ix.scopeNames. +func (p *pkgIndex) emitPackageLevel(index int) uint32 { + off := uint32(len(p.data)) + p.data = append(p.data, 0) // zero varint => no parent + p.data = binary.AppendUvarint(p.data, uint64(index)) + return off +} + +// emitPathSegment emits a record for a non-initial object path segment. +func (p *pkgIndex) emitPathSegment(parent uint32, op byte, index int) uint32 { + off := uint32(len(p.data)) + p.data = binary.AppendUvarint(p.data, uint64(parent)) + p.data = append(p.data, op) + switch op { + case opAt, opField, opMethod, opTypeParam, opRecvTypeParam: + p.data = binary.AppendUvarint(p.data, uint64(index)) + } + return off +} + +// path returns the Path for the encoded node at the specified offset. +func (p *pkgIndex) path(offset uint32) Path { + var elems []string // path elements in reverse + for { + // Read parent index. + parent, n := binary.Uvarint(p.data[offset:]) + offset += uint32(n) + + if parent == 0 { + break // root (end of path) + } + + op := p.data[offset] + offset++ + + // The [AFMTr] operators have a numeric operand. + switch op { + case opAt, opField, opMethod, opTypeParam, opRecvTypeParam: + val, n := binary.Uvarint(p.data[offset:]) + offset += uint32(n) + elems = append(elems, strconv.Itoa(int(val))) + } + + elems = append(elems, string([]byte{op})) + + offset = uint32(parent) + } + idx, _ := binary.Uvarint(p.data[offset:]) + + // Convert index to Path string. + name := p.scopeNames[idx] + sz := len(name) + for _, elem := range elems { + sz += len(elem) + } + var buf strings.Builder + buf.Grow(sz) + buf.WriteString(name) + for _, elem := range slices.Backward(elems) { + buf.WriteString(elem) + } + return Path(buf.String()) } -func appendOpArg(path []byte, op byte, arg int) []byte { +// appendOpArg appends (op, index) to the object path. +// A negative index is ignored. +func appendOpArg(path []byte, op byte, index int) []byte { path = append(path, op) - path = strconv.AppendInt(path, int64(arg), 10) + if index >= 0 { + path = strconv.AppendInt(path, int64(index), 10) + } return path } @@ -442,138 +723,6 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { // panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named))) } -// find finds obj within type T, returning the path to it, or nil if not found. -// -// The seen map is used to short circuit cycles through type parameters. If -// nil, it will be allocated as necessary. -// -// The seenMethods map is used internally to short circuit cycles through -// interface methods, such as occur in the following example: -// -// type I interface { f() interface{I} } -// -// See golang/go#68046 for details. -func find(obj types.Object, T types.Type, path []byte) []byte { - return (&finder{obj: obj}).find(T, path) -} - -// finder closes over search state for a call to find. -type finder struct { - obj types.Object // the sought object - seenTParamNames map[*types.TypeName]bool // for cycle breaking through type parameters - seenMethods map[*types.Func]bool // for cycle breaking through recursive interfaces -} - -func (f *finder) find(T types.Type, path []byte) []byte { - switch T := T.(type) { - case *types.Alias: - return f.find(types.Unalias(T), path) - case *types.Basic, *types.Named: - // Named types belonging to pkg were handled already, - // so T must belong to another package. No path. - return nil - case *types.Pointer: - return f.find(T.Elem(), append(path, opElem)) - case *types.Slice: - return f.find(T.Elem(), append(path, opElem)) - case *types.Array: - return f.find(T.Elem(), append(path, opElem)) - case *types.Chan: - return f.find(T.Elem(), append(path, opElem)) - case *types.Map: - if r := f.find(T.Key(), append(path, opKey)); r != nil { - return r - } - return f.find(T.Elem(), append(path, opElem)) - case *types.Signature: - if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil { - return r - } - if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil { - return r - } - if r := f.find(T.Params(), append(path, opParams)); r != nil { - return r - } - return f.find(T.Results(), append(path, opResults)) - case *types.Struct: - for i := 0; i < T.NumFields(); i++ { - fld := T.Field(i) - path2 := appendOpArg(path, opField, i) - if fld == f.obj { - return path2 // found field var - } - if r := f.find(fld.Type(), append(path2, opType)); r != nil { - return r - } - } - return nil - case *types.Tuple: - for i := 0; i < T.Len(); i++ { - v := T.At(i) - path2 := appendOpArg(path, opAt, i) - if v == f.obj { - return path2 // found param/result var - } - if r := f.find(v.Type(), append(path2, opType)); r != nil { - return r - } - } - return nil - case *types.Interface: - for i := 0; i < T.NumMethods(); i++ { - m := T.Method(i) - if f.seenMethods[m] { - continue // break cycles (see TestIssue70418) - } - path2 := appendOpArg(path, opMethod, i) - if m == f.obj { - return path2 // found interface method - } - if f.seenMethods == nil { - f.seenMethods = make(map[*types.Func]bool) - } - f.seenMethods[m] = true - if r := f.find(m.Type(), append(path2, opType)); r != nil { - return r - } - } - return nil - case *types.TypeParam: - name := T.Obj() - if f.seenTParamNames[name] { - return nil - } - if name == f.obj { - return append(path, opObj) - } - if f.seenTParamNames == nil { - f.seenTParamNames = make(map[*types.TypeName]bool) - } - f.seenTParamNames[name] = true - if r := f.find(T.Constraint(), append(path, opConstraint)); r != nil { - return r - } - return nil - } - panic(T) -} - -func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte) []byte { - return (&finder{obj: obj}).findTypeParam(list, path, op) -} - -func (f *finder) findTypeParam(list *types.TypeParamList, path []byte, op byte) []byte { - for i := 0; i < list.Len(); i++ { - tparam := list.At(i) - path2 := appendOpArg(path, op, i) - if r := f.find(tparam, path2); r != nil { - return r - } - } - return nil -} - // Object returns the object denoted by path p within the package pkg. func Object(pkg *types.Package, p Path) (types.Object, error) { pathstr := string(p) @@ -708,7 +857,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { } tparams := hasTypeParams.TypeParams() if n := tparams.Len(); index >= n { - return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + return nil, fmt.Errorf("type parameter index %d out of range [0-%d)", index, n) } t = tparams.At(index) @@ -719,7 +868,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { } rtparams := sig.RecvTypeParams() if n := rtparams.Len(); index >= n { - return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + return nil, fmt.Errorf("receiver type parameter index %d out of range [0-%d)", index, n) } t = rtparams.At(index) @@ -794,23 +943,3 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { return obj, nil // success } - -// scopeObjects is a memoization of scope objects. -// Callers must not modify the result. -func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object { - m := enc.scopeMemo - if m == nil { - m = make(map[*types.Scope][]types.Object) - enc.scopeMemo = m - } - objs, ok := m[scope] - if !ok { - names := scope.Names() // allocates and sorts - objs = make([]types.Object, len(names)) - for i, name := range names { - objs[i] = scope.Lookup(name) - } - m[scope] = objs - } - return objs -} diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go index 3db62b89089..5d3b7c867a3 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go @@ -11,6 +11,7 @@ import ( "go/token" "go/types" "sort" + "strings" "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/pkgbits" @@ -523,6 +524,12 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { return objPkg, objName } + // TODO(mark): This, like the above splitVargenSuffix, is not ideal. + // Ignore generic methods promoted to global scope. + if strings.Contains(objName, ".") { + return objPkg, objName + } + if objPkg.Scope().Lookup(objName) == nil { dict := pr.objDictIdx(idx) @@ -554,15 +561,11 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { case pkgbits.ObjFunc: pos := r.pos() - var rtparams []*types.TypeParam - var recv *types.Var - if r.Version().Has(pkgbits.GenericMethods) && r.Bool() { - r.selector() - rtparams = r.typeParamNames(true) - recv = r.param() + if r.Version().Has(pkgbits.GenericMethods) { + assert(!r.Bool()) // generic methods are read in their defining type } tparams := r.typeParamNames(false) - sig := r.signature(recv, rtparams, tparams) + sig := r.signature(nil, nil, tparams) declare(types.NewFunc(pos, objPkg, objName, sig)) case pkgbits.ObjType: @@ -630,6 +633,29 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { named.AddMethod(r.method()) } + if r.Version().Has(pkgbits.GenericMethods) { + for range r.Len() { + // Careful: objIdx is used to read in package-scoped declarations, which + // methods are not. Instead, decode it here. This makes it easier to + // associate it with the type and avoids the main objIdx loop. + idx := r.Reloc(pkgbits.RelocObj) + + r := pr.tempReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1) + r.dict = pr.objDictIdx(idx) + + pos := r.pos() + assert(r.Bool()) // generic method + pkg, name := r.selector() + rtparams := r.typeParamNames(true) + recv := r.param() + tparams := r.typeParamNames(false) + sig := r.signature(recv, rtparams, tparams) + + pr.retireReader(r) + named.AddMethod(types.NewFunc(pos, pkg, name, sig)) + } + } + case pkgbits.ObjVar: pos := r.pos() typ := r.typ() @@ -653,7 +679,7 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { } nreceivers := 0 - if r.Version().Has(pkgbits.GenericMethods) && r.Bool() { + if r.Version().Has(pkgbits.GenericMethods) { nreceivers = r.Len() } nexplicits := r.Len() diff --git a/vendor/golang.org/x/tools/internal/gocommand/version.go b/vendor/golang.org/x/tools/internal/gocommand/version.go index cce290c4194..d82f13a7e60 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/version.go +++ b/vendor/golang.org/x/tools/internal/gocommand/version.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "regexp" + "slices" "strings" ) @@ -41,9 +42,9 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { } // Split up "[go1.1 go1.15]" and return highest go1.X value. tags := strings.Fields(stdout[1 : len(stdout)-2]) - for i := len(tags) - 1; i >= 0; i-- { + for _, tag := range slices.Backward(tags) { var version int - if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil { + if _, err := fmt.Sscanf(tag, "go1.%d", &version); err != nil { continue } return version, nil diff --git a/vendor/modules.txt b/vendor/modules.txt index b5d027d0968..2f28fcaa05c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -402,9 +402,11 @@ golang.org/x/crypto/cryptobyte golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/ed25519 golang.org/x/crypto/ocsp -# golang.org/x/mod v0.35.0 +# golang.org/x/mod v0.37.0 ## explicit; go 1.25.0 golang.org/x/mod/semver +golang.org/x/mod/sumdb/note +golang.org/x/mod/sumdb/tlog # golang.org/x/net v0.55.0 ## explicit; go 1.25.0 golang.org/x/net/bpf @@ -437,7 +439,7 @@ golang.org/x/text/unicode/norm # golang.org/x/time v0.15.0 ## explicit; go 1.25.0 golang.org/x/time/rate -# golang.org/x/tools v0.44.0 +# golang.org/x/tools v0.45.0 ## explicit; go 1.25.0 golang.org/x/tools/go/ast/edge golang.org/x/tools/go/ast/inspector From db344fc7bfed36148eab727aab6485d0806d5811 Mon Sep 17 00:00:00 2001 From: Samantha Date: Wed, 1 Jul 2026 17:53:48 -0400 Subject: [PATCH 2/2] Two additional test cases --- trees/checkpoint/checkpoint_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trees/checkpoint/checkpoint_test.go b/trees/checkpoint/checkpoint_test.go index 176b9207488..3395a6706b2 100644 --- a/trees/checkpoint/checkpoint_test.go +++ b/trees/checkpoint/checkpoint_test.go @@ -85,6 +85,8 @@ func TestParseCheckpointRejects(t *testing.T) { {"Leading zero size", "example.com/log\n01\n" + exampleHashB64 + "\n"}, {"Negative size", "example.com/log\n-1\n" + exampleHashB64 + "\n"}, {"Non-numeric size", "example.com/log\nx\n" + exampleHashB64 + "\n"}, + {"Size above int64", "example.com/log\n9223372036854775808\n" + exampleHashB64 + "\n"}, + {"Signed note instead of a bare body", "example.com/log\n1\n" + exampleHashB64 + "\n\n— key AAAA\n"}, {"Bad base64 hash", "example.com/log\n1\n!!!notbase64!!!\n"}, {"Non-canonical base64 hash", "example.com/log\n1\nCsUYapGGPo4dkMgIAUqom/Xajj7h2fB2MPA3j2jxq2J=\n"}, {"Short hash", "example.com/log\n1\nAAAA\n"},