From f08a31168f8a33b5dea6f4c6a5837eb098b0754d Mon Sep 17 00:00:00 2001 From: Gabe <7622243+decentralgabe@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:55:21 -0700 Subject: [PATCH] Address TXT records > 255 characters; make language more consistent; separate section for root records (#162) * clarify text on root records * text consistency * update test vector with long vector * update test vectors * update readmes * pr comment * update go version * update go dep --- .github/workflows/ci.yml | 4 +- CONTRIBUTING.md | 96 ++----------- README.md | 3 +- impl/README.md | 20 ++- impl/build/Dockerfile | 2 +- impl/go.mod | 4 +- impl/go.sum | 8 +- impl/internal/did/did.go | 84 ++++++++---- impl/internal/did/did_test.go | 31 +++-- .../did/testdata/vector-1-dns-records.json | 4 +- .../did/testdata/vector-2-dns-records.json | 18 +-- .../did/testdata/vector-3-did-document.json | 23 ++-- .../did/testdata/vector-3-dns-records.json | 14 +- spec/spec.md | 128 ++++++++++++------ 14 files changed, 227 insertions(+), 212 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b27fc352..3b9ddc67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.22.1 + go-version: 1.22.2 - name: Install Mage run: go install github.com/magefile/mage @@ -38,7 +38,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.22.1 + go-version: 1.22.2 - name: Install Mage run: go install github.com/magefile/mage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28a5519f..63aa9a02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,13 +13,11 @@ This guide is for you. ## Development Prerequisites -___***UPDATE TABLE OF PROJECT DEPS AND INSTALLATION NOTES***___ -| Requirement | Tested Version | Installation Instructions | -|-------------|----------------|------------------------------------------------------| -| Go | 1.17.6 |[go.dev](https://go.dev/doc/tutorial/compile-install) | -| Mage | 1.12.1 |[magefile.org](https://magefile.org/) | -| Java | 17.0.2 | Below, recommended via [SDKMan](https://sdkman.io) | +| Requirement | Tested Version | Installation Instructions | +|-------------|----------------|-------------------------------------------------------| +| Go | 1.22.2 | [go.dev](https://go.dev/doc/tutorial/compile-install) | +| Mage | 1.15.0-5 | [magefile.org](https://magefile.org/) | ### Go @@ -29,7 +27,7 @@ You may verify your `go` installation via the terminal: ``` $> go version -go version go1.17.6 darwin/amd64 +go version go1.22.2 darwin/amd64 ``` If you do not have go, we recommend installing it by: @@ -53,10 +51,10 @@ You may verify your `mage` installation via the terminal: ``` $> mage --version -Mage Build Tool 1.12.1 -Build Date: 2021-12-15T21:00:02Z -Commit: 2f1ec40 -built with: go1.17.6 +Mage Build Tool v1.15.0-5-g2385abb +Build Date: 2024-03-21T12:20:13-07:00 +Commit: 2385abb +built with: go1.22.2 ``` #### MacOS @@ -70,47 +68,6 @@ $> brew install mage #### Linux Installation instructions are on the [Magefile home page](https://magefile.org/). - -### Java - -This project is written in Java, a typesafe, compiled programming language. - -You may verify your `java` installation via the terminal by running `java -version`. - -If you do not have Java, we recommend installing it -via [SDKMan](https://sdkman.io/install). This is a project which will allow you -to easily install the Java Development Kit (JDK), runtime (JRE), and related frameworks, -build tools, and runtimes. - -After you've installed SDKMan, you may install Java: - -#### SDKMan (cross-platform instructions) - -```shell -$> sdk install java - ... -Do you want java 17.0.2-open to be set as default? (Y/n): Y -Setting java 17.0.2-open as default. -``` - -You may test your installation: - -```shell -$> java -version -openjdk version "17.0.2" 2022-01-18 -OpenJDK Runtime Environment (build 17.0.2+8-86) -OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing) -``` - ---- -**NOTE** - -You may additionally look for other Java versions to install by running `sdk list java`: - -...or other installation candidates like Apache Ant, Apache Maven, etc, by running `sdk list`. - -Consult the SDKMan documentation for more info. - --- ## Build (Mage) @@ -119,47 +76,12 @@ Consult the SDKMan documentation for more info. $> mage build ``` -## Build (Java / Gradle) - -### macOS / Linux -```shell -$> ./gradlew build -``` - -### Windows -```shell -$> gradlew.bat build -``` - ## Test (Mage) ``` $> mage test ``` -## Test (Java / Gradle) - -### macOS / Linux -```shell -$> ./gradlew test -``` - -### Windows -```shell -$> gradlew.bat test -``` - ---- -**NOTE** - -You may also combine Gradle build targets in one call, like: - -```shell -$> ./gradlew clean build test -``` - ---- - ## Communications ### Issues diff --git a/README.md b/README.md index 8b101e97..633854d8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![godoc did-dht-method](https://img.shields.io/badge/godoc-did_dht_method-blue)](https://github.com/TBD54566975/did-dht-method/impl) -[![go version 1.22.1](https://img.shields.io/badge/go_version-1.22.1-brightgreen)](https://go.dev/) +[![go version 1.22.2](https://img.shields.io/badge/go_version-1.22.2-brightgreen)](https://go.dev/) [![license Apache 2](https://img.shields.io/badge/license-Apache%202-black)](https://github.com/TBD54566975/did-dht-method/blob/main/LICENSE) [![issues](https://img.shields.io/github/issues/TBD54566975/did-dht-method)](https://github.com/TBD54566975/did-dht-method/issues) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/tbd54566975/did-dht-method/ci.yml) @@ -59,6 +59,7 @@ docker run \ | Typescript | Yes | No | [web5-js](https://github.com/TBD54566975/web5-js/blob/main/packages/dids/src/methods/did-dht.ts) | | Kotlin | Yes | No | [web5-kt](https://github.com/TBD54566975/web5-kt/tree/main/dids/src/main/kotlin/web5/sdk/dids/methods/dht) | | Swift | Yes | No | [web5-swift](https://github.com/TBD54566975/web5-swift/blob/main/Sources/Web5/Dids/Methods/DIDDHT.swift) | +| Dart | Yes | No | [web5-dart](https://github.com/TBD54566975/web5-dart/tree/main/packages/web5/lib/src/dids/did_dht) | | Rust | Yes | No | Coming soon! | ## Project Resources diff --git a/impl/README.md b/impl/README.md index 78f57eb9..99e699f8 100644 --- a/impl/README.md +++ b/impl/README.md @@ -1,18 +1,11 @@ # Server Implementation -- Heavily a work-in-progress -- Designed to be run as a single instance - ## Config ### TOML Config File -Config is managed using a [TOML](https://toml.io/en/) [file](../../config/dev.toml). There are sets of configuration values for the server -(e.g. which port to listen on), the services (e.g. which database to use), and each service. - -Each service may define specific configuration, such as which DID methods are enabled for the DID service. - -A full config example is [provided here](../../config/kitchensink.toml). +Config is managed using a [TOML](https://toml.io/en/) [file](../../config/dev.toml). There are sets of configuration value +s for the server (e.g. which port to listen on) and each sub-component (e.g. which database to use). ## Usage @@ -60,10 +53,13 @@ docker build \ and then ```sh -docker run --publish 8305:8305 did-dht +docker run \ + --publish 8305:8305 \ + --publish 6881:6881/udp \ + did-dht ``` ### Postgres -To use a postgres database as the storage backend, set configuration option `storage_uri` to a `postgres://` URI with the database -connection string. The schema will be created or updated as needed while the program starts. +To use a postgres database as the storage backend, set configuration option `storage_uri` to a `postgres://` URI with +the database connection string. The schema will be created or updated as needed while the program starts. diff --git a/impl/build/Dockerfile b/impl/build/Dockerfile index 17dbc0f1..8b01065e 100644 --- a/impl/build/Dockerfile +++ b/impl/build/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.1-alpine +FROM golang:1.22.2-alpine # Create directory for our app inside the container WORKDIR /app diff --git a/impl/go.mod b/impl/go.mod index 13658e50..80ae7081 100644 --- a/impl/go.mod +++ b/impl/go.mod @@ -15,6 +15,7 @@ require ( github.com/goccy/go-json v0.10.2 github.com/jackc/pgx/v5 v5.5.5 github.com/joho/godotenv v1.5.1 + github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/magefile/mage v1.15.0 github.com/miekg/dns v1.1.58 github.com/mitchellh/go-homedir v1.1.0 @@ -98,7 +99,6 @@ require ( github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.5 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.0.21 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -139,7 +139,7 @@ require ( golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.22.0 // indirect + golang.org/x/net v0.23.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/impl/go.sum b/impl/go.sum index 8c461a06..9a087d67 100644 --- a/impl/go.sum +++ b/impl/go.sum @@ -24,10 +24,6 @@ github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrX github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240401231222-6550aeed8a9d h1:M/dYJOKyLNBDh97VYAyLvzO7sS01K1eIBwNcAK2SFDQ= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240401231222-6550aeed8a9d/go.mod h1:nyTjplXnrari2nQg63ztI4C0rgMb7Jjn3gfn0OM656g= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240402005210-f282aecaa186 h1:XIaS0WBSrg2wU00Cx45NO9H3x3ca7WEI35sSef6NC5c= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240402005210-f282aecaa186/go.mod h1:nyTjplXnrari2nQg63ztI4C0rgMb7Jjn3gfn0OM656g= github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240402005820-2c6b20991baa h1:1kJozfMxe8fRI0jjKUbKhj7/o16d1oTDOfZJLOMTU28= github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240402005820-2c6b20991baa/go.mod h1:nyTjplXnrari2nQg63ztI4C0rgMb7Jjn3gfn0OM656g= github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk= @@ -607,8 +603,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/impl/internal/did/did.go b/impl/internal/did/did.go index f75e8a37..c8d4709b 100644 --- a/impl/internal/did/did.go +++ b/impl/internal/did/did.go @@ -268,7 +268,7 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{controllerTxt}, + Txt: chunkTextRecord(controllerTxt), } records = append(records, &controllerAnswer) } @@ -287,7 +287,7 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{akaTxt}, + Txt: chunkTextRecord(akaTxt), } records = append(records, &akaAnswer) } @@ -301,7 +301,7 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{string(gateway)}, + Txt: chunkTextRecord(string(gateway)), } records = append(records, &gatewayAnswer) } @@ -347,6 +347,10 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori } txtRecord += fmt.Sprintf(";c=%s", vm.Controller) } + + if len(txtRecord) > 255 { + return nil, fmt.Errorf("key value exceeds 255 characters") + } keyRecord := dns.TXT{ Hdr: dns.RR_Header{ Name: fmt.Sprintf("_%s._did.", recordIdentifier), @@ -354,7 +358,7 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{txtRecord}, + Txt: chunkTextRecord(txtRecord), } records = append(records, &keyRecord) @@ -386,16 +390,12 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{svcTxt}, + Txt: chunkTextRecord(svcTxt), } records = append(records, &serviceRecord) svcIDs = append(svcIDs, recordIdentifier) } - // add services to the root record - if len(svcIDs) != 0 { - rootRecord = append(rootRecord, fmt.Sprintf("svc=%s", strings.Join(svcIDs, ","))) - } // add verification relationships to the root record var authIDs []string @@ -438,6 +438,11 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori rootRecord = append(rootRecord, fmt.Sprintf("del=%s", strings.Join(capabilityDelegationIDs, ","))) } + // add services to the root record + if len(svcIDs) != 0 { + rootRecord = append(rootRecord, fmt.Sprintf("svc=%s", strings.Join(svcIDs, ","))) + } + // add the root record rootAnswer := dns.TXT{ Hdr: dns.RR_Header{ @@ -463,7 +468,7 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex, gateways []Authori Class: dns.ClassINET, Ttl: 7200, }, - Txt: []string{"id=" + strings.Join(typesStr, ",")}, + Txt: chunkTextRecord("id=" + strings.Join(typesStr, ",")), } records = append(records, &typesAnswer) } @@ -524,7 +529,8 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, []Authorit switch record := rr.(type) { case *dns.TXT: if strings.HasPrefix(record.Hdr.Name, "_cnt") { - controllers := strings.Split(record.Txt[0], ",") + unchunkedTextRecord := unchunkTextRecord(record.Txt) + controllers := strings.Split(unchunkedTextRecord, ",") if len(controllers) == 1 { doc.Controller = controllers[0] } else { @@ -532,10 +538,12 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, []Authorit } } if strings.HasPrefix(record.Hdr.Name, "_aka") { - doc.AlsoKnownAs = strings.Split(record.Txt[0], ",") + unchunkedTextRecord := unchunkTextRecord(record.Txt) + doc.AlsoKnownAs = strings.Split(unchunkedTextRecord, ",") } if strings.HasPrefix(record.Hdr.Name, "_k") { - data := parseTxtData(strings.Join(record.Txt, ",")) + unchunkedTextRecord := unchunkTextRecord(record.Txt) + data := parseTxtData(unchunkedTextRecord) vmID := data["id"] keyType := keyTypeLookUp(data["t"]) keyBase64URL := data["k"] @@ -598,20 +606,15 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, []Authorit // add to key lookup (e.g. "k1" -> "key1") keyLookup[strings.Split(record.Hdr.Name, ".")[0][1:]] = vmID } else if strings.HasPrefix(record.Hdr.Name, "_s") { - data := parseTxtData(strings.Join(record.Txt, ",")) + unchunkedTextRecord := unchunkTextRecord(record.Txt) + data := parseTxtData(unchunkedTextRecord) sID := data["id"] serviceType := data["t"] serviceEndpoint := data["se"] - var serviceEndpointValue any - if strings.Contains(serviceEndpoint, ",") { - serviceEndpointValue = strings.Split(serviceEndpoint, ",") - } else { - serviceEndpointValue = serviceEndpoint - } service := did.Service{ ID: d.String() + "#" + sID, Type: serviceType, - ServiceEndpoint: serviceEndpointValue, + ServiceEndpoint: strings.Split(serviceEndpoint, ","), } if data["sig"] != "" { if strings.Contains(data["sig"], ",") { @@ -630,10 +633,11 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, []Authorit doc.Services = append(doc.Services, service) } else if record.Hdr.Name == "_typ._did." { - if record.Txt[0] == "" || len(record.Txt) != 1 { - return nil, nil, nil, fmt.Errorf("invalid types record") + if record.Txt[0] == "" { + return nil, nil, nil, fmt.Errorf("types record is empty") } - typesStr := strings.Split(strings.TrimPrefix(record.Txt[0], "id="), ",") + unchunkedTextRecord := unchunkTextRecord(record.Txt) + typesStr := strings.Split(strings.TrimPrefix(unchunkedTextRecord, "id="), ",") for _, t := range typesStr { tInt, err := strconv.Atoi(t) if err != nil { @@ -642,13 +646,14 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, []Authorit types = append(types, TypeIndex(tInt)) } } else if record.Hdr.Name == fmt.Sprintf("_did.%s.", suffix) && record.Hdr.Rrtype == dns.TypeNS { - if record.Txt[0] == "" || len(record.Txt) != 1 { - return nil, nil, nil, fmt.Errorf("invalid gateways record: %s", record.String()) + if record.Txt[0] == "" { + return nil, nil, nil, fmt.Errorf("gateway record is empty") } - gateways = append(gateways, AuthoritativeGateway(record.Txt[0])) + unchunkedTextRecord := unchunkTextRecord(record.Txt) + gateways = append(gateways, AuthoritativeGateway(unchunkedTextRecord)) } else if record.Hdr.Name == fmt.Sprintf("_did.%s.", suffix) && record.Hdr.Rrtype == dns.TypeTXT { - rootData := strings.Join(record.Txt, ";") - rootItems := strings.Split(rootData, ";") + unchunkedTextRecord := unchunkTextRecord(record.Txt) + rootItems := strings.Split(unchunkedTextRecord, ";") seenVersion := false for _, item := range rootItems { @@ -792,3 +797,24 @@ func keyTypeForJWK(jwk jwx.PublicKeyJWK) int { } return -1 } + +// chunkTextRecord splits a text record into chunks of 255 characters, taking into account multi-byte characters +func chunkTextRecord(record string) []string { + var chunks []string + runeArray := []rune(record) // Convert to rune slice to properly handle multi-byte characters + for len(runeArray) > 0 { + if len(runeArray) <= 255 { + chunks = append(chunks, string(runeArray)) + break + } + + chunks = append(chunks, string(runeArray[:255])) + runeArray = runeArray[255:] + } + return chunks +} + +// unchunkTextRecord joins chunks of a text record +func unchunkTextRecord(chunks []string) string { + return strings.Join(chunks, "") +} diff --git a/impl/internal/did/did_test.go b/impl/internal/did/did_test.go index e4f9927b..fe72b545 100644 --- a/impl/internal/did/did_test.go +++ b/impl/internal/did/did_test.go @@ -188,7 +188,7 @@ func TestToDNSPacket(t *testing.T) { { ID: "vcs", Type: "VerifiableCredentialService", - ServiceEndpoint: "https://example.com/vc/", + ServiceEndpoint: []string{"https://example.com/vc/"}, Sig: []string{"1", "2"}, Enc: "3", }, @@ -227,10 +227,10 @@ func TestToDNSPacket(t *testing.T) { func TestVectors(t *testing.T) { type testVectorDNSRecord struct { - Name string `json:"name"` - RecordType string `json:"type"` - TTL string `json:"ttl"` - Record string `json:"rdata"` + Name string `json:"name"` + RecordType string `json:"type"` + TTL string `json:"ttl"` + Record []string `json:"rdata"` } t.Run("test vector 1", func(t *testing.T) { @@ -275,7 +275,7 @@ func TestVectors(t *testing.T) { s := record.String() if strings.Contains(s, expectedRecord.RecordType) && strings.Contains(s, expectedRecord.TTL) && - strings.Contains(s, expectedRecord.Record) { + strings.Contains(s, strings.Join(expectedRecord.Record, "")) { matchedRecords[i] = true // Mark as matched break } @@ -370,7 +370,7 @@ func TestVectors(t *testing.T) { s := record.String() if strings.Contains(s, expectedRecord.RecordType) && strings.Contains(s, expectedRecord.TTL) && - strings.Contains(s, expectedRecord.Record) { + strings.Contains(s, strings.Join(expectedRecord.Record, "")) { matchedRecords[i] = true // Mark as matched break } @@ -418,6 +418,13 @@ func TestVectors(t *testing.T) { Purposes: []did.PublicKeyPurpose{did.KeyAgreement}, }, }, + Services: []did.Service{ + { + ID: "service-1", + Type: "TestLongService", + ServiceEndpoint: []string{"https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1"}, + }, + }, }) require.NoError(t, err) require.NotEmpty(t, doc) @@ -432,7 +439,6 @@ func TestVectors(t *testing.T) { require.NoError(t, err) assert.JSONEq(t, string(expectedDIDDocJSON), string(docJSON)) - didID := DHT(doc.ID) packet, err := didID.ToDNSPacket(*doc, nil, []AuthoritativeGateway{"gateway1.example-did-dht-gateway.com."}) require.NoError(t, err) @@ -452,8 +458,13 @@ func TestVectors(t *testing.T) { if record.Header().Name == expectedRecord.Name { s := record.String() if strings.Contains(s, expectedRecord.RecordType) && - strings.Contains(s, expectedRecord.TTL) && - strings.Contains(s, expectedRecord.Record) { + strings.Contains(s, expectedRecord.TTL) { + // make sure all parts of the record are contained within s + for _, r := range expectedRecord.Record { + if !strings.Contains(s, r) { + break + } + } matchedRecords[i] = true // Mark as matched break } diff --git a/impl/internal/did/testdata/vector-1-dns-records.json b/impl/internal/did/testdata/vector-1-dns-records.json index 84b9703c..05f6690d 100644 --- a/impl/internal/did/testdata/vector-1-dns-records.json +++ b/impl/internal/did/testdata/vector-1-dns-records.json @@ -3,12 +3,12 @@ "name": "_did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo.", "type": "TXT", "ttl": "7200", - "rdata": "v=0;vm=k0;auth=k0;asm=k0;inv=k0;del=k0" + "rdata": ["v=0;vm=k0;auth=k0;asm=k0;inv=k0;del=k0"] }, { "name": "_k0._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + "rdata": ["id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE"] } ] \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-2-dns-records.json b/impl/internal/did/testdata/vector-2-dns-records.json index f81f9f5e..a0f56552 100644 --- a/impl/internal/did/testdata/vector-2-dns-records.json +++ b/impl/internal/did/testdata/vector-2-dns-records.json @@ -3,54 +3,54 @@ "name": "_did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo.", "type": "NS", "ttl": "7200", - "rdata": "gateway1.example-did-dht-gateway.com." + "rdata": ["gateway1.example-did-dht-gateway.com."] }, { "name": "_did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo.", "type": "NS", "ttl": "7200", - "rdata": "gateway2.example-did-dht-gateway.com." + "rdata": ["gateway2.example-did-dht-gateway.com."] }, { "name": "_did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo.", "type": "TXT", "ttl": "7200", - "rdata": "v=0;vm=k0,k1;svc=s0;auth=k0;asm=k0,k1;inv=k0,k1;del=k0" + "rdata": ["v=0;vm=k0,k1;auth=k0;asm=k0,k1;inv=k0,k1;del=k0;svc=s0"] }, { "name": "_cnt._did.", "type": "TXT", "ttl": "7200", - "rdata": "did:example:abcd" + "rdata": ["did:example:abcd"] }, { "name": "_aka._did.", "type": "TXT", "ttl": "7200", - "rdata": "did:example:efgh,did:example:ijkl" + "rdata": ["did:example:efgh,did:example:ijkl"] }, { "name": "_k0._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + "rdata": ["id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE"] }, { "name": "_k1._did.", "type": "TXT", "ttl": "7200", - "rdata": "t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9" + "rdata": ["t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9"] }, { "name": "_s0._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=service-1;t=TestService;se=https://test-service.com/1,https://test-service.com/2" + "rdata": ["id=service-1;t=TestService;se=https://test-service.com/1,https://test-service.com/2"] }, { "name": "_typ._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=1,2,3" + "rdata": ["id=1,2,3"] } ] \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-3-did-document.json b/impl/internal/did/testdata/vector-3-did-document.json index fb40938f..0c02e918 100644 --- a/impl/internal/did/testdata/vector-3-did-document.json +++ b/impl/internal/did/testdata/vector-3-did-document.json @@ -6,11 +6,11 @@ "type": "JsonWebKey", "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", "publicKeyJwk": { - "kty": "OKP", - "crv": "Ed25519", - "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g", + "kid": "0", "alg": "Ed25519", - "kid": "0" + "crv": "Ed25519", + "kty": "OKP", + "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g" } }, { @@ -18,11 +18,11 @@ "type": "JsonWebKey", "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", "publicKeyJwk": { - "kty": "OKP", - "crv": "X25519", - "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4", + "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ", "alg": "ECDH-ES+A128KW", - "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ" + "crv": "X25519", + "kty": "OKP", + "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4" } } ], @@ -40,5 +40,12 @@ ], "capabilityDelegation": [ "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "service": [ + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#service-1", + "type": "TestLongService", + "serviceEndpoint": ["https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1"] + } ] } \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-3-dns-records.json b/impl/internal/did/testdata/vector-3-dns-records.json index 7db3bb88..4f250e9f 100644 --- a/impl/internal/did/testdata/vector-3-dns-records.json +++ b/impl/internal/did/testdata/vector-3-dns-records.json @@ -3,24 +3,30 @@ "name": "_did.sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy.", "type": "NS", "ttl": "7200", - "rdata": "gateway1.example-did-dht-gateway.com." + "rdata": ["gateway1.example-did-dht-gateway.com."] }, { "name": "_did.sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy.", "type": "TXT", "ttl": "7200", - "rdata": "v=0;vm=k0,k1;auth=k0;asm=k0;agm=k1;inv=k0;del=k0" + "rdata": ["v=0;vm=k0,k1;auth=k0;asm=k0;agm=k1;inv=k0;del=k0;svc=s0"] }, { "name": "_k0._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=0;t=0;k=sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g" + "rdata": ["id=0;t=0;k=sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g"] }, { "name": "_k1._did.", "type": "TXT", "ttl": "7200", - "rdata": "id=WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ;t=3;k=3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4;a=ECDH-ES+A128KW" + "rdata": ["id=service-1;t=TestLongService;se=https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiii", "iiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1"] + }, + { + "name": "_s0._did.", + "type": "TXT", + "ttl": "7200", + "rdata": ["id=0;t=0;k=0"] } ] \ No newline at end of file diff --git a/spec/spec.md b/spec/spec.md index 973f90d2..9e9a3f5f 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -9,7 +9,7 @@ The DID DHT Method Specification 1.0 **Draft Created:** October 20, 2023 -**Latest Update:** April 1, 2024 +**Latest Update:** April 3, 2024 **Editors:** ~ [Gabe Cohen](https://github.com/decentralgabe) @@ -184,8 +184,8 @@ Similarly, the requirement of an Identity Key enables [interoperability with oth ### DIDs as DNS Records -In this scheme, we encode the [[ref:DID Document]] as [DNS Resource Records](https://en.wikipedia.org/wiki/Domain_Name_System#Resource_records). -Comprising a DNS packet [[spec:RFC1034]] [[spec:RFC1035]], which is then stored in the [[ref:DHT]]. +In this scheme, we encode the [[ref:DID Document]] as [DNS Resource Records](https://en.wikipedia.org/wiki/Domain_Name_System#Resource_records). These resource records make up a DNS packet [[spec:RFC1034]] [[spec:RFC1035]], which is then +stored in the [[ref:DHT]]. | Name | Type | TTL | Rdata | | ------------ | ---- | ------ | ------------------------------------------------------------ | @@ -200,26 +200,51 @@ Comprising a DNS packet [[spec:RFC1034]] [[spec:RFC1035]], which is then stored The recommended TTL value is 7200 seconds (2 hours), the default TTL for [[ref:Mainline]] records. ::: -- The root record's name ****MUST**** be of the form, `_did..`, where `ID` is the [[ref:Pkarr]] identifier +- The [Root Record](#root-record) serves as a "map" to reconstruct a [[ref:DID Document]] from a DNS packet. +This record contains a _version_ number, indicating the version of this specification a given record is created against. + +- Additional records like `srv` (services), `vm` ([Verification Methods](#verification-methods)), and +[Verification Relationships](#verification-relationships) (e.g., authentication, assertion, etc.) are +represented as additional records in the format `._did.`. These records contain the zero-indexed value of +each `key` or `service` as attributes. + +- All resource record names, aside from the [Root Record](#root-record) and optional +[Authoritative Gateway Records](#designating-authoritative-gateways), ****MUST**** end in `_did.`. + +- The DNS packet ****MUST**** set the _Authoritative Answer_ flag since this is always an _Authoritative_ packet. + +- `TXT` records ****MAY**** exceed 255 characters as per [[spec:RFC1035]]. Records exceeding 255 characters are +represented as multiple strings, which upon DID Document reconstructin, can be concatenated to a single value. + +#### Root Record + +The root record is a special record which serves as instructions on how to reconstruct a [[ref:DID Document]], +by providing a [property mapping](#property-mapping) for a [[ref:DID Document]], along with containing pertinent +metadata such as a version identifier. + +- The root record's **name** ****MUST**** be of the form, `_did..`, where `ID` is the [[ref:Pkarr]] identifier associated with the DID (i.e. `did:dht:` becomes `_did..`). - The root record's **type** is `TXT`, indicating a Text record. -- The root record's value identifies the [property mapping](#property-mapping) for the document, along with -versioning information. +- The root record's **rdata** is represented by the form `v=M;vm=N;auth=O;asm=P;inv=Q;del=R;srv=S` where +`M` is the version of the DNS packet representation defined by this specification. `N` is the set +[Verification Method](#verification-methods) identifiers (e.g. `k0,k1`) present in the document, always +containing at least `k0`. `O`, `P`, `Q`, and `R` contain the set of Verification Method resource +aliases (e.g. `k0`) for each [Verification Relationship](#verification-relationships). `S` contains +the set of Service resource aliases (e.g. `s0`) for each [Service](#services). -- The root record ****MUST**** include a version number. The version of the DNS packet representation for -the `did:dht` method is represented by a property `v=V` where `V` is a monotonically increasing version number. -The version number for this specification version is **0** (e.g. `v=0`). The version number is not present -in the corresponding DID Document. +Additionally: -- Additional records like `srv` (services), `vm` (verification methods), and verification relationships -(e.g., authentication, assertion, etc.) are represented as additional records in the format `._did.`. -These records contain the zero-indexed value of each `key` or `service` as attributes. + - A version number ****MUST**** be present. The version number for this specification version + is **0** (e.g. `v=0`). The version number is not present in the corresponding DID Document. -- All resource record names, aside from the root record, ****MUST**** end in `_did.`. + - The `vm` property ****MUST**** always contain _at least_ the [[ref:Identity Key]] represented by `k0`. + + - Verification Relationships (`auth`, `asm`, `inv`, `del`, `srv`) without any members ****MUST**** be omitted. + + - If there are no [Services](#services) the `srv` property ****MUST**** be omitted. -- The DNS packet ****MUST**** set the _Authoritative Answer_ flag since this is always an _Authoritative_ packet. **Example Root Record** @@ -229,7 +254,7 @@ These records contain the zero-indexed value of each `key` or `service` as attri ### Property Mapping -The following section describes mapping a [[ref:DID Document]] to a DNS packet. +The following section describes mapping a [[ref:DID Document]] to a DNS packet's **rdata**. - Resource names are aliased with zero-indexed values (e.g. `k0`, `k1`, `s0`, `s1`). @@ -250,8 +275,11 @@ The subsequent instructions serve as a reference for mapping DID Document proper A [DID controller](https://www.w3.org/TR/did-core/#did-controller) ****MAY**** be present in a `did:dht` document. -If present, a DID controller ****MUST**** be represented as a `_cnt._did.` TXT record in the form of a comma-separated -list of controller DID identifiers. +- The [Controller](https://www.w3.org/TR/did-core/#did-controller) record's **name** is represented as a `_cnt._did.`. + +- The [Controller](https://www.w3.org/TR/did-core/#did-controller) record's **type** is `TXT`, indicating a Text record. + +- The [Controller](https://www.w3.org/TR/did-core/#did-controller) record's **data** is represented as a comma-separatedlist of controller DID identifiers. To ensure that the DID controller is authorized to make changes to the DID Document, the controller for the [[ref:Identity Key]] Verification Method ****MUST**** be contained within the controller property. @@ -265,8 +293,11 @@ To ensure that the DID controller is authorized to make changes to the DID Docum A `did:dht` document ****MAY**** have multiple identifiers using the [alsoKnownAs](https://www.w3.org/TR/did-core/#also-known-as) property. -If present, alternate DID identifiers ****MUST**** be represented as `_aka_.did.` TXT record in the form of a -comma-separated list of DID identifiers. +- The [Also Known As](https://www.w3.org/TR/did-core/#also-known-as) record's **name** is represented as a `_aka._did.`. + +- The [Also Known As](https://www.w3.org/TR/did-core/#also-known-as) record's **type** is `TXT`, indicating a Text record. + +- The [Also Known As](https://www.w3.org/TR/did-core/#also-known-as) record's **data** is represented as a comma-separated list of DID identifiers. **Example AKA Record** @@ -276,12 +307,12 @@ comma-separated list of DID identifiers. #### Verification Methods -- Each [Verification Method's](https://www.w3.org/TR/did-core/#verification-methods) **name** is represented +- Each [Verification Method](https://www.w3.org/TR/did-core/#verification-methods) record's **name** is represented as a `_kN._did.` record where `N` is the zero-indexed positional index of a given Verification Method (e.g. `_k0`, `_k1`). -- Each [Verification Method's]((https://www.w3.org/TR/did-core/#verification-methods)) record **type** is `TXT`, indicating a Text record. +- Each [Verification Method]((https://www.w3.org/TR/did-core/#verification-methods)) record's **type** is `TXT`, indicating a Text record. -- Each [Verification Method's](https://www.w3.org/TR/did-core/#verification-methods) **rdata** is represented by the form +- Each [Verification Method](https://www.w3.org/TR/did-core/#verification-methods) record's **rdata** is represented by the form `id=M;t=N;k=O;a=P` where `M` is the Verification Method's `id`, `N` is the index of the key's type from the [key type index](registry/index.html#key-type-index), `N` is the unpadded base64URL [[spec:RFC4648]] representation of the public key, and `P` is the `JWK` `alg` identifier of the key. @@ -307,7 +338,7 @@ assert the veracity of their relations. #### Verification Relationships - Each [Verification Relationship](https://www.w3.org/TR/did-core/#verification-relationships) is represented as a part -of the root `_did..` record (see: [Property Mapping](#property-mapping)). +of the root `_did..` record (see: [Root Record](#root-record)). The following table acts as a map between Verification Relationship types and their record name: @@ -336,12 +367,12 @@ represented as a comma-separated list of key references. #### Services -- Each [Service's](https://www.w3.org/TR/did-core/#services) **name** is represented as a `_sN._did.` record where `N` is +- Each [Service](https://www.w3.org/TR/did-core/#services) record's **name** is represented as a `_sN._did.` record where `N` is the zero-indexed positional index of the Service (e.g. `_s0`, `_s1`). -- The record **type** is `TXT`, indicating a Text record. +- Each [Service](https://www.w3.org/TR/did-core/#services) record's **type** is `TXT`, indicating a Text record. -- Each [Service's](https://www.w3.org/TR/did-core/#services) **data** is represented with the form `id=M;t=N;se=O` +- Each [Service](https://www.w3.org/TR/did-core/#services) record's **data** is represented with the form `id=M;t=N;se=O` where `M` is the Service's ID, `N` is the Service's Type and `O` is the Service's URI. - Multiple service endpoints can be represented as an array (e.g. `id=dwn;t=DecentralizedWebNode;se=https://dwn.org/dwn1,https://dwn.org/dwn2`) @@ -530,11 +561,11 @@ designation is accomplished by incorporating [DNS NS records](https://en.wikiped DNS packet destined for storage in the DHT. A DID ****MAY**** have multiple NS records, enhancing redundancy and reliability. The format for these records is outlined as follows: -- The record **name** is represented as `_did..` record, where `ID` represents the unique identifier of the [[ref:DID Document]]. +- Each Gateway record's **name** is represented as `_did..` record, where `ID` represents the unique identifier of the [[ref:DID Document]]. -- The record **type** is `NS`, indicating a Name Server record. +- Each Gateway record's **type** is `NS`, indicating a Name Server record. -- The record **data** is represented as a [Fully Qualified Domain Name (FQDN)](https://en.wikipedia.org/wiki/Fully_qualified_domain_name). +- Each Gateway record's **data** is represented as a [Fully Qualified Domain Name (FQDN)](https://en.wikipedia.org/wiki/Fully_qualified_domain_name). | Name | Type | TTL | Rdata | | ------------ | ---- | ----- | ------------------------------------- | @@ -548,13 +579,13 @@ a particular type. Types are not included as a part of the DID Document, but rat for DIDs to be indexed by type by [[ref:Gateways]], and for DIDs to be resolved by type. DIDs can be indexed by type by adding a `_typ._did.` record to the DNS packet. A DID ****MAY**** have **AT MOST** one type index -record. This record is a TXT record with the following format: +record. This record is of the following format: -- The record **name** is represented as a `_typ._did.` record. +- The Type Index record's **name** is represented as a `_typ._did.` record. -- The record **type** is `TXT`, indicating a Text record. +- The Type Index record's **type** is `TXT`, indicating a Text record. -- The record **data** is represented with the form `id=0,1,2` where the value is a comma-separated list of integer types from +- The Type Index record's **data** is represented with the form `id=0,1,2` where the value is a comma-separated list of integer types from the [type index](#type-indexing). **Example Type Index Record** @@ -800,7 +831,8 @@ follows the same process as [updating a DID](#register-or-update-a-did), but wit [section on deactivation](#deactivate). Upon receiving a request to deactivate a DID, the Gateway ****MUST**** verify the signature of the request, and if valid, -stop republishing the DHT. If the DNS Packets contain a `_typ._did.` record, the Gateway ****MUST**** remove the type index. +stop republishing the DHT. If the DNS Packets contain a `_typ._did.` record, the Gateway ****MUST**** stop indexing the +type(s) for the DID. #### Type Indexing @@ -986,7 +1018,7 @@ such as a [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol). ### Data Authenticity -To enter into the DHT using [[ref:BEP44]] records ****MUST*** be signed by an [[ref:Ed25519]] private key, known as the +To enter into the DHT using [[ref:BEP44]] records ****MUST**** be signed by an [[ref:Ed25519]] private key, known as the [[ref:Identity Key]]. When retrieving records either through a [[ref:Mainline Server]] or a [[ref:Gateway]] is it ****RECOMMENDED**** that one verifies the cryptographic integrity of the record themselves instead of trusting a server to have done the validation. Servers that do not return a signature value ****MUST NOT**** be trusted. @@ -1187,7 +1219,7 @@ With controller: `did:dht:i9xkp8ddcbcg8jwq54ox699wuzxyifsqx4jru45zodqu453ksz6y`. | Name | Type | TTL | Rdata | | --------- | ---- | ---- | ----------- | | _did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo. | NS | 7200 | gateway1.example-did-dht-gateway.com. | -| _did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo. | TXT | 7200 | v=0;vm=k0,k1;svc=s0;auth=k0;asm=k0,k1;inv=k0,k1;del=k0 | +| _did.cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo. | TXT | 7200 | v=0;vm=k0,k1;auth=k0;asm=k0,k1;inv=k0,k1;del=k0;svc=s0 | | _cnt.did. | TXT | 7200 | did:example:abcd | | _aka.did. | TXT | 7200 | did:example:efgh,did:example:ijkl | | _k0.did. | TXT | 7200 | id=0;t=0;k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE;c=did:example:abcd | @@ -1198,7 +1230,7 @@ With controller: `did:dht:i9xkp8ddcbcg8jwq54ox699wuzxyifsqx4jru45zodqu453ksz6y`. #### Vector 3 A DID Document with two keys -- the [[ref:Identity Key]] and an X25519 key used with a different `alg` value than -what is specified in the registry. The DID also has two gateway records. +what is specified in the registry. The DID also has two gateway records and a service with an endpoint greater than 255 characters. **Identity Public Key JWK:** @@ -1226,6 +1258,16 @@ what is specified in the registry. The DID also has two gateway records. **Key Purposes:** `Key Agreement`. +**Service:** + +```json +{ + "id": "service-1", + "type": "TestLongService", + "serviceEndpoint": ["https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1"] +} +``` + **Gateways:**: `gateway1.example-did-dht-gateway.com.`, `gateway2.example-did-dht-gateway.com.`. **DID Document:** @@ -1273,6 +1315,13 @@ what is specified in the registry. The DID also has two gateway records. ], "capabilityDelegation": [ "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "service": [ + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#service-1", + "type": "TestLongService", + "serviceEndpoint": ["https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1"] + } ] } ``` @@ -1282,9 +1331,10 @@ what is specified in the registry. The DID also has two gateway records. | Name | Type | TTL | Rdata | | --------- | ---- | ---- | ----------- | | _did.sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy. | NS | 7200 | gateway1.example-did-dht-gateway.com. | -| _did.sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy. | TXT | 7200 | v=0;vm=k0,k1;auth=k0;asm=k0;agm=k1;inv=k0;del=k0 | +| _did.sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy. | TXT | 7200 | v=0;vm=k0,k1;auth=k0;asm=k0;agm=k1;inv=k0;del=k0;svc=s0 | | _k0.did. | TXT | 7200 | id=0;t=0;k=sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g | | _k1.did. | TXT | 7200 | id=WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ;t=3;k=3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4;a=ECDH-ES+A128KW | +| _s0.did. | TXT | 7200 | id=service-1;t=TestLongService;se=https://test-lllllllllllllllllllllllllllllllllllooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggggggggggggggggggggggggggggggggggggsssssssssssssssssssssssssseeeeeeeeeeeeeeeeeeerrrrrrrrrrrrrrrvvvvvvvvvvvvvvvvvvvviiiiiiiiiiiiiiii iiiiiiiiiiiiiiiccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.com/1 | ### Open API Definition