├── .env ├── Makefile ├── docker-compose.yml ├── Dockerfile ├── .gitignore ├── LICENSE ├── pkg ├── feeds │ └── static │ │ └── feed.go ├── feedrouter │ └── feedrouter.go ├── gin │ └── endpoints.go └── auth │ └── auth.go ├── README.md ├── go.mod ├── cmd └── main.go └── go.sum /.env: -------------------------------------------------------------------------------- 1 | PORT=9032 2 | FEED_ACTOR_DID=did:plc:replace-me-with-your-did 3 | SERVICE_ENDPOINT=https://replace-me-with-your-service-endpoint.example.com 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Variables 2 | ENV_FILE = .env 3 | GO_CMD = CGO_ENABLED=1 GOOS=linux go 4 | 5 | # Build the Feedgen Go binary 6 | build: 7 | @echo "Building Feed Generator Go binary..." 8 | $(GO_CMD) build -o feedgen cmd/*.go 9 | 10 | up: 11 | @echo "Starting Go Feed Generator..." 12 | docker compose -f docker-compose.yml up --build -d 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | go-bsky-feed-generator: 5 | build: 6 | dockerfile: Dockerfile 7 | extra_hosts: 8 | - "host.docker.internal:host-gateway" 9 | image: go-bsky-feed-generator 10 | container_name: go-bsky-feed-generator 11 | environment: 12 | - GIN_MODE=release 13 | env_file: 14 | - .env 15 | ports: 16 | - "9032:9032" 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22 as builder 2 | 3 | WORKDIR /app 4 | 5 | COPY go.mod go.sum ./ 6 | 7 | RUN go mod download 8 | 9 | COPY pkg/ pkg/ 10 | 11 | COPY cmd/ cmd/ 12 | 13 | COPY Makefile Makefile 14 | 15 | RUN make build 16 | 17 | FROM alpine:latest as certs 18 | 19 | RUN apk --update add ca-certificates 20 | 21 | FROM debian:stable-slim 22 | 23 | COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 24 | 25 | COPY --from=builder /app/feedgen . 26 | 27 | CMD ["./feedgen"] 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | .vscode/ 12 | .env 13 | 14 | # Feed Generator Binary 15 | feedgen 16 | 17 | # Test binary, built with `go test -c` 18 | *.test 19 | 20 | # Output of the go coverage tool, specifically when used with LiteIDE 21 | *.out 22 | 23 | # Dependency directories (remove the comment below to include it) 24 | # vendor/ 25 | 26 | # Go workspace file 27 | go.work 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jaz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/feeds/static/feed.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strconv" 7 | 8 | appbsky "github.com/bluesky-social/indigo/api/bsky" 9 | ) 10 | 11 | type StaticFeed struct { 12 | FeedActorDID string 13 | FeedName string 14 | StaticPostURIs []string 15 | } 16 | 17 | // NewStaticFeed returns a new StaticFeed, a list of aliases for the feed, and an error 18 | // StaticFeed is a trivial implementation of the Feed interface, so its aliases are just the input feedName 19 | func NewStaticFeed(ctx context.Context, feedActorDID string, feedName string, staticPostURIs []string) (*StaticFeed, []string, error) { 20 | return &StaticFeed{ 21 | FeedActorDID: feedActorDID, 22 | FeedName: feedName, 23 | StaticPostURIs: staticPostURIs, 24 | }, []string{feedName}, nil 25 | } 26 | 27 | // GetPage returns a list of FeedDefs_SkeletonFeedPost, a new cursor, and an error 28 | // It takes a feed name, a user DID, a limit, and a cursor 29 | // The feed name can be used to produce different feeds from the same feed generator 30 | func (sf *StaticFeed) GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) ([]*appbsky.FeedDefs_SkeletonFeedPost, *string, error) { 31 | cursorAsInt := int64(0) 32 | var err error 33 | 34 | if cursor != "" { 35 | cursorAsInt, err = strconv.ParseInt(cursor, 10, 64) 36 | if err != nil { 37 | return nil, nil, fmt.Errorf("cursor is not an integer: %w", err) 38 | } 39 | } 40 | 41 | posts := []*appbsky.FeedDefs_SkeletonFeedPost{} 42 | 43 | for i, postURI := range sf.StaticPostURIs { 44 | if int64(i) < cursorAsInt { 45 | continue 46 | } 47 | 48 | if int64(len(posts)) >= limit { 49 | break 50 | } 51 | 52 | posts = append(posts, &appbsky.FeedDefs_SkeletonFeedPost{ 53 | Post: postURI, 54 | }) 55 | } 56 | 57 | cursorAsInt += int64(len(posts)) 58 | 59 | var newCursor *string 60 | 61 | if cursorAsInt < int64(len(sf.StaticPostURIs)) { 62 | newCursor = new(string) 63 | *newCursor = strconv.FormatInt(cursorAsInt, 10) 64 | } 65 | 66 | return posts, newCursor, nil 67 | } 68 | 69 | // Describe returns a list of FeedDescribeFeedGenerator_Feed, and an error 70 | // StaticFeed is a trivial implementation of the Feed interface, so it returns a single FeedDescribeFeedGenerator_Feed 71 | // For a more complicated feed, this function would return a list of FeedDescribeFeedGenerator_Feed with the URIs of aliases 72 | // supported by the feed 73 | func (sf *StaticFeed) Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error) { 74 | return []appbsky.FeedDescribeFeedGenerator_Feed{ 75 | { 76 | Uri: "at://" + sf.FeedActorDID + "/app.bsky.feed.generator/" + sf.FeedName, 77 | }, 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/feedrouter/feedrouter.go: -------------------------------------------------------------------------------- 1 | // Package feedrouter describes the FeedRouter type, which is responsible for generating feeds for a given DID. 2 | // It also describes the Feed interface, which is implemented by the various feed types. 3 | package feedrouter 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | appbsky "github.com/bluesky-social/indigo/api/bsky" 10 | did "github.com/whyrusleeping/go-did" 11 | ) 12 | 13 | type Feed interface { 14 | GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) (feedPosts []*appbsky.FeedDefs_SkeletonFeedPost, newCursor *string, err error) 15 | Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error) 16 | } 17 | 18 | type FeedRouter struct { 19 | FeedActorDID did.DID // DID of the Repo the Feed is published under 20 | ServiceEndpoint string // URL of the FeedRouter service 21 | ServiceDID did.DID // DID of the FeedRouter service 22 | DIDDocument did.Document // DID Document of the FeedRouter service 23 | AcceptableURIPrefixes []string // URIs that the FeedRouter is allowed to generate feeds for 24 | FeedMap map[string]Feed // map of FeedName to Feed 25 | Feeds []Feed 26 | } 27 | 28 | type NotFoundError struct { 29 | error 30 | } 31 | 32 | // NewFeedRouter returns a new FeedRouter 33 | func NewFeedRouter( 34 | ctx context.Context, 35 | feedActorDIDString string, 36 | serviceDIDString string, 37 | acceptableDIDs []string, 38 | serviceEndpoint string, 39 | ) (*FeedRouter, error) { 40 | acceptableURIPrefixes := []string{} 41 | for _, did := range acceptableDIDs { 42 | acceptableURIPrefixes = append(acceptableURIPrefixes, "at://"+did+"/app.bsky.feed.generator/") 43 | } 44 | 45 | serviceDID, err := did.ParseDID(serviceDIDString) 46 | if err != nil { 47 | return nil, fmt.Errorf("error parsing serviceDID: %w", err) 48 | } 49 | 50 | feedActorDID, err := did.ParseDID(feedActorDIDString) 51 | if err != nil { 52 | return nil, fmt.Errorf("error parsing feedActorDID: %w", err) 53 | } 54 | 55 | serviceID, err := did.ParseDID("#bsky_fg") 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | doc := did.Document{ 61 | Context: []string{did.CtxDIDv1}, 62 | ID: serviceDID, 63 | Service: []did.Service{ 64 | { 65 | ID: serviceID, 66 | Type: "BskyFeedGenerator", 67 | ServiceEndpoint: serviceEndpoint, 68 | }, 69 | }, 70 | } 71 | 72 | return &FeedRouter{ 73 | FeedMap: map[string]Feed{}, 74 | FeedActorDID: feedActorDID, 75 | ServiceDID: serviceDID, 76 | DIDDocument: doc, 77 | AcceptableURIPrefixes: acceptableURIPrefixes, 78 | ServiceEndpoint: serviceEndpoint, 79 | }, nil 80 | } 81 | 82 | // AddFeed adds a feed to the FeedRouter 83 | // Feed precedence for overlapping aliases is determined by the order in which 84 | // they are added (first added is highest precedence) 85 | func (fg *FeedRouter) AddFeed(feedAliases []string, feed Feed) { 86 | if fg.FeedMap == nil { 87 | fg.FeedMap = map[string]Feed{} 88 | } 89 | 90 | for _, feedAlias := range feedAliases { 91 | // Skip the feed if we already have the alias registered so we don't add it twice 92 | // Feed precedence is determined by the order in which they are added 93 | if _, ok := fg.FeedMap[feedAlias]; ok { 94 | continue 95 | } 96 | 97 | fg.FeedMap[feedAlias] = feed 98 | } 99 | 100 | fg.Feeds = append(fg.Feeds, feed) 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-bsky-feed-generator 2 | A minimal implementation of a BlueSky Feed Generator in Go 3 | 4 | 5 | ## Requirements 6 | 7 | To run this feed generator, all you need is `docker` with `docker-compose`. 8 | 9 | ## Running 10 | 11 | Start up the feed generator by running: `make up` 12 | 13 | This will build the feed generator service binary inside a docker container and stand up the service on your machine at port `9032`. 14 | 15 | To view a sample static feed (with only one post) go to: 16 | 17 | - [`http://localhost:9032/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:replace-me-with-your-did/app.bsky.feed.generator/static`](http://localhost:9032/xrpc/app.bsky.feed.getFeedSkeleton?feed=at://did:plc:replace-me-with-your-did/app.bsky.feed.generator/static) 18 | 19 | Update the variables in `.env` when you actually want to deploy the service somewhere, at which point `did:plc:replace-me-with-your-did` should be replaced with the value of `FEED_ACTOR_DID`. 20 | 21 | ## Accessing 22 | 23 | This service exposes the following routes: 24 | 25 | - `/.well-known/did.json` 26 | - This route is used by ATProto to verify ownership of the DID the service is claiming, it's a static JSON document. 27 | - You can see how this is generated in `pkg/gin/endpoints.go:GetWellKnownDID()` 28 | - `/xrpc/app.bsky.feed.getFeedSkeleton` 29 | - This route is what clients call to generate a feed page, it includes three query parameters for feed generation: `feed`, `cursor`, and `limit` 30 | - You can see how those are parsed and handled in `pkg/gin/endpoints.go:GetFeedSkeleton()` 31 | - `/xrpc/app.bsky.feed.describeFeedGenerator` 32 | - This route is how the service advertises which feeds it supports to clients. 33 | - You can see how those are parsed and handled in `pkg/gin/endpoints.go:DescribeFeeds()` 34 | 35 | ## Publishing 36 | 37 | Once you've got your feed generator up and running and have it exposed to the internet, you can publish the feed using the script from the official BSky repo [here](https://github.com/bluesky-social/feed-generator/blob/main/scripts/publishFeedGen.ts). 38 | 39 | Your feed will be published under _your_ DID and should show up in your profile under the `feeds` tab. 40 | 41 | ## Architecture 42 | 43 | This repo is structured to abstract away a `Feed` interface that allows for you to add all sorts of feeds to the router. 44 | 45 | These feeds can be simple static feeds like the `pkg/feeds/static/feed.go` implementation, or they can be much more complex feeds that draw on different data sources and filter them in cool ways to produce pages of feed items. 46 | 47 | The `Feed` interface is defined by any struct implementing two functions: 48 | 49 | ``` go 50 | type Feed interface { 51 | GetPage(ctx context.Context, feed string, userDID string, limit int64, cursor string) (feedPosts []*appbsky.FeedDefs_SkeletonFeedPost, newCursor *string, err error) 52 | Describe(ctx context.Context) ([]appbsky.FeedDescribeFeedGenerator_Feed, error) 53 | } 54 | ``` 55 | 56 | `GetPage` gets a page of a feed for a given user with the limit and cursor provided, this is the main function that serves posts to a user. 57 | 58 | `Describe` is used by the router to advertise what feeds are available, for foward compatibility, `Feed`s should be self describing in case this endpoint allows more details about feeds to be provided. 59 | 60 | You can configure external resources and requirements in your Feed implementation before `Adding` the feed to the `FeedRouter` with `feedRouter.AddFeed([]string{"{feed_name}"}, feedInstance)` 61 | 62 | This `Feed` interface is somewhat flexible right now but it could be better. I'm not sure if it will change in the future so keep that in mind when using this template. 63 | 64 | - This has since been updated to allow a Feed to take in a feed name when generating a page and register multiple aliases for feeds that are supported. 65 | -------------------------------------------------------------------------------- /pkg/gin/endpoints.go: -------------------------------------------------------------------------------- 1 | package gin 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | "strings" 8 | 9 | appbsky "github.com/bluesky-social/indigo/api/bsky" 10 | "github.com/ericvolp12/go-bsky-feed-generator/pkg/feedrouter" 11 | "github.com/gin-gonic/gin" 12 | "github.com/whyrusleeping/go-did" 13 | "go.opentelemetry.io/otel" 14 | "go.opentelemetry.io/otel/attribute" 15 | ) 16 | 17 | type Endpoints struct { 18 | FeedRouter *feedrouter.FeedRouter 19 | } 20 | 21 | type DidResponse struct { 22 | Context []string `json:"@context"` 23 | ID string `json:"id"` 24 | Service []did.Service `json:"service"` 25 | } 26 | 27 | func NewEndpoints(feedRouter *feedrouter.FeedRouter) *Endpoints { 28 | return &Endpoints{ 29 | FeedRouter: feedRouter, 30 | } 31 | } 32 | 33 | func (ep *Endpoints) GetWellKnownDID(c *gin.Context) { 34 | tracer := otel.Tracer("feedrouter") 35 | _, span := tracer.Start(c.Request.Context(), "GetWellKnownDID") 36 | defer span.End() 37 | 38 | // Use a custom struct to fix missing omitempty on did.Document 39 | didResponse := DidResponse{ 40 | Context: ep.FeedRouter.DIDDocument.Context, 41 | ID: ep.FeedRouter.DIDDocument.ID.String(), 42 | Service: ep.FeedRouter.DIDDocument.Service, 43 | } 44 | 45 | c.JSON(http.StatusOK, didResponse) 46 | } 47 | 48 | func (ep *Endpoints) DescribeFeeds(c *gin.Context) { 49 | tracer := otel.Tracer("feedrouter") 50 | ctx, span := tracer.Start(c.Request.Context(), "DescribeFeeds") 51 | defer span.End() 52 | 53 | feedDescriptions := []*appbsky.FeedDescribeFeedGenerator_Feed{} 54 | 55 | for _, feed := range ep.FeedRouter.Feeds { 56 | newDescriptions, err := feed.Describe(ctx) 57 | if err != nil { 58 | span.RecordError(err) 59 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 60 | return 61 | } 62 | 63 | for _, newDescription := range newDescriptions { 64 | description := newDescription 65 | feedDescriptions = append(feedDescriptions, &description) 66 | } 67 | } 68 | 69 | span.SetAttributes(attribute.Int("feeds.length", len(feedDescriptions))) 70 | 71 | feedGeneratorDescription := appbsky.FeedDescribeFeedGenerator_Output{ 72 | Did: ep.FeedRouter.FeedActorDID.String(), 73 | Feeds: feedDescriptions, 74 | } 75 | 76 | c.JSON(http.StatusOK, feedGeneratorDescription) 77 | } 78 | 79 | func (ep *Endpoints) GetFeedSkeleton(c *gin.Context) { 80 | // Incoming requests should have a query parameter "feed" that looks like: 81 | // at://did:web:feedsky.jazco.io/app.bsky.feed.generator/feed-name 82 | // Also a query parameter "limit" that looks like: 50 83 | // Also a query parameter "cursor" that is either the empty string 84 | // or the cursor returned from a previous request 85 | tracer := otel.Tracer("feed-generator") 86 | ctx, span := tracer.Start(c.Request.Context(), "FeedGenerator:GetFeedSkeleton") 87 | defer span.End() 88 | 89 | // Get userDID from the request context, which is set by the auth middleware 90 | userDID := c.GetString("user_did") 91 | 92 | feedQuery := c.Query("feed") 93 | if feedQuery == "" { 94 | c.JSON(http.StatusBadRequest, gin.H{"error": "feed query parameter is required"}) 95 | return 96 | } 97 | 98 | c.Set("feedQuery", feedQuery) 99 | span.SetAttributes(attribute.String("feed.query", feedQuery)) 100 | 101 | feedPrefix := "" 102 | for _, acceptablePrefix := range ep.FeedRouter.AcceptableURIPrefixes { 103 | if strings.HasPrefix(feedQuery, acceptablePrefix) { 104 | feedPrefix = acceptablePrefix 105 | break 106 | } 107 | } 108 | 109 | if feedPrefix == "" { 110 | c.JSON(http.StatusBadRequest, gin.H{"error": "this feed generator does not serve feeds for the given DID"}) 111 | return 112 | } 113 | 114 | // Get the feed name from the query 115 | feedName := strings.TrimPrefix(feedQuery, feedPrefix) 116 | if feedName == "" { 117 | c.JSON(http.StatusBadRequest, gin.H{"error": "feed name is required"}) 118 | return 119 | } 120 | 121 | span.SetAttributes(attribute.String("feed.name", feedName)) 122 | c.Set("feedName", feedName) 123 | 124 | // Get the limit from the query, default to 50, maximum of 250 125 | limit := int64(50) 126 | limitQuery := c.Query("limit") 127 | span.SetAttributes(attribute.String("feed.limit.raw", limitQuery)) 128 | if limitQuery != "" { 129 | parsedLimit, err := strconv.ParseInt(limitQuery, 10, 64) 130 | if err != nil { 131 | span.SetAttributes(attribute.Bool("feed.limit.failed_to_parse", true)) 132 | limit = 50 133 | } else { 134 | limit = parsedLimit 135 | if limit > 250 { 136 | span.SetAttributes(attribute.Bool("feed.limit.clamped", true)) 137 | limit = 250 138 | } 139 | } 140 | } 141 | 142 | span.SetAttributes(attribute.Int64("feed.limit.parsed", limit)) 143 | 144 | // Get the cursor from the query 145 | cursor := c.Query("cursor") 146 | c.Set("cursor", cursor) 147 | 148 | if ep.FeedRouter.FeedMap == nil { 149 | c.JSON(http.StatusInternalServerError, gin.H{"error": "feed generator has no feeds configured"}) 150 | return 151 | } 152 | 153 | feed, ok := ep.FeedRouter.FeedMap[feedName] 154 | if !ok { 155 | c.JSON(http.StatusNotFound, gin.H{"error": "feed not found"}) 156 | return 157 | } 158 | 159 | // Get the feed items 160 | feedItems, newCursor, err := feed.GetPage(ctx, feedName, userDID, limit, cursor) 161 | if err != nil { 162 | span.RecordError(err) 163 | c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get feed items: %s", err.Error())}) 164 | return 165 | } 166 | 167 | span.SetAttributes(attribute.Int("feed.items.length", len(feedItems))) 168 | 169 | c.JSON(http.StatusOK, appbsky.FeedGetFeedSkeleton_Output{ 170 | Feed: feedItems, 171 | Cursor: newCursor, 172 | }) 173 | } 174 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ericvolp12/go-bsky-feed-generator 2 | 3 | go 1.22 4 | 5 | toolchain go1.22.2 6 | 7 | require ( 8 | github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1 9 | github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283 10 | github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 11 | github.com/gin-gonic/gin v1.9.1 12 | github.com/golang-jwt/jwt v3.2.2+incompatible 13 | github.com/hashicorp/golang-lru/arc/v2 v2.0.7 14 | github.com/prometheus/client_golang v1.17.0 15 | github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 16 | gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 17 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 18 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 19 | go.opentelemetry.io/otel v1.21.0 20 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 21 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 22 | go.opentelemetry.io/otel/sdk v1.21.0 23 | golang.org/x/time v0.3.0 24 | ) 25 | 26 | require ( 27 | github.com/beorn7/perks v1.0.1 // indirect 28 | github.com/bytedance/sonic v1.9.1 // indirect 29 | github.com/carlmjohnson/versioninfo v0.22.5 // indirect 30 | github.com/cenkalti/backoff/v4 v4.2.1 // indirect 31 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 32 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 33 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 34 | github.com/felixge/httpsnoop v1.0.4 // indirect 35 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 36 | github.com/gin-contrib/sse v0.1.0 // indirect 37 | github.com/go-logr/logr v1.4.1 // indirect 38 | github.com/go-logr/stdr v1.2.2 // indirect 39 | github.com/go-playground/locales v0.14.1 // indirect 40 | github.com/go-playground/universal-translator v0.18.1 // indirect 41 | github.com/go-playground/validator/v10 v10.14.0 // indirect 42 | github.com/goccy/go-json v0.10.2 // indirect 43 | github.com/gogo/protobuf v1.3.2 // indirect 44 | github.com/golang/protobuf v1.5.3 // indirect 45 | github.com/google/uuid v1.4.0 // indirect 46 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect 47 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 48 | github.com/hashicorp/go-retryablehttp v0.7.5 // indirect 49 | github.com/hashicorp/golang-lru v1.0.2 // indirect 50 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 51 | github.com/ipfs/bbloom v0.0.4 // indirect 52 | github.com/ipfs/go-block-format v0.2.0 // indirect 53 | github.com/ipfs/go-cid v0.4.1 // indirect 54 | github.com/ipfs/go-datastore v0.6.0 // indirect 55 | github.com/ipfs/go-ipfs-blockstore v1.3.1 // indirect 56 | github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect 57 | github.com/ipfs/go-ipfs-util v0.0.3 // indirect 58 | github.com/ipfs/go-ipld-cbor v0.1.0 // indirect 59 | github.com/ipfs/go-ipld-format v0.6.0 // indirect 60 | github.com/ipfs/go-log v1.0.5 // indirect 61 | github.com/ipfs/go-log/v2 v2.5.1 // indirect 62 | github.com/ipfs/go-metrics-interface v0.0.1 // indirect 63 | github.com/jbenet/goprocess v0.1.4 // indirect 64 | github.com/json-iterator/go v1.1.12 // indirect 65 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 66 | github.com/leodido/go-urn v1.2.4 // indirect 67 | github.com/lestrrat-go/blackmagic v1.0.1 // indirect 68 | github.com/lestrrat-go/httpcc v1.0.1 // indirect 69 | github.com/lestrrat-go/httprc v1.0.4 // indirect 70 | github.com/lestrrat-go/iter v1.0.2 // indirect 71 | github.com/lestrrat-go/jwx/v2 v2.0.12 // indirect 72 | github.com/lestrrat-go/option v1.0.1 // indirect 73 | github.com/mattn/go-isatty v0.0.20 // indirect 74 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 75 | github.com/minio/sha256-simd v1.0.1 // indirect 76 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 77 | github.com/modern-go/reflect2 v1.0.2 // indirect 78 | github.com/mr-tron/base58 v1.2.0 // indirect 79 | github.com/multiformats/go-base32 v0.1.0 // indirect 80 | github.com/multiformats/go-base36 v0.2.0 // indirect 81 | github.com/multiformats/go-multibase v0.2.0 // indirect 82 | github.com/multiformats/go-multihash v0.2.3 // indirect 83 | github.com/multiformats/go-varint v0.0.7 // indirect 84 | github.com/opentracing/opentracing-go v1.2.0 // indirect 85 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 86 | github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect 87 | github.com/prometheus/client_model v0.5.0 // indirect 88 | github.com/prometheus/common v0.45.0 // indirect 89 | github.com/prometheus/procfs v0.12.0 // indirect 90 | github.com/segmentio/asm v1.2.0 // indirect 91 | github.com/sirupsen/logrus v1.9.2 // indirect 92 | github.com/spaolacci/murmur3 v1.1.0 // indirect 93 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 94 | github.com/ugorji/go/codec v1.2.11 // indirect 95 | github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 // indirect 96 | gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 97 | go.opentelemetry.io/otel/metric v1.21.0 // indirect 98 | go.opentelemetry.io/otel/trace v1.21.0 // indirect 99 | go.opentelemetry.io/proto/otlp v1.0.0 // indirect 100 | go.uber.org/atomic v1.11.0 // indirect 101 | go.uber.org/multierr v1.11.0 // indirect 102 | go.uber.org/zap v1.26.0 // indirect 103 | golang.org/x/arch v0.3.0 // indirect 104 | golang.org/x/crypto v0.21.0 // indirect 105 | golang.org/x/net v0.21.0 // indirect 106 | golang.org/x/sys v0.18.0 // indirect 107 | golang.org/x/text v0.14.0 // indirect 108 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 109 | google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect 110 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect 111 | google.golang.org/grpc v1.59.0 // indirect 112 | google.golang.org/protobuf v1.31.0 // indirect 113 | gopkg.in/yaml.v3 v3.0.1 // indirect 114 | lukechampine.com/blake3 v1.2.1 // indirect 115 | ) 116 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "time" 11 | 12 | auth "github.com/ericvolp12/go-bsky-feed-generator/pkg/auth" 13 | "github.com/ericvolp12/go-bsky-feed-generator/pkg/feedrouter" 14 | ginendpoints "github.com/ericvolp12/go-bsky-feed-generator/pkg/gin" 15 | 16 | staticfeed "github.com/ericvolp12/go-bsky-feed-generator/pkg/feeds/static" 17 | ginprometheus "github.com/ericvolp12/go-gin-prometheus" 18 | "github.com/gin-gonic/gin" 19 | "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" 20 | "go.opentelemetry.io/otel" 21 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace" 22 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" 23 | "go.opentelemetry.io/otel/sdk/resource" 24 | sdktrace "go.opentelemetry.io/otel/sdk/trace" 25 | semconv "go.opentelemetry.io/otel/semconv/v1.17.0" 26 | ) 27 | 28 | func main() { 29 | ctx := context.Background() 30 | 31 | // Configure feed generator from environment variables 32 | 33 | // Registers a tracer Provider globally if the exporter endpoint is set 34 | if os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") != "" { 35 | log.Println("initializing tracer...") 36 | shutdown, err := installExportPipeline(ctx) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | defer func() { 41 | if err := shutdown(ctx); err != nil { 42 | log.Fatal(err) 43 | } 44 | }() 45 | } 46 | 47 | feedActorDID := os.Getenv("FEED_ACTOR_DID") 48 | if feedActorDID == "" { 49 | log.Fatal("FEED_ACTOR_DID environment variable must be set") 50 | } 51 | 52 | // serviceEndpoint is a URL that the feed generator will be available at 53 | serviceEndpoint := os.Getenv("SERVICE_ENDPOINT") 54 | if serviceEndpoint == "" { 55 | log.Fatal("SERVICE_ENDPOINT environment variable must be set") 56 | } 57 | 58 | // Set the acceptable DIDs for the feed generator to respond to 59 | // We'll default to the feedActorDID and the Service Endpoint as a did:web 60 | serviceURL, err := url.Parse(serviceEndpoint) 61 | if err != nil { 62 | log.Fatal(fmt.Errorf("error parsing service endpoint: %w", err)) 63 | } 64 | 65 | serviceWebDID := "did:web:" + serviceURL.Hostname() 66 | 67 | log.Printf("service DID Web: %s", serviceWebDID) 68 | 69 | acceptableDIDs := []string{feedActorDID, serviceWebDID} 70 | 71 | // Create a new feed router instance 72 | feedRouter, err := feedrouter.NewFeedRouter(ctx, feedActorDID, serviceWebDID, acceptableDIDs, serviceEndpoint) 73 | if err != nil { 74 | log.Fatal(fmt.Errorf("error creating feed router: %w", err)) 75 | } 76 | 77 | // Here we can add feeds to the Feed Router instance 78 | // Feeds conform to the Feed interface, which is defined in 79 | // pkg/feedrouter/feedrouter.go 80 | 81 | // For demonstration purposes, we'll use a static feed generator 82 | // that will always return the same feed skeleton (one post) 83 | staticFeed, staticFeedAliases, err := staticfeed.NewStaticFeed( 84 | ctx, 85 | feedActorDID, 86 | "static", 87 | // This static post is the conversation that sparked this demo repo 88 | []string{"at://did:plc:q6gjnaw2blty4crticxkmujt/app.bsky.feed.post/3jx7msc4ive26"}, 89 | ) 90 | 91 | // Add the static feed to the feed generator 92 | feedRouter.AddFeed(staticFeedAliases, staticFeed) 93 | 94 | // Create a gin router with default middleware for logging and recovery 95 | router := gin.Default() 96 | 97 | // Plug in OTEL Middleware and skip metrics endpoint 98 | router.Use( 99 | otelgin.Middleware( 100 | "go-bsky-feed-generator", 101 | otelgin.WithFilter(func(req *http.Request) bool { 102 | return req.URL.Path != "/metrics" 103 | }), 104 | ), 105 | ) 106 | 107 | // Add Prometheus metrics middleware 108 | p := ginprometheus.NewPrometheus("gin", nil) 109 | p.Use(router) 110 | 111 | // Add unauthenticated routes for feed generator 112 | ep := ginendpoints.NewEndpoints(feedRouter) 113 | router.GET("/.well-known/did.json", ep.GetWellKnownDID) 114 | router.GET("/xrpc/app.bsky.feed.describeFeedGenerator", ep.DescribeFeeds) 115 | 116 | // Plug in Authentication Middleware 117 | auther, err := auth.NewAuth( 118 | 100_000, 119 | time.Hour*12, 120 | 5, 121 | serviceWebDID, 122 | ) 123 | if err != nil { 124 | log.Fatalf("Failed to create Auth: %v", err) 125 | } 126 | 127 | router.Use(auther.AuthenticateGinRequestViaJWT) 128 | 129 | // Add authenticated routes for feed generator 130 | router.GET("/xrpc/app.bsky.feed.getFeedSkeleton", ep.GetFeedSkeleton) 131 | 132 | port := os.Getenv("PORT") 133 | if port == "" { 134 | port = "8080" 135 | } 136 | 137 | log.Printf("Starting server on port %s", port) 138 | router.Run(fmt.Sprintf(":%s", port)) 139 | } 140 | 141 | // installExportPipeline registers a trace provider instance as a global trace provider, 142 | func installExportPipeline(ctx context.Context) (func(context.Context) error, error) { 143 | client := otlptracehttp.NewClient() 144 | exporter, err := otlptrace.New(ctx, client) 145 | if err != nil { 146 | return nil, fmt.Errorf("creating OTLP trace exporter: %w", err) 147 | } 148 | 149 | tracerProvider := newTraceProvider(exporter) 150 | otel.SetTracerProvider(tracerProvider) 151 | 152 | return tracerProvider.Shutdown, nil 153 | } 154 | 155 | // newTraceProvider creates a new trace provider instance. 156 | func newTraceProvider(exp sdktrace.SpanExporter) *sdktrace.TracerProvider { 157 | // Ensure default SDK resources and the required service name are set. 158 | r, err := resource.Merge( 159 | resource.Default(), 160 | resource.NewWithAttributes( 161 | semconv.SchemaURL, 162 | semconv.ServiceName("go-bsky-feed-generator"), 163 | ), 164 | ) 165 | 166 | if err != nil { 167 | panic(err) 168 | } 169 | 170 | // initialize the traceIDRatioBasedSampler to sample all traces 171 | traceIDRatioBasedSampler := sdktrace.TraceIDRatioBased(1) 172 | 173 | return sdktrace.NewTracerProvider( 174 | sdktrace.WithSampler(traceIDRatioBasedSampler), 175 | sdktrace.WithBatcher(exp), 176 | sdktrace.WithResource(r), 177 | ) 178 | } 179 | -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/bluesky-social/indigo/atproto/identity" 11 | "github.com/bluesky-social/indigo/atproto/syntax" 12 | es256k "github.com/ericvolp12/jwt-go-secp256k1" 13 | "github.com/gin-gonic/gin" 14 | "github.com/golang-jwt/jwt" 15 | lru "github.com/hashicorp/golang-lru/arc/v2" 16 | "github.com/prometheus/client_golang/prometheus" 17 | "github.com/prometheus/client_golang/prometheus/promauto" 18 | "gitlab.com/yawning/secp256k1-voi/secec" 19 | "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 20 | "go.opentelemetry.io/otel" 21 | "go.opentelemetry.io/otel/attribute" 22 | "golang.org/x/time/rate" 23 | ) 24 | 25 | type KeyCacheEntry struct { 26 | UserDID string 27 | Key any 28 | ExpiresAt time.Time 29 | } 30 | 31 | // Initialize Prometheus Metrics for cache hits and misses 32 | var cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{ 33 | Name: "feedgen_auth_cache_hits_total", 34 | Help: "The total number of cache hits", 35 | }, []string{"cache_type"}) 36 | 37 | var cacheMisses = promauto.NewCounterVec(prometheus.CounterOpts{ 38 | Name: "feedgen_auth_cache_misses_total", 39 | Help: "The total number of cache misses", 40 | }, []string{"cache_type"}) 41 | 42 | var cacheSize = promauto.NewGaugeVec(prometheus.GaugeOpts{ 43 | Name: "feedgen_auth_cache_size_bytes", 44 | Help: "The size of the cache in bytes", 45 | }, []string{"cache_type"}) 46 | 47 | type Auth struct { 48 | KeyCache *lru.ARCCache[string, KeyCacheEntry] 49 | KeyCacheTTL time.Duration 50 | ServiceDID string 51 | Dir *identity.CacheDirectory 52 | } 53 | 54 | // NewAuth creates a new Auth instance with the given key cache size and TTL 55 | // The PLC Directory URL is also required, as well as the DID of the service 56 | // for JWT audience validation 57 | // The key cache is used to cache the public keys of users for a given TTL 58 | // The PLC Directory URL is used to fetch the public keys of users 59 | // The service DID is used to validate the audience of JWTs 60 | // The HTTP client is used to make requests to the PLC Directory 61 | // A rate limiter is used to limit the number of requests to the PLC Directory 62 | func NewAuth( 63 | keyCacheSize int, 64 | keyCacheTTL time.Duration, 65 | requestsPerSecond int, 66 | serviceDID string, 67 | ) (*Auth, error) { 68 | keyCache, err := lru.NewARC[string, KeyCacheEntry](keyCacheSize) 69 | if err != nil { 70 | return nil, fmt.Errorf("Failed to create key cache: %v", err) 71 | } 72 | 73 | // Initialize the HTTP client with OpenTelemetry instrumentation 74 | client := http.Client{ 75 | Transport: otelhttp.NewTransport(http.DefaultTransport), 76 | } 77 | 78 | baseDir := identity.BaseDirectory{ 79 | PLCURL: identity.DefaultPLCURL, 80 | PLCLimiter: rate.NewLimiter(rate.Limit(float64(requestsPerSecond)), 1), 81 | HTTPClient: client, 82 | TryAuthoritativeDNS: true, 83 | // primary Bluesky PDS instance only supports HTTP resolution method 84 | SkipDNSDomainSuffixes: []string{".bsky.social"}, 85 | } 86 | dir := identity.NewCacheDirectory(&baseDir, keyCacheSize, keyCacheTTL, time.Minute*2, keyCacheTTL) 87 | 88 | return &Auth{ 89 | KeyCache: keyCache, 90 | KeyCacheTTL: keyCacheTTL, 91 | ServiceDID: serviceDID, 92 | Dir: &dir, 93 | }, nil 94 | } 95 | 96 | func (auth *Auth) GetClaimsFromAuthHeader(ctx context.Context, authHeader string, claims jwt.Claims) error { 97 | tracer := otel.Tracer("auth") 98 | ctx, span := tracer.Start(ctx, "Auth:GetClaimsFromAuthHeader") 99 | defer span.End() 100 | 101 | if authHeader == "" { 102 | span.End() 103 | return fmt.Errorf("No Authorization header provided") 104 | } 105 | 106 | authHeaderParts := strings.Split(authHeader, " ") 107 | if len(authHeaderParts) != 2 { 108 | return fmt.Errorf("Invalid Authorization header") 109 | } 110 | 111 | if authHeaderParts[0] != "Bearer" { 112 | return fmt.Errorf("Invalid Authorization header (expected Bearer)") 113 | } 114 | 115 | accessToken := authHeaderParts[1] 116 | 117 | parser := jwt.Parser{ 118 | ValidMethods: []string{es256k.SigningMethodES256K.Alg()}, 119 | } 120 | 121 | token, err := parser.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) { 122 | if claims, ok := token.Claims.(*jwt.StandardClaims); ok { 123 | // Get the user's key from PLC Directory 124 | userDID := claims.Issuer 125 | entry, ok := auth.KeyCache.Get(userDID) 126 | if ok && entry.ExpiresAt.After(time.Now()) { 127 | cacheHits.WithLabelValues("key").Inc() 128 | span.SetAttributes(attribute.Bool("caches.keys.hit", true)) 129 | return entry.Key, nil 130 | } 131 | 132 | cacheMisses.WithLabelValues("key").Inc() 133 | span.SetAttributes(attribute.Bool("caches.keys.hit", false)) 134 | 135 | did, err := syntax.ParseDID(userDID) 136 | if err != nil { 137 | return nil, fmt.Errorf("Failed to parse user DID: %v", err) 138 | } 139 | 140 | // Get the user's key from PLC Directory 141 | id, err := auth.Dir.LookupDID(ctx, did) 142 | if err != nil { 143 | return nil, fmt.Errorf("Failed to lookup user DID: %v", err) 144 | } 145 | 146 | key, err := id.GetPublicKey("atproto") 147 | if err != nil { 148 | return nil, fmt.Errorf("Failed to get user public key: %v", err) 149 | } 150 | 151 | parsedPubkey, err := secec.NewPublicKey(key.UncompressedBytes()) 152 | if err != nil { 153 | return nil, fmt.Errorf("Failed to parse user public key: %v", err) 154 | } 155 | 156 | // Add the ECDSA key to the cache 157 | auth.KeyCache.Add(userDID, KeyCacheEntry{ 158 | Key: parsedPubkey, 159 | ExpiresAt: time.Now().Add(auth.KeyCacheTTL), 160 | }) 161 | 162 | return parsedPubkey, nil 163 | } 164 | 165 | return nil, fmt.Errorf("Invalid authorization token (failed to parse claims)") 166 | }) 167 | 168 | if err != nil { 169 | return fmt.Errorf("Failed to parse authorization token: %v", err) 170 | } 171 | 172 | if !token.Valid { 173 | return fmt.Errorf("Invalid authorization token") 174 | } 175 | 176 | return nil 177 | } 178 | 179 | func (auth *Auth) AuthenticateGinRequestViaJWT(c *gin.Context) { 180 | tracer := otel.Tracer("auth") 181 | ctx, span := tracer.Start(c.Request.Context(), "Auth:AuthenticateGinRequestViaJWT") 182 | 183 | authHeader := c.GetHeader("Authorization") 184 | if authHeader == "" { 185 | span.End() 186 | c.Next() 187 | return 188 | } 189 | 190 | claims := jwt.StandardClaims{} 191 | 192 | err := auth.GetClaimsFromAuthHeader(ctx, authHeader, &claims) 193 | if err != nil { 194 | c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Errorf("Failed to get claims from auth header: %v", err).Error()}) 195 | span.End() 196 | c.Abort() 197 | return 198 | } 199 | 200 | if claims.Audience != auth.ServiceDID { 201 | c.JSON(http.StatusUnauthorized, gin.H{"error": fmt.Sprintf("Invalid audience (expected %s)", auth.ServiceDID)}) 202 | c.Abort() 203 | return 204 | } 205 | 206 | // Set claims Issuer to context as user DID 207 | c.Set("user_did", claims.Issuer) 208 | span.SetAttributes(attribute.String("user.did", claims.Issuer)) 209 | span.End() 210 | c.Next() 211 | } 212 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1 h1:ShHHUAk9RAMML+ectW+lbz0O7Xp0Xpn7XiYhIfCk7bA= 6 | github.com/bluesky-social/indigo v0.0.0-20240425170844-efe2ce5ca2e1/go.mod h1:dBIOGhsiK0rgEETnxiGiuEyrSx6DKxEotHIPbiKD6WU= 7 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 8 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 9 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 10 | github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= 11 | github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= 12 | github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 13 | github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 14 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 15 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 16 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 17 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 18 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 19 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 25 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 26 | github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283 h1:wafHcqvqdYoSHRGetNRQ20h7N8UJiY1ZtJrU6+Ru3/E= 27 | github.com/ericvolp12/go-gin-prometheus v0.0.0-20221219081010-fc0e0436c283/go.mod h1:+yIuLDugyQ+ho7DQ/nJnM/Jej5jb1rftWD5eMW0CVP4= 28 | github.com/ericvolp12/jwt-go-secp256k1 v0.0.2 h1:puGwrNTY2vCt8eakkSEq2yeNxUD3zb2kPhv1OsF1hPs= 29 | github.com/ericvolp12/jwt-go-secp256k1 v0.0.2/go.mod h1:ntxzdN7EhBp8h+N78AtN2hjbVKHa7mijryYd9nPMyMo= 30 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 31 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 32 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 33 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 34 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 35 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 36 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 37 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 38 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 39 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 40 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 41 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 42 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 43 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 44 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 45 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 46 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 47 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 48 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 49 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 50 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 51 | github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 52 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 53 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 54 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 55 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 56 | github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= 57 | github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= 58 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 59 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 60 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 61 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 62 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 63 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 64 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 65 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 66 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 67 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 68 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 69 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 70 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= 71 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= 72 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= 73 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 74 | github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 75 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 76 | github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= 77 | github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 78 | github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= 79 | github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 80 | github.com/hashicorp/golang-lru/arc/v2 v2.0.7 h1:QxkVTxwColcduO+LP7eJO56r2hFiG8zEbfAAzRv52KQ= 81 | github.com/hashicorp/golang-lru/arc/v2 v2.0.7/go.mod h1:Pe7gBlGdc8clY5LJ0LpJXMt5AmgmWNH1g+oFFVUHOEc= 82 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 83 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 84 | github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= 85 | github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= 86 | github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= 87 | github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= 88 | github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= 89 | github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= 90 | github.com/ipfs/go-datastore v0.6.0 h1:JKyz+Gvz1QEZw0LsX1IBn+JFCJQH4SJVFtM4uWU0Myk= 91 | github.com/ipfs/go-datastore v0.6.0/go.mod h1:rt5M3nNbSO/8q1t4LNkLyUwRs8HupMeN/8O4Vn9YAT8= 92 | github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= 93 | github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= 94 | github.com/ipfs/go-ipfs-blockstore v1.3.1 h1:cEI9ci7V0sRNivqaOr0elDsamxXFxJMMMy7PTTDQNsQ= 95 | github.com/ipfs/go-ipfs-blockstore v1.3.1/go.mod h1:KgtZyc9fq+P2xJUiCAzbRdhhqJHvsw8u2Dlqy2MyRTE= 96 | github.com/ipfs/go-ipfs-ds-help v1.1.1 h1:B5UJOH52IbcfS56+Ul+sv8jnIV10lbjLF5eOO0C66Nw= 97 | github.com/ipfs/go-ipfs-ds-help v1.1.1/go.mod h1:75vrVCkSdSFidJscs8n4W+77AtTpCIAdDGAwjitJMIo= 98 | github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= 99 | github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= 100 | github.com/ipfs/go-ipld-cbor v0.1.0 h1:dx0nS0kILVivGhfWuB6dUpMa/LAwElHPw1yOGYopoYs= 101 | github.com/ipfs/go-ipld-cbor v0.1.0/go.mod h1:U2aYlmVrJr2wsUBU67K4KgepApSZddGRDWBYR0H4sCk= 102 | github.com/ipfs/go-ipld-format v0.6.0 h1:VEJlA2kQ3LqFSIm5Vu6eIlSxD/Ze90xtc4Meten1F5U= 103 | github.com/ipfs/go-ipld-format v0.6.0/go.mod h1:g4QVMTn3marU3qXchwjpKPKgJv+zF+OlaKMyhJ4LHPg= 104 | github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= 105 | github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= 106 | github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= 107 | github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= 108 | github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= 109 | github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= 110 | github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= 111 | github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= 112 | github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= 113 | github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= 114 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 115 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 116 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 117 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 118 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 119 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 120 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 121 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 122 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 123 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 124 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 125 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 126 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 127 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 128 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 129 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 130 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 131 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 132 | github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= 133 | github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= 134 | github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= 135 | github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= 136 | github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8= 137 | github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= 138 | github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= 139 | github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= 140 | github.com/lestrrat-go/jwx/v2 v2.0.12 h1:3d589+5w/b9b7S3DneICPW16AqTyYXB7VRjgluSDWeA= 141 | github.com/lestrrat-go/jwx/v2 v2.0.12/go.mod h1:Mq4KN1mM7bp+5z/W5HS8aCNs5RKZ911G/0y2qUjAQuQ= 142 | github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 143 | github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= 144 | github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= 145 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 146 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 147 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 148 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 149 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 150 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 151 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 152 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 153 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 154 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 155 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 156 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 157 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 158 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 159 | github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= 160 | github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= 161 | github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= 162 | github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= 163 | github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= 164 | github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= 165 | github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= 166 | github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= 167 | github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= 168 | github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= 169 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 170 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 171 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 172 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 173 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 174 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 175 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 176 | github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f h1:VXTQfuJj9vKR4TCkEuWIckKvdHFeJH/huIFJ9/cXOB0= 177 | github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= 178 | github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 179 | github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 180 | github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 181 | github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 182 | github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 183 | github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 184 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 185 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 186 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 187 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 188 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 189 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 190 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 191 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 192 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 193 | github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= 194 | github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 195 | github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= 196 | github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 197 | github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= 198 | github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= 199 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 200 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 201 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 202 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 203 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 204 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 205 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 206 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 207 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 208 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 209 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 210 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 211 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 212 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 213 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 214 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 215 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 216 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 217 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 218 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 219 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 220 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 221 | github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 222 | github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= 223 | github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= 224 | github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302 h1:MhInbXe4SzcImAKktUvWBCWZgcw6MYf5NfumTj1BhAw= 225 | github.com/whyrusleeping/cbor-gen v0.1.1-0.20240311221002-68b9f235c302/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so= 226 | github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 h1:yJ9/LwIGIk/c0CdoavpC9RNSGSruIspSZtxG3Nnldic= 227 | github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6/go.mod h1:39U9RRVr4CKbXpXYopWn+FSH5s+vWu6+RmguSPWAq5s= 228 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 229 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 230 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 231 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 232 | gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 233 | gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 234 | gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= 235 | gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02/go.mod h1:JTnUj0mpYiAsuZLmKjTx/ex3AtMowcCgnE7YNyCEP0I= 236 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0 h1:l7AmwSVqozWKKXeZHycpdmpycQECRpoGwJ1FW2sWfTo= 237 | go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.42.0/go.mod h1:Ep4uoO2ijR0f49Pr7jAqyTjSCyS1SRL18wwttKfwqXA= 238 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= 239 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 240 | go.opentelemetry.io/contrib/propagators/b3 v1.17.0 h1:ImOVvHnku8jijXqkwCSyYKRDt2YrnGXD4BbhcpfbfJo= 241 | go.opentelemetry.io/contrib/propagators/b3 v1.17.0/go.mod h1:IkfUfMpKWmynvvE0264trz0sf32NRTZL4nuAN9AbWRc= 242 | go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= 243 | go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= 244 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= 245 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= 246 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= 247 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= 248 | go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= 249 | go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= 250 | go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= 251 | go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= 252 | go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= 253 | go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= 254 | go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= 255 | go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 256 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 257 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 258 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 259 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 260 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 261 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 262 | go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= 263 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 264 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 265 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 266 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 267 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 268 | go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= 269 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 270 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 271 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 272 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 273 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 274 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 275 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 276 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 277 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 278 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 279 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 280 | golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 281 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 282 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 283 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 284 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 285 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 286 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 287 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 288 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 289 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 290 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 291 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 292 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 293 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 294 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 295 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 296 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 297 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 298 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 299 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 300 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 301 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 302 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 305 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 306 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 307 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 308 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 309 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 314 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 315 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 316 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 317 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 318 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 319 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 320 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 321 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 322 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 323 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 324 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 325 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 326 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 327 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 328 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 329 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 330 | golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 331 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 332 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 333 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 334 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 335 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 336 | golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 337 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 338 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 339 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 340 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 341 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 342 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 343 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 344 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 345 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 346 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 347 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 348 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 349 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 350 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 351 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 352 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 353 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 354 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 355 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 356 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 357 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 358 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 359 | google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= 360 | google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= 361 | google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= 362 | google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= 363 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= 364 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= 365 | google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 366 | google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 367 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 368 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 369 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 370 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 371 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 372 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 373 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 374 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 375 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 376 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 377 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 378 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 379 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 380 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 381 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 382 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 383 | lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 384 | lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 385 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 386 | --------------------------------------------------------------------------------