From fe6c534ca71304b91e6ed7a3424dfbae69c9170d Mon Sep 17 00:00:00 2001 From: Gabe <7622243+decentralgabe@users.noreply.github.com> Date: Tue, 21 Nov 2023 22:53:57 +0100 Subject: [PATCH] Add two test vectors (#55) * pretty picture * add two test vectors * fix thumbprint logic * fix test --- impl/go.mod | 14 +- impl/go.sum | 22 +-- impl/internal/did/did.go | 29 ++- impl/internal/did/did_test.go | 118 ++++++++++- .../did/testdata/vector-1-did-document.json | 21 ++ .../did/testdata/vector-1-dns-records.json | 12 ++ .../testdata/vector-1-public-key-jwk-1.json | 7 + .../did/testdata/vector-2-did-document.json | 41 ++++ .../did/testdata/vector-2-dns-records.json | 27 +++ .../testdata/vector-2-public-key-jwk-2.json | 8 + impl/internal/did/testdata_test.go | 37 ++++ impl/pkg/dht/pkarr_test.go | 5 +- spec/spec.md | 185 +++++++++++++++++- spec/specs.json | 2 +- 14 files changed, 484 insertions(+), 44 deletions(-) create mode 100644 impl/internal/did/testdata/vector-1-did-document.json create mode 100644 impl/internal/did/testdata/vector-1-dns-records.json create mode 100644 impl/internal/did/testdata/vector-1-public-key-jwk-1.json create mode 100644 impl/internal/did/testdata/vector-2-did-document.json create mode 100644 impl/internal/did/testdata/vector-2-dns-records.json create mode 100644 impl/internal/did/testdata/vector-2-public-key-jwk-2.json create mode 100644 impl/internal/did/testdata_test.go diff --git a/impl/go.mod b/impl/go.mod index 039da2e3..017af602 100644 --- a/impl/go.mod +++ b/impl/go.mod @@ -4,11 +4,13 @@ go 1.21 require ( github.com/BurntSushi/toml v0.3.1 - github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20231012171634-7bd066d36fb1 + github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20231121023732-c496504a93c4 github.com/anacrolix/dht/v2 v2.20.0 + github.com/anacrolix/log v0.14.0 github.com/anacrolix/torrent v1.52.5 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 + github.com/go-co-op/gocron v1.35.2 github.com/go-playground/validator/v10 v10.15.1 github.com/goccy/go-json v0.10.2 github.com/joho/godotenv v1.5.1 @@ -34,7 +36,6 @@ require ( github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/anacrolix/chansync v0.3.0 // indirect github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 // indirect - github.com/anacrolix/log v0.14.0 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo/perf v1.0.0 // indirect github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 // indirect @@ -47,22 +48,21 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/cloudflare/circl v1.3.3 // indirect + github.com/cloudflare/circl v1.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-co-op/gocron v1.35.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/spec v0.20.9 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/google/uuid v1.3.1 // indirect - github.com/gowebpki/jcs v1.0.0 // indirect + github.com/google/uuid v1.4.0 // indirect + github.com/gowebpki/jcs v1.0.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/hyperledger/aries-framework-go v0.3.2 // indirect @@ -80,7 +80,7 @@ require ( github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect - github.com/lestrrat-go/jwx/v2 v2.0.13 // indirect + github.com/lestrrat-go/jwx/v2 v2.0.16 // 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 diff --git a/impl/go.sum b/impl/go.sum index 5177ffd1..adde4c68 100644 --- a/impl/go.sum +++ b/impl/go.sum @@ -50,8 +50,8 @@ 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.20231012171634-7bd066d36fb1 h1:X1ay773wbND0WFMmrpnWgDIdOuk62W9A+yeZI8S15VQ= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20231012171634-7bd066d36fb1/go.mod h1:M/z16HzB/Nyi1yxVKlIw5aNyOGZ4ziKJpArdiKZ8mO0= +github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20231121023732-c496504a93c4 h1:r0pRi+TDJEL5EaEY/VHzyMphNx5fNUmk0hsxzwU9EiI= +github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20231121023732-c496504a93c4/go.mod h1:QmlzAUyFIChi0qqr3h5QvE9I8z0fc3U6A6hIQa2yr40= github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk= github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= @@ -130,8 +130,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= +github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -280,8 +280,9 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -291,8 +292,8 @@ github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gowebpki/jcs v1.0.0 h1:0pZtOgGetfH/L7yXb4KWcJqIyZNA43WXFyMd7ftZACw= -github.com/gowebpki/jcs v1.0.0/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI= +github.com/gowebpki/jcs v1.0.1 h1:Qjzg8EOkrOTuWP7DqQ1FbYtcpEbeTzUoTN9bptp8FOU= +github.com/gowebpki/jcs v1.0.1/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -365,8 +366,8 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= -github.com/lestrrat-go/jwx/v2 v2.0.13 h1:XdxzJbudGaHEoNmyJACAT8aFCB+DmviiaiMoZwuJoUo= -github.com/lestrrat-go/jwx/v2 v2.0.13/go.mod h1:UzXMzcV99p9/xe1JsIb336NJDGXLsleR+Qj3ucEDtfI= +github.com/lestrrat-go/jwx/v2 v2.0.16 h1:TuH3dBkYTy2giQg/9D8f20znS3JtMRuQJ372boS3lWk= +github.com/lestrrat-go/jwx/v2 v2.0.16/go.mod h1:jBHyESp4e7QxfERM0UKkQ80/94paqNIEcdEfiUYz5zE= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= @@ -570,7 +571,6 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -730,14 +730,12 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/impl/internal/did/did.go b/impl/internal/did/did.go index 8258c92c..299d0b77 100644 --- a/impl/internal/did/did.go +++ b/impl/internal/did/did.go @@ -11,7 +11,6 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/cryptosuite" "github.com/TBD54566975/ssi-sdk/did" - "github.com/TBD54566975/ssi-sdk/did/ion" "github.com/miekg/dns" "github.com/tv42/zbase32" ) @@ -70,7 +69,7 @@ type CreateDIDDHTOpts struct { type VerificationMethod struct { VerificationMethod did.VerificationMethod `json:"verificationMethod"` - Purposes []ion.PublicKeyPurpose `json:"purposes"` + Purposes []did.PublicKeyPurpose `json:"purposes"` } // GenerateDIDDHT generates a did:dht identifier given a set of options @@ -117,10 +116,19 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu seenIDs[vm.VerificationMethod.ID] = true // update ID and controller in place - if vm.VerificationMethod.ID == "" || strings.Contains(vm.VerificationMethod.ID, "#") { + if strings.Contains(vm.VerificationMethod.ID, "#") { return nil, fmt.Errorf("verification method id %s is invalid", vm.VerificationMethod.ID) } + // set to thumbprint if none is provided + if vm.VerificationMethod.ID == "" { + vm.VerificationMethod.ID = vm.VerificationMethod.PublicKeyJWK.KID + } else { + // make sure the verification method ID and KID match + vm.VerificationMethod.PublicKeyJWK.KID = vm.VerificationMethod.ID + } vm.VerificationMethod.ID = id + "#" + vm.VerificationMethod.ID + + // if there's no controller, set it to the DID itself if vm.VerificationMethod.Controller != "" { vm.VerificationMethod.Controller = id } @@ -130,15 +138,15 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu vmID := vm.VerificationMethod.ID[strings.LastIndex(vm.VerificationMethod.ID, "#"):] for _, purpose := range vm.Purposes { switch purpose { - case ion.Authentication: + case did.Authentication: authentication = append(authentication, vmID) - case ion.AssertionMethod: + case did.AssertionMethod: assertionMethod = append(assertionMethod, vmID) - case ion.KeyAgreement: + case did.KeyAgreement: keyAgreement = append(keyAgreement, vmID) - case ion.CapabilityInvocation: + case did.CapabilityInvocation: capabilityInvocation = append(capabilityInvocation, vmID) - case ion.CapabilityDelegation: + case did.CapabilityDelegation: capabilityDelegation = append(capabilityDelegation, vmID) default: return nil, fmt.Errorf("unknown key purpose: %s:%s", vmID, purpose) @@ -162,7 +170,8 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu } // create the did document - key0JWK, err := jwx.PublicKeyToPublicKeyJWK("0", pubKey) + kid := "0" + key0JWK, err := jwx.PublicKeyToPublicKeyJWK(&kid, pubKey) if err != nil { return nil, err } @@ -376,7 +385,7 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) { if err != nil { return nil, nil, err } - pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(vmID, pubKey) + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(&vmID, pubKey) if err != nil { return nil, nil, err } diff --git a/impl/internal/did/did_test.go b/impl/internal/did/did_test.go index 532ddb58..8e7da96d 100644 --- a/impl/internal/did/did_test.go +++ b/impl/internal/did/did_test.go @@ -1,12 +1,13 @@ package did import ( + "crypto/ed25519" + "encoding/json" "testing" "github.com/TBD54566975/ssi-sdk/crypto" "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/did" - "github.com/TBD54566975/ssi-sdk/did/ion" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -44,7 +45,7 @@ func TestGenerateDIDDHT(t *testing.T) { t.Run("test generate did:dht with opts", func(t *testing.T) { pubKey, _, err := crypto.GenerateSECP256k1Key() require.NoError(t, err) - pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK("key1", pubKey) + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(nil, pubKey) require.NoError(t, err) opts := CreateDIDDHTOpts{ @@ -56,7 +57,7 @@ func TestGenerateDIDDHT(t *testing.T) { Controller: "did:dht:123456789abcdefghi", PublicKeyJWK: pubKeyJWK, }, - Purposes: []ion.PublicKeyPurpose{ion.AssertionMethod, ion.CapabilityInvocation}, + Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, }, }, Services: []did.Service{ @@ -156,7 +157,7 @@ func TestToDNSPacket(t *testing.T) { t.Run("doc with multiple keys and services - test to dns packet round trip", func(t *testing.T) { pubKey, _, err := crypto.GenerateSECP256k1Key() require.NoError(t, err) - pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK("key1", pubKey) + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(nil, pubKey) require.NoError(t, err) opts := CreateDIDDHTOpts{ @@ -168,7 +169,7 @@ func TestToDNSPacket(t *testing.T) { Controller: "did:dht:123456789abcdefghi", PublicKeyJWK: pubKeyJWK, }, - Purposes: []ion.PublicKeyPurpose{ion.AssertionMethod, ion.CapabilityInvocation}, + Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, }, }, Services: []did.Service{ @@ -199,6 +200,111 @@ func TestToDNSPacket(t *testing.T) { require.NotEmpty(t, decodedDoc) require.Empty(t, types) - assert.EqualValues(t, *doc, *decodedDoc) + decodedJSON, err := json.Marshal(decodedDoc) + require.NoError(t, err) + + docJSON, err := json.Marshal(doc) + require.NoError(t, err) + + assert.JSONEq(t, string(docJSON), string(decodedJSON)) + }) +} + +func TestVectors(t *testing.T) { + + type testVectorDNSRecord struct { + RecordType string `json:"type"` + TTL string `json:"ttl"` + Record string `json:"rdata"` + } + + t.Run("test vector 1", func(t *testing.T) { + var pubKeyJWK jwx.PublicKeyJWK + retrieveTestVectorAs(t, vector1PublicKeyJWK1, &pubKeyJWK) + + pubKey, err := pubKeyJWK.ToPublicKey() + require.NoError(t, err) + + doc, err := CreateDIDDHTDID(pubKey.(ed25519.PublicKey), CreateDIDDHTOpts{}) + require.NoError(t, err) + require.NotEmpty(t, doc) + + var expectedDIDDocument did.Document + retrieveTestVectorAs(t, vector1DIDDocument, &expectedDIDDocument) + assert.EqualValues(t, expectedDIDDocument, *doc) + + didID := DHT(doc.ID) + packet, err := didID.ToDNSPacket(*doc, nil) + require.NoError(t, err) + require.NotEmpty(t, packet) + + var expectedDNSRecords map[string]testVectorDNSRecord + retrieveTestVectorAs(t, vector1DNSRecords, &expectedDNSRecords) + + for _, record := range packet.Answer { + expectedRecord, ok := expectedDNSRecords[record.Header().Name] + require.True(t, ok) + + s := record.String() + assert.Contains(t, s, expectedRecord.RecordType) + assert.Contains(t, s, expectedRecord.TTL) + assert.Contains(t, s, expectedRecord.Record) + } + }) + + t.Run("test vector 2", func(t *testing.T) { + var pubKeyJWK jwx.PublicKeyJWK + retrieveTestVectorAs(t, vector1PublicKeyJWK1, &pubKeyJWK) + + pubKey, err := pubKeyJWK.ToPublicKey() + require.NoError(t, err) + + var secpJWK jwx.PublicKeyJWK + retrieveTestVectorAs(t, vector2PublicKeyJWK2, &secpJWK) + + doc, err := CreateDIDDHTDID(pubKey.(ed25519.PublicKey), CreateDIDDHTOpts{ + VerificationMethods: []VerificationMethod{ + { + VerificationMethod: did.VerificationMethod{ + ID: secpJWK.KID, + Type: "JsonWebKey2020", + PublicKeyJWK: &secpJWK, + }, + Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, + }, + }, + Services: []did.Service{ + { + ID: "service-1", + Type: "TestService", + ServiceEndpoint: "https://test-service.com", + }, + }, + }) + require.NoError(t, err) + require.NotEmpty(t, doc) + + var expectedDIDDocument did.Document + retrieveTestVectorAs(t, vector2DIDDocument, &expectedDIDDocument) + assert.EqualValues(t, expectedDIDDocument, *doc) + + didID := DHT(doc.ID) + packet, err := didID.ToDNSPacket(*doc, []TypeIndex{1, 2, 3}) + require.NoError(t, err) + require.NotEmpty(t, packet) + + var expectedDNSRecords map[string]testVectorDNSRecord + retrieveTestVectorAs(t, vector2DNSRecords, &expectedDNSRecords) + + println(packet.String()) + for _, record := range packet.Answer { + expectedRecord, ok := expectedDNSRecords[record.Header().Name] + require.True(t, ok) + + s := record.String() + assert.Contains(t, s, expectedRecord.RecordType) + assert.Contains(t, s, expectedRecord.TTL) + assert.Contains(t, s, expectedRecord.Record) + } }) } diff --git a/impl/internal/did/testdata/vector-1-did-document.json b/impl/internal/did/testdata/vector-1-did-document.json new file mode 100644 index 00000000..a98f4cbf --- /dev/null +++ b/impl/internal/did/testdata/vector-1-did-document.json @@ -0,0 +1,21 @@ +{ + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "verificationMethod": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0", + "type": "JsonWebKey2020", + "controller": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" + } + } + ], + "authentication": ["#0"], + "assertionMethod": ["#0"], + "capabilityInvocation": ["#0"], + "capabilityDelegation": ["#0"] +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-1-dns-records.json b/impl/internal/did/testdata/vector-1-dns-records.json new file mode 100644 index 00000000..1244933e --- /dev/null +++ b/impl/internal/did/testdata/vector-1-dns-records.json @@ -0,0 +1,12 @@ +{ + "_did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "vm=k0;auth=k0;asm=k0;inv=k0;del=k0" + }, + "_k0._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=0,t=0,k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + } +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-1-public-key-jwk-1.json b/impl/internal/did/testdata/vector-1-public-key-jwk-1.json new file mode 100644 index 00000000..eae78bb9 --- /dev/null +++ b/impl/internal/did/testdata/vector-1-public-key-jwk-1.json @@ -0,0 +1,7 @@ +{ + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-2-did-document.json b/impl/internal/did/testdata/vector-2-did-document.json new file mode 100644 index 00000000..761d69bb --- /dev/null +++ b/impl/internal/did/testdata/vector-2-did-document.json @@ -0,0 +1,41 @@ +{ + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "verificationMethod": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0", + "type": "JsonWebKey2020", + "controller": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" + } + }, + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw", + "type": "JsonWebKey2020", + "controller": "", + "publicKeyJwk": { + "kty": "EC", + "crv": "secp256k1", + "x": "1_o0IKHGNamet8-3VYNUTiKlhVK-LilcKrhJSPHSNP0", + "y": "qzU8qqh0wKB6JC_9HCu8pHE-ZPkDpw4AdJ-MsV2InVY", + "alg": "ES256K", + "kid": "0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw" + } + } + ], + "authentication": ["#0"], + "assertionMethod": ["#0", "#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw"], + "capabilityInvocation": ["#0", "#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw"], + "capabilityDelegation": ["#0"], + "service": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#service-1", + "type": "TestService", + "serviceEndpoint": "https://test-service.com" + } + ] +} \ 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 new file mode 100644 index 00000000..a54202a6 --- /dev/null +++ b/impl/internal/did/testdata/vector-2-dns-records.json @@ -0,0 +1,27 @@ +{ + "_did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "vm=k0,k1;svc=s0;auth=k0;asm=k0,k1;inv=k0,k1;del=k0" + }, + "_k0._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=0,t=0,k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE" + }, + "_k1._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw,t=1,k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9" + }, + "_s0._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=service-1,t=TestService,uri=https://test-service.com" + }, + "_typ._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=1,2,3" + } +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-2-public-key-jwk-2.json b/impl/internal/did/testdata/vector-2-public-key-jwk-2.json new file mode 100644 index 00000000..35dcf9fa --- /dev/null +++ b/impl/internal/did/testdata/vector-2-public-key-jwk-2.json @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "crv": "secp256k1", + "x": "1_o0IKHGNamet8-3VYNUTiKlhVK-LilcKrhJSPHSNP0", + "y": "qzU8qqh0wKB6JC_9HCu8pHE-ZPkDpw4AdJ-MsV2InVY", + "alg": "ES256K", + "kid": "0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw" +} \ No newline at end of file diff --git a/impl/internal/did/testdata_test.go b/impl/internal/did/testdata_test.go new file mode 100644 index 00000000..c870a4b6 --- /dev/null +++ b/impl/internal/did/testdata_test.go @@ -0,0 +1,37 @@ +package did + +import ( + "embed" + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +var ( + //go:embed testdata + testData embed.FS +) + +const ( + vector1PublicKeyJWK1 string = "vector-1-public-key-jwk-1.json" + vector1DIDDocument string = "vector-1-did-document.json" + vector1DNSRecords string = "vector-1-dns-records.json" + + vector2PublicKeyJWK2 string = "vector-2-public-key-jwk-2.json" + vector2DIDDocument string = "vector-2-did-document.json" + vector2DNSRecords string = "vector-2-dns-records.json" +) + +func getTestData(fileName string) ([]byte, error) { + return testData.ReadFile("testdata/" + fileName) +} + +// retrieveTestVectorAs retrieves a test vector from the testdata folder and unmarshals it into the given interface +func retrieveTestVectorAs(t *testing.T, fileName string, output interface{}) { + t.Helper() + testDataBytes, err := getTestData(fileName) + require.NoError(t, err) + err = json.Unmarshal(testDataBytes, output) + require.NoError(t, err) +} diff --git a/impl/pkg/dht/pkarr_test.go b/impl/pkg/dht/pkarr_test.go index 284d1089..1f3566c4 100644 --- a/impl/pkg/dht/pkarr_test.go +++ b/impl/pkg/dht/pkarr_test.go @@ -7,7 +7,6 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto" "github.com/TBD54566975/ssi-sdk/crypto/jwx" didsdk "github.com/TBD54566975/ssi-sdk/did" - "github.com/TBD54566975/ssi-sdk/did/ion" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -67,7 +66,7 @@ func TestGetPutDIDDHT(t *testing.T) { pubKey, _, err := crypto.GenerateSECP256k1Key() require.NoError(t, err) - pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK("key1", pubKey) + pubKeyJWK, err := jwx.PublicKeyToPublicKeyJWK(nil, pubKey) require.NoError(t, err) opts := did.CreateDIDDHTOpts{ @@ -79,7 +78,7 @@ func TestGetPutDIDDHT(t *testing.T) { Controller: "did:dht:123456789abcdefghi", PublicKeyJWK: pubKeyJWK, }, - Purposes: []ion.PublicKeyPurpose{ion.AssertionMethod, ion.CapabilityInvocation}, + Purposes: []didsdk.PublicKeyPurpose{didsdk.AssertionMethod, didsdk.CapabilityInvocation}, }, }, Services: []didsdk.Service{ diff --git a/spec/spec.md b/spec/spec.md index d1359333..22711e7b 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -3,9 +3,9 @@ The DID DHT Method Specification 1.0 **Specification Status**: Working Draft -**Latest Draft:** [tbd54566975.github.io/did-dht-method](https://tbd54566975.github.io/did-dht-method) +**Latest Draft:** [https://did-dht.com](https://did-dht.com) -**Registry:** [DID DHT Method Specification Registry](registry/index.html) +**Registry:** [https://did-dht.com/registry](https://did-dht.com/registry) **Latest Update:** November 20, 2023 @@ -21,6 +21,36 @@ The DID DHT Method Specification 1.0 ## Abstract A [DID Method](https://www.w3.org/TR/did-core/) based on [Pkarr](https://github.com/nuhvi/pkarr), identified by `did:dht`. + + +:----: | +DID DHT | +[Pkarr](https://github.com/nuhvi/pkarr)| +[Mainline DHT](https://en.wikipedia.org/wiki/Mainline_DHT) | + [[ref:Pkarr]] stands for **P**ublic **K**ey **A**ddressable **R**esource **R**ecords. [[ref:Pkarr]] makes use of the [Mainline DHT](https://en.wikipedia.org/wiki/Mainline_DHT), specifically [[ref:BEP44]] to store records. This DID method uses [[ref:Pkarr]] to store _DID Documents_. @@ -637,9 +667,154 @@ The security of data within the [[ref:Mainline DHT]] which relies on mutable rec ### Test Vectors -::: issue -[](https://github.com/TBD54566975/did-dht-method/issues/37) -::: +#### Vector 1 + +A minimal DID Document. + +**Identity Public Key JWK:** + +```json +{ + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" +} +``` + +**DID Document:** + +```json +{ + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "verificationMethod": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0", + "type": "JsonWebKey2020", + "controller": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" + } + } + ], + "authentication": ["#0"], + "assertionMethod": ["#0"], + "capabilityInvocation": ["#0"], + "capabilityDelegation": ["#0"] +} +``` + +**DNS Resource Records:** + +| Name | Type | TTL | Rdata | +| --------- | ---- | ---- | ----------- | +| _did. | TXT | 7200 | vm=k0;auth=k0;asm=k0;inv=k0;del=k0 | +| _k0._did. | TXT | 7200 | id=0,t=0,k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE | + + +#### Vector 2 + +A DID Document with two keys ([[ref:Identity Key]] and a secp256k1 key), a service endpoint, and two types to index. + +**Identity Public Key JWK:** + +```json +{ + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" +} +``` + +**secp256k1 Public Key JWK:** + +```json +{ + "kty": "EC", + "crv": "secp256k1", + "x": "1_o0IKHGNamet8-3VYNUTiKlhVK-LilcKrhJSPHSNP0", + "y": "qzU8qqh0wKB6JC_9HCu8pHE-ZPkDpw4AdJ-MsV2InVY", + "alg": "ES256K", + "kid": "0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw" +} +``` + +**Key Purposes:** Assertion Method, Capability Invocation. + +**Service:** + +```json +{ + "id": "service-1", + "type": "TestService", + "serviceEndpoint": "https://test-service.com" +} +``` + +**Types:** 1, 2, 3. + +**DID Document:** + +```json +{ + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "verificationMethod": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0", + "type": "JsonWebKey2020", + "controller": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", + "alg": "EdDSA", + "kid": "0" + } + }, + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw", + "type": "JsonWebKey2020", + "controller": "", + "publicKeyJwk": { + "kty": "EC", + "crv": "secp256k1", + "x": "1_o0IKHGNamet8-3VYNUTiKlhVK-LilcKrhJSPHSNP0", + "y": "qzU8qqh0wKB6JC_9HCu8pHE-ZPkDpw4AdJ-MsV2InVY", + "alg": "ES256K", + "kid": "0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw" + } + } + ], + "authentication": ["#0"], + "assertionMethod": ["#0", "#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw"], + "capabilityInvocation": ["#0", "#0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw"], + "capabilityDelegation": ["#0"], + "service": [ + { + "id": "did:dht:cyuoqaf7itop8ohww4yn5ojg13qaq83r9zihgqntc5i9zwrfdfoo#service-1", + "type": "TestService", + "serviceEndpoint": "https://test-service.com" + } + ] +} +``` + +**DNS Resource Records:** + +| Name | Type | TTL | Rdata | +| --------- | ---- | ---- | ----------- | +| _did. | TXT | 7200 | vm=k0,k1;svc=s0;auth=k0;asm=k0,k1;inv=k0,k1;del=k0 | +| _k0.did. | TXT | 7200 | id=0,t=0,k=YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE | +| _k1.did. | TXT | 7200 | id=0GkvkdCGu3DL7Mkv0W1DhTMCBT9-z0CkFqZoJQtw7vw,t=1,k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9 | +| _s0.did. | TXT | 7200 | id=service-1,t=TestService,uri=https://test-service.com | +| _typ.did. | TXT | 7200 | id=1,2,3 | ### Open API Definition diff --git a/spec/specs.json b/spec/specs.json index 7955a967..2ddc1f91 100644 --- a/spec/specs.json +++ b/spec/specs.json @@ -13,7 +13,7 @@ } }, { - "title": "The DID DHT Method Registry", + "title": "The DID DHT Method Specification Registry", "spec_directory": "./registry", "output_path": "./build/registry", "logo": "https://avatars.githubusercontent.com/u/94492651",