diff --git a/impl/go.mod b/impl/go.mod index 3b3e0e05..13658e50 100644 --- a/impl/go.mod +++ b/impl/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( github.com/BurntSushi/toml v1.3.2 - github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240321215515-97ccd06a631d + github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240402005820-2c6b20991baa github.com/allegro/bigcache/v3 v3.1.0 github.com/anacrolix/dht/v2 v2.21.1 github.com/anacrolix/log v0.15.2 diff --git a/impl/go.sum b/impl/go.sum index 0bf2a8ad..8c461a06 100644 --- a/impl/go.sum +++ b/impl/go.sum @@ -24,8 +24,12 @@ 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.20240321215515-97ccd06a631d h1:lEekCCpwjxtQBNNUoUmPiDg35t3quQzDgtetug5xbx4= -github.com/TBD54566975/ssi-sdk v0.0.4-alpha.0.20240321215515-97ccd06a631d/go.mod h1:UoNlAhXuPb1VxsAkNbLyr4XYeyHhLvcwSbkmsaOeGjM= +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= github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o= github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= diff --git a/impl/internal/did/did.go b/impl/internal/did/did.go index 8470d79c..19897f07 100644 --- a/impl/internal/did/did.go +++ b/impl/internal/did/did.go @@ -11,6 +11,7 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/cryptosuite" "github.com/TBD54566975/ssi-sdk/did" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/miekg/dns" "github.com/tv42/zbase32" ) @@ -22,9 +23,8 @@ type ( const ( // Prefix did:dht prefix - Prefix = "did:dht" - DHTMethod did.Method = "dht" - JSONWebKeyType cryptosuite.LDKeyType = "JsonWebKey" + Prefix = "did:dht" + DHTMethod did.Method = "dht" // Version corresponds to the version fo the specification https://did-dht.com/#dids-as-dns-records Version int = 0 @@ -137,7 +137,7 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu if seenIDs[vm.VerificationMethod.ID] { return nil, fmt.Errorf("verification method id %s is not unique", vm.VerificationMethod.ID) } - if vm.VerificationMethod.Type != JSONWebKeyType { + if vm.VerificationMethod.Type != cryptosuite.JSONWebKeyType { return nil, fmt.Errorf("verification method type %s is not supported", vm.VerificationMethod.Type) } if vm.VerificationMethod.PublicKeyJWK == nil { @@ -204,12 +204,14 @@ func CreateDIDDHTDID(pubKey ed25519.PublicKey, opts CreateDIDDHTOpts) (*did.Docu // create the did document kid := "0" key0JWK, err := jwx.PublicKeyToPublicKeyJWK(&kid, pubKey) + // temporary workaround until https://github.com/TBD54566975/ssi-sdk/issues/520 is in place + key0JWK.ALG = string(crypto.Ed25519DSA) if err != nil { return nil, err } vm0 := did.VerificationMethod{ ID: id + "#0", - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, Controller: id, PublicKeyJWK: key0JWK, } @@ -287,9 +289,9 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex) (*dns.Msg, error) recordIdentifier := fmt.Sprintf("k%d", i) keyLookup[vm.ID] = recordIdentifier - keyType := keyTypeByAlg(crypto.SignatureAlgorithm(vm.PublicKeyJWK.ALG)) + keyType := keyTypeForJWK(*vm.PublicKeyJWK) if keyType < 0 { - return nil, fmt.Errorf("unsupported key type given alg: %s", vm.PublicKeyJWK.ALG) + return nil, fmt.Errorf("+unsupported key type given alg: %s", vm.PublicKeyJWK.ALG) } // convert the public key to a base64url encoded string @@ -307,6 +309,13 @@ func (d DHT) ToDNSPacket(doc did.Document, types []TypeIndex) (*dns.Msg, error) keyBase64URL := base64.RawURLEncoding.EncodeToString(pubKeyBytes) vmKeyFragment := vm.ID[strings.LastIndex(vm.ID, "#")+1:] txtRecord := fmt.Sprintf("id=%s;t=%d;k=%s", vmKeyFragment, keyType, keyBase64URL) + + // only include the alg if it's not the default alg for the key type + forKeyType := algIsDefaultForKeyType(*vm.PublicKeyJWK) + if !forKeyType { + txtRecord += fmt.Sprintf(";alg=%s", vm.PublicKeyJWK.ALG) + } + // note the controller if it differs from the DID if vm.Controller != doc.ID { // handle the case where the controller of the identity key is not the DID itself @@ -531,7 +540,7 @@ func (d DHT) FromDNSPacket(msg *dns.Msg) (*did.Document, []TypeIndex, error) { vm := did.VerificationMethod{ ID: d.String() + "#" + vmID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, Controller: d.String(), PublicKeyJWK: pubKeyJWK, } @@ -647,6 +656,30 @@ func parseTxtData(data string) map[string]string { return result } +// algIsDefaultForKeyType returns true if the given JWK ALG is the default for the given key type +// according to the key type index https://did-dht.com/registry/#key-type-index +func algIsDefaultForKeyType(jwk jwx.PublicKeyJWK) bool { + // Ed25519 : Ed25519 + if jwk.CRV == crypto.Ed25519.String() && jwk.KTY == jwa.OKP.String() { + return jwk.ALG == string(crypto.Ed25519DSA) + } + // secp256k1 : ES256K + if jwk.CRV == crypto.SECP256k1.String() && jwk.KTY == jwa.EC.String() { + return jwk.ALG == string(crypto.ES256K) + } + // P-256 : ES256 + if jwk.CRV == crypto.P256.String() && jwk.KTY == jwa.EC.String() { + return jwk.ALG == string(crypto.ES256) + } + // X25519 : ECDH-ES+A256KW + if jwk.CRV == crypto.X25519.String() && jwk.KTY == jwa.OKP.String() { + return jwk.ALG == string(crypto.ECDHESA256KW) + } + return false +} + +// keyTypeLookUp returns the key type for the given key type index +// https://did-dht.com/registry/#key-type-index func keyTypeLookUp(keyType string) crypto.KeyType { switch keyType { case "0": @@ -655,20 +688,31 @@ func keyTypeLookUp(keyType string) crypto.KeyType { return crypto.SECP256k1 case "2": return crypto.P256 + case "3": + return crypto.X25519 default: return "" } } -func keyTypeByAlg(alg crypto.SignatureAlgorithm) int { - switch alg { - case crypto.EdDSA: +// keyTypeForJWK returns the key type index for the given JWK according to the key type index +// https://did-dht.com/registry/#key-type-index +func keyTypeForJWK(jwk jwx.PublicKeyJWK) int { + // Ed25519 : Ed25519 : 0 + if jwk.CRV == crypto.Ed25519.String() && jwk.KTY == jwa.OKP.String() { return 0 - case crypto.ES256K: + } + // secp256k1 : ES256K : 1 + if jwk.CRV == crypto.SECP256k1.String() && jwk.KTY == jwa.EC.String() { return 1 - case crypto.ES256: + } + // P-256 : ES256 : 2 + if jwk.CRV == crypto.P256.String() && jwk.KTY == jwa.EC.String() { return 2 - default: - return -1 } + // X25519 : ECDH-ES+A256KW : 3 + if jwk.CRV == crypto.X25519.String() && jwk.KTY == jwa.OKP.String() { + return 3 + } + return -1 } diff --git a/impl/internal/did/did_test.go b/impl/internal/did/did_test.go index 2bd2f431..bd2b5a21 100644 --- a/impl/internal/did/did_test.go +++ b/impl/internal/did/did_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/TBD54566975/ssi-sdk/cryptosuite" "github.com/goccy/go-json" "github.com/TBD54566975/ssi-sdk/crypto" @@ -55,7 +56,7 @@ func TestGenerateDIDDHT(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: "key1", - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, Controller: "did:dht:123456789abcdefghi", PublicKeyJWK: pubKeyJWK, }, @@ -173,7 +174,7 @@ func TestToDNSPacket(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: "key1", - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: pubKeyJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -277,7 +278,7 @@ func TestVectors(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -323,6 +324,63 @@ func TestVectors(t *testing.T) { assert.Contains(t, s, expectedRecord.Record) } }) + + t.Run("test vector 3", func(t *testing.T) { + var pubKeyJWK jwx.PublicKeyJWK + retrieveTestVectorAs(t, vector3PublicKeyJWK1, &pubKeyJWK) + + pubKey, err := pubKeyJWK.ToPublicKey() + require.NoError(t, err) + + var x25519JWK jwx.PublicKeyJWK + retrieveTestVectorAs(t, vector3PublicKeyJWK2, &x25519JWK) + + doc, err := CreateDIDDHTDID(pubKey.(ed25519.PublicKey), CreateDIDDHTOpts{ + VerificationMethods: []VerificationMethod{ + { + VerificationMethod: did.VerificationMethod{ + ID: x25519JWK.KID, + Type: cryptosuite.JSONWebKeyType, + PublicKeyJWK: &x25519JWK, + }, + Purposes: []did.PublicKeyPurpose{did.KeyAgreement}, + }, + }, + }) + require.NoError(t, err) + require.NotEmpty(t, doc) + + var expectedDIDDocument did.Document + retrieveTestVectorAs(t, vector3DIDDocument, &expectedDIDDocument) + + docJSON, err := json.Marshal(doc) + require.NoError(t, err) + + expectedDIDDocJSON, err := json.Marshal(expectedDIDDocument) + require.NoError(t, err) + + assert.JSONEq(t, string(expectedDIDDocJSON), string(docJSON)) + + didID := DHT(doc.ID) + packet, err := didID.ToDNSPacket(*doc, nil) + require.NoError(t, err) + require.NotEmpty(t, packet) + + println(packet.String()) + + var expectedDNSRecords map[string]testVectorDNSRecord + retrieveTestVectorAs(t, vector3DNSRecords, &expectedDNSRecords) + + for _, record := range packet.Answer { + expectedRecord, ok := expectedDNSRecords[record.Header().Name] + require.True(t, ok, "record not found: %s", record.Header().Name) + + s := record.String() + assert.Contains(t, s, expectedRecord.RecordType) + assert.Contains(t, s, expectedRecord.TTL) + assert.Contains(t, s, expectedRecord.Record) + } + }) } func TestMisc(t *testing.T) { @@ -353,7 +411,7 @@ func TestMisc(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -378,7 +436,7 @@ func TestMisc(t *testing.T) { VerificationMethods: []VerificationMethod{ { VerificationMethod: did.VerificationMethod{ - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -404,7 +462,7 @@ func TestMisc(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -430,7 +488,7 @@ func TestMisc(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: "#key-1", - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -456,7 +514,7 @@ func TestMisc(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.Authentication, did.KeyAgreement, did.CapabilityDelegation}, @@ -493,7 +551,7 @@ func TestCreationFailures(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: "#0", - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -518,7 +576,7 @@ func TestCreationFailures(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -526,7 +584,7 @@ func TestCreationFailures(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -578,7 +636,7 @@ func TestCreationFailures(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: nil, }, Purposes: []did.PublicKeyPurpose{did.AssertionMethod, did.CapabilityInvocation}, @@ -604,7 +662,7 @@ func TestCreationFailures(t *testing.T) { { VerificationMethod: did.VerificationMethod{ ID: secpJWK.KID, - Type: JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, PublicKeyJWK: &secpJWK, }, Purposes: []did.PublicKeyPurpose{"fake purpose"}, diff --git a/impl/internal/did/testdata/vector-1-did-document.json b/impl/internal/did/testdata/vector-1-did-document.json index 1a2c863b..11361fc1 100644 --- a/impl/internal/did/testdata/vector-1-did-document.json +++ b/impl/internal/did/testdata/vector-1-did-document.json @@ -9,7 +9,7 @@ "kty": "OKP", "crv": "Ed25519", "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", - "alg": "EdDSA", + "alg": "Ed25519", "kid": "0" } } 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 index eae78bb9..b7869103 100644 --- a/impl/internal/did/testdata/vector-1-public-key-jwk-1.json +++ b/impl/internal/did/testdata/vector-1-public-key-jwk-1.json @@ -2,6 +2,6 @@ "kty": "OKP", "crv": "Ed25519", "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", - "alg": "EdDSA", + "alg": "Ed25519", "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 index 77dacc8c..8c303a82 100644 --- a/impl/internal/did/testdata/vector-2-did-document.json +++ b/impl/internal/did/testdata/vector-2-did-document.json @@ -11,7 +11,7 @@ "kty": "OKP", "crv": "Ed25519", "x": "YCcHYL2sYNPDlKaALcEmll2HHyT968M4UWbr-9CFGWE", - "alg": "EdDSA", + "alg": "Ed25519", "kid": "0" } }, diff --git a/impl/internal/did/testdata/vector-3-did-document.json b/impl/internal/did/testdata/vector-3-did-document.json new file mode 100644 index 00000000..fb40938f --- /dev/null +++ b/impl/internal/did/testdata/vector-3-did-document.json @@ -0,0 +1,44 @@ +{ + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "verificationMethod": [ + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0", + "type": "JsonWebKey", + "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g", + "alg": "Ed25519", + "kid": "0" + } + }, + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ", + "type": "JsonWebKey", + "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "publicKeyJwk": { + "kty": "OKP", + "crv": "X25519", + "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4", + "alg": "ECDH-ES+A128KW", + "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ" + } + } + ], + "authentication": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "assertionMethod": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "keyAgreement": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ" + ], + "capabilityInvocation": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "capabilityDelegation": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ] +} \ 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 new file mode 100644 index 00000000..9f51f583 --- /dev/null +++ b/impl/internal/did/testdata/vector-3-dns-records.json @@ -0,0 +1,17 @@ +{ + "_did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "v=0;vm=k0,k1;auth=k0;asm=k0;agm=k1;inv=k0;del=k0" + }, + "_k0._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=0;t=0;k=sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g" + }, + "_k1._did.": { + "type": "TXT", + "ttl": "7200", + "rdata": "id=WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ;t=3;k=3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4;alg=ECDH-ES+A128KW" + } +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-3-public-key-jwk-1.json b/impl/internal/did/testdata/vector-3-public-key-jwk-1.json new file mode 100644 index 00000000..6c131cd8 --- /dev/null +++ b/impl/internal/did/testdata/vector-3-public-key-jwk-1.json @@ -0,0 +1,7 @@ +{ + "kty": "OKP", + "crv": "Ed25519", + "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g", + "alg": "Ed25519", + "kid": "0" +} \ No newline at end of file diff --git a/impl/internal/did/testdata/vector-3-public-key-jwk-2.json b/impl/internal/did/testdata/vector-3-public-key-jwk-2.json new file mode 100644 index 00000000..fcaab691 --- /dev/null +++ b/impl/internal/did/testdata/vector-3-public-key-jwk-2.json @@ -0,0 +1,7 @@ +{ + "kty": "OKP", + "crv": "X25519", + "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4", + "alg": "ECDH-ES+A128KW", + "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ" +} \ No newline at end of file diff --git a/impl/internal/did/testdata_test.go b/impl/internal/did/testdata_test.go index 92d4db52..1e39a36a 100644 --- a/impl/internal/did/testdata_test.go +++ b/impl/internal/did/testdata_test.go @@ -22,6 +22,11 @@ const ( vector2PublicKeyJWK2 string = "vector-2-public-key-jwk-2.json" vector2DIDDocument string = "vector-2-did-document.json" vector2DNSRecords string = "vector-2-dns-records.json" + + vector3PublicKeyJWK1 string = "vector-3-public-key-jwk-1.json" + vector3PublicKeyJWK2 string = "vector-3-public-key-jwk-2.json" + vector3DIDDocument string = "vector-3-did-document.json" + vector3DNSRecords string = "vector-3-dns-records.json" ) func getTestData(fileName string) ([]byte, error) { diff --git a/impl/pkg/dht/pkarr_test.go b/impl/pkg/dht/pkarr_test.go index 75d96d0d..0d4a0be8 100644 --- a/impl/pkg/dht/pkarr_test.go +++ b/impl/pkg/dht/pkarr_test.go @@ -6,6 +6,7 @@ import ( "github.com/TBD54566975/ssi-sdk/crypto" "github.com/TBD54566975/ssi-sdk/crypto/jwx" + "github.com/TBD54566975/ssi-sdk/cryptosuite" didsdk "github.com/TBD54566975/ssi-sdk/did" "github.com/miekg/dns" "github.com/stretchr/testify/assert" @@ -71,7 +72,7 @@ func TestGetPutDIDDHT(t *testing.T) { { VerificationMethod: didsdk.VerificationMethod{ ID: "key1", - Type: did.JSONWebKeyType, + Type: cryptosuite.JSONWebKeyType, Controller: "did:dht:123456789abcdefghi", PublicKeyJWK: pubKeyJWK, }, diff --git a/spec/spec.md b/spec/spec.md index 122319e9..4fa4f623 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:** March 29, 2024 +**Latest Update:** April 1, 2024 **Editors:** ~ [Gabe Cohen](https://github.com/decentralgabe) @@ -1078,8 +1078,8 @@ A minimal DID Document. #### Vector 2 -A DID Document with two keys ([[ref:Identity Key]] and an uncompressed secp256k1 key), a service with multiple endpoints, a gateway, -two types to index, an aka, and controller properties. +A DID Document with two keys ([[ref:Identity Key]] and an uncompressed secp256k1 key), a service with multiple +endpoints, a gateway, two types to index, an aka, and controller properties. **Identity Public Key JWK:** @@ -1191,10 +1191,101 @@ With controller: `did:dht:i9xkp8ddcbcg8jwq54ox699wuzxyifsqx4jru45zodqu453ksz6y`. | _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 | -| _k1.did. | TXT | 7200 | t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9;a=ES256K | +| _k1.did. | TXT | 7200 | t=1;k=Atf6NCChxjWpnrfPt1WDVE4ipYVSvi4pXCq4SUjx0jT9 | | _s0.did. | TXT | 7200 | id=service-1;t=TestService;se=https://test-service.com/1,https://test-service.com/2 | | _typ.did. | TXT | 7200 | id=1,2,3 | +#### 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 a gateway record. + +**Identity Public Key JWK:** + +```json +{ + "kid": "0", + "alg": "Ed25519", + "crv": "Ed25519", + "kty": "OKP", + "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g" +} +``` + +**X25519 Public Key JWK:** + +```json +{ + "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ", + "alg": "ECDH-ES+A128KW", + "crv": "X25519", + "kty": "OKP", + "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4" +} +``` + +**Key Purposes:** `Key Agreement`. + +**Gateway:**: `gateway1.example-did-dht-gateway.com.`. + +**DID Document:** + +```json +{ + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "verificationMethod": [ + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0", + "type": "JsonWebKey", + "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "publicKeyJwk": { + "kid": "0", + "alg": "Ed25519", + "crv": "Ed25519", + "kty": "OKP", + "x": "sTyTLYw-n1NI9X-84NaCuis1wZjAA8lku6f6Et5201g" + } + }, + { + "id": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ", + "type": "JsonWebKey", + "controller": "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy", + "publicKeyJwk": { + "kid": "WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ", + "alg": "ECDH-ES+A128KW", + "crv": "X25519", + "kty": "OKP", + "x": "3POE0_i2mGeZ2qiQCA3KcLfi1fZo0311CXFSIwt1nB4" + } + } + ], + "authentication": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "assertionMethod": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "keyAgreement": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#WVy5IWMa36AoyAXZDvPd5j9zxt2t-GjifDEV-DwgIdQ" + ], + "capabilityInvocation": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ], + "capabilityDelegation": [ + "did:dht:sr6jgmcc84xig18ix66qbiwnzeiumocaaybh13f5w97bfzus4pcy#0" + ] +} +``` + +**DNS Resource 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 | +| _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;alg=ECDH-ES+A128KW | + ### Open API Definition ```yaml