diff --git a/pkg/orbclient/doctransformer/transformer.go b/pkg/orbclient/doctransformer/transformer.go index bceab6c04..30031d893 100644 --- a/pkg/orbclient/doctransformer/transformer.go +++ b/pkg/orbclient/doctransformer/transformer.go @@ -7,10 +7,12 @@ SPDX-License-Identifier: Apache-2.0 package diddoctransformer import ( + "bytes" "encoding/json" "fmt" "strings" + "github.com/trustbloc/sidetree-core-go/pkg/canonicalizer" "github.com/trustbloc/sidetree-core-go/pkg/document" ) @@ -166,3 +168,41 @@ func minimum(a, b int) int { return b } + +// Equal transforms documents into canonical form and compares them. +// Exclude tags (optional) will be removed from document before comparison. +func Equal(doc1, doc2 document.Document, excludeTags ...string) error { + for _, tag := range excludeTags { + delete(doc1, tag) + delete(doc2, tag) + } + + doc1Bytes, err := canonicalizer.MarshalCanonical(doc1) + if err != nil { + return err + } + + doc2Bytes, err := canonicalizer.MarshalCanonical(doc2) + if err != nil { + return err + } + + if !bytes.Equal(doc1Bytes, doc2Bytes) { + return fmt.Errorf("documents [%s] and [%s] do not match", string(doc1Bytes), string(doc2Bytes)) + } + + return nil +} + +// VerifyWebDocumentFromOrbDocument will create web document from orb resolution result and compare that web document +// with provided web document for equality. +func VerifyWebDocumentFromOrbDocument(webRR, orbRR *document.ResolutionResult, excludeTags ...string) error { + webDID := webRR.Document.ID() + + webDocFromOrbDoc, err := WebDocumentFromOrbDocument(webDID, orbRR) + if err != nil { + return err + } + + return Equal(webRR.Document, webDocFromOrbDoc, excludeTags...) +} diff --git a/pkg/orbclient/doctransformer/transformer_test.go b/pkg/orbclient/doctransformer/transformer_test.go index 776443076..f3536b8ad 100644 --- a/pkg/orbclient/doctransformer/transformer_test.go +++ b/pkg/orbclient/doctransformer/transformer_test.go @@ -24,7 +24,7 @@ const ( testUnpublishedSuffix = "EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw" ) -func TestResolveHandler_Resolve(t *testing.T) { +func TestWebDocumentFromOrbDocument(t *testing.T) { t.Run("success - published did with also known as", func(t *testing.T) { rr, err := getTestResolutionResult() require.NoError(t, err) @@ -125,6 +125,24 @@ func TestResolveHandler_Resolve(t *testing.T) { "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") }) + t.Run("success - also known as is string array", func(t *testing.T) { + rr, err := getTestResolutionResult() + require.NoError(t, err) + + rr.Document[document.AlsoKnownAs] = document.StringArray(rr.Document[document.AlsoKnownAs]) + + response, err := WebDocumentFromOrbDocument(webDID, rr) + require.NoError(t, err) + require.NotNil(t, response) + + require.Equal(t, "did:web:orb.domain1.com:scid:"+testSuffix, response.ID()) + require.Equal(t, response[document.AlsoKnownAs].([]string)[0], "https://myblog.example/") + require.Equal(t, response[document.AlsoKnownAs].([]string)[1], + "did:orb:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") + require.Equal(t, response[document.AlsoKnownAs].([]string)[2], + "did:orb:hl:uEiAZPHwtTJ7-rG0nBeD6nqyL3Xsg1IA2BX1n9iGlv5yBJQ:uoQ-CeEtodHRwczovL29yYi5kb21haW4xLmNvbS9jYXMvdUVpQVpQSHd0VEo3LXJHMG5CZUQ2bnF5TDNYc2cxSUEyQlgxbjlpR2x2NXlCSlF4QmlwZnM6Ly9iYWZrcmVpYXpocjZjMnRlNjcyd2cyanlmNGQ1ajVsZWwzdjVzYnZlYWd5Y3gyejd3ZWdzMzdoZWJldQ:EiBmPHOGe4f8L4_ZVgBg5V343_nDSSX3l6X-9VKRhE57Tw") //nolint:lll + }) + t.Run("success - equivalent ID is string array", func(t *testing.T) { rr, err := getTestResolutionResult() require.NoError(t, err) @@ -187,10 +205,98 @@ func TestResolveHandler_Resolve(t *testing.T) { }) } +func TestVerifyWebDocumentFromOrbDocument(t *testing.T) { + t.Run("success", func(t *testing.T) { + webRR, err := getResolutionResult(webResponse) + require.NoError(t, err) + + orbRR, err := getResolutionResult(orbResponse) + require.NoError(t, err) + + err = VerifyWebDocumentFromOrbDocument(webRR, orbRR) + require.NoError(t, err) + }) + + t.Run("error - documents do not match", func(t *testing.T) { + webRR, err := getResolutionResult(`{"didDocument": {}}`) + require.NoError(t, err) + + orbRR, err := getResolutionResult(orbResponse) + require.NoError(t, err) + + err = VerifyWebDocumentFromOrbDocument(webRR, orbRR) + require.Error(t, err) + require.Contains(t, err.Error(), "do not match") + }) + + t.Run("error - parse also known as error", func(t *testing.T) { + webRR, err := getResolutionResult(`{}`) + require.NoError(t, err) + + orbRR, err := getResolutionResult(`{"didDocument": {"alsoKnownAs": 1}}`) + require.NoError(t, err) + + err = VerifyWebDocumentFromOrbDocument(webRR, orbRR) + require.Error(t, err) + require.Contains(t, err.Error(), "unexpected interface 'float64' for also known as") + }) +} + +func TestEqual(t *testing.T) { + t.Run("success", func(t *testing.T) { + doc1, err := getDocument(`{"id" : "some-id"}`) + require.NoError(t, err) + + doc2, err := getDocument(`{"id" : "some-id"}`) + require.NoError(t, err) + + err = Equal(doc1, doc2) + require.NoError(t, err) + }) + + t.Run("success - empty doc", func(t *testing.T) { + doc1, err := getDocument("{}") + require.NoError(t, err) + + doc2, err := getDocument("{}") + require.NoError(t, err) + + err = Equal(doc1, doc2) + require.NoError(t, err) + }) + + t.Run("success - with exclude tag", func(t *testing.T) { + doc1, err := getDocument(`{}`) + require.NoError(t, err) + + doc2, err := getDocument(`{"alsoKnownAs": ["did:web:hello.com"]}`) + require.NoError(t, err) + + err = Equal(doc1, doc2, "alsoKnownAs") + require.NoError(t, err) + }) + + t.Run("error - not equal", func(t *testing.T) { + doc1, err := getDocument(`{"id" : "some-id"}`) + require.NoError(t, err) + + doc2, err := getDocument(`{"id" : "other-id"}`) + require.NoError(t, err) + + err = Equal(doc1, doc2) + require.Error(t, err) + require.Contains(t, err.Error(), `documents [{"id":"some-id"}] and [{"id":"other-id"}] do not match`) + }) +} + func getTestResolutionResult() (*document.ResolutionResult, error) { + return getResolutionResult(didResolutionResult) +} + +func getResolutionResult(str string) (*document.ResolutionResult, error) { var docResolutionResult document.ResolutionResult - err := json.Unmarshal([]byte(didResolutionResult), &docResolutionResult) + err := json.Unmarshal([]byte(str), &docResolutionResult) if err != nil { return nil, err } @@ -198,6 +304,17 @@ func getTestResolutionResult() (*document.ResolutionResult, error) { return &docResolutionResult, nil } +func getDocument(str string) (document.Document, error) { + var doc document.Document + + err := json.Unmarshal([]byte(str), &doc) + if err != nil { + return nil, err + } + + return doc, nil +} + //nolint:lll var didResolutionResult = ` { @@ -358,3 +475,142 @@ var unpublishedDIDResolutionResult = ` } } }` + +// nolint:lll +var webResponse = ` +{ + "@context": "https://w3id.org/did-resolution/v1", + "didDocument": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "alsoKnownAs": [ + "https://myblog.example/", + "did:orb:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "did:orb:hl:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:uoQ-BeEtodHRwczovL29yYi5kb21haW4zLmNvbS9jYXMvdUVpQ3hGR0N6Z2QwZ1Rrb0xobkV6UDZBT3d2TThGSG40UVRUYjVZamxXNHVRSFE:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA" + ], + "assertionMethod": [ + "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#auth" + ], + "authentication": [ + "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#createKey" + ], + "id": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "service": [ + { + "id": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#didcomm", + "priority": 0, + "recipientKeys": [ + "9kQ8WK6mj32d3v6SZp6bzngPajta2KPMd92qjcQZ4bLG" + ], + "serviceEndpoint": "https://hub.example.com/.identity/did:example:0123456789abcdef/", + "type": "did-communication" + } + ], + "verificationMethod": [ + { + "controller": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "id": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#createKey", + "publicKeyJwk": { + "crv": "P-256", + "kty": "EC", + "x": "k2WMSkwqKWZR6imfF1Nv-OLJLhylNJMX1n8_dRGlYuE", + "y": "2ES0qDhNfbMe9CimiYj69zU60mhrXVwVlcwKwhW_DVs" + }, + "type": "JsonWebKey2020" + }, + { + "controller": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "id": "did:web:orb.domain3.com:scid:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#auth", + "publicKeyBase58": "VM6LMBqwetP9yLJo9C6nZkA4B4LwLA5ZkqeTstp8vdq", + "type": "Ed25519VerificationKey2018" + } + ] + } +}` + +// nolint:lll +var orbResponse = ` +{ + "@context": "https://w3id.org/did-resolution/v1", + "didDocument": { + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/jws-2020/v1", + "https://w3id.org/security/suites/ed25519-2018/v1" + ], + "alsoKnownAs": [ + "https://myblog.example/" + ], + "assertionMethod": [ + "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#auth" + ], + "authentication": [ + "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#createKey" + ], + "id": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "service": [ + { + "id": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#didcomm", + "priority": 0, + "recipientKeys": [ + "9kQ8WK6mj32d3v6SZp6bzngPajta2KPMd92qjcQZ4bLG" + ], + "serviceEndpoint": "https://hub.example.com/.identity/did:example:0123456789abcdef/", + "type": "did-communication" + } + ], + "verificationMethod": [ + { + "controller": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "id": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#createKey", + "publicKeyJwk": { + "crv": "P-256", + "kty": "EC", + "x": "k2WMSkwqKWZR6imfF1Nv-OLJLhylNJMX1n8_dRGlYuE", + "y": "2ES0qDhNfbMe9CimiYj69zU60mhrXVwVlcwKwhW_DVs" + }, + "type": "JsonWebKey2020" + }, + { + "controller": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "id": "did:orb:uAAA:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA#auth", + "publicKeyBase58": "VM6LMBqwetP9yLJo9C6nZkA4B4LwLA5ZkqeTstp8vdq", + "type": "Ed25519VerificationKey2018" + } + ] + }, + "didDocumentMetadata": { + "canonicalId": "did:orb:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "created": "2022-08-31T19:13:44Z", + "equivalentId": [ + "did:orb:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "did:orb:hl:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:uoQ-BeEtodHRwczovL29yYi5kb21haW4zLmNvbS9jYXMvdUVpQ3hGR0N6Z2QwZ1Rrb0xobkV6UDZBT3d2TThGSG40UVRUYjVZamxXNHVRSFE:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA", + "did:orb:https:shared.domain.com:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:EiAWS5rfPpy3KK_3l7_aPb4sZsCraVCxFM-SeEqWrVi0RA" + ], + "method": { + "anchorOrigin": "https://orb.domain3.com", + "published": true, + "publishedOperations": [ + { + "type": "create", + "operation": "eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJhZGQtYWxzby1rbm93bi1hcyIsInVyaXMiOlsiaHR0cHM6Ly9teWJsb2cuZXhhbXBsZS8iXX0seyJhY3Rpb24iOiJhZGQtcHVibGljLWtleXMiLCJwdWJsaWNLZXlzIjpbeyJpZCI6ImNyZWF0ZUtleSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImsyV01Ta3dxS1daUjZpbWZGMU52LU9MSkxoeWxOSk1YMW44X2RSR2xZdUUiLCJ5IjoiMkVTMHFEaE5mYk1lOUNpbWlZajY5elU2MG1oclhWd1ZsY3dLd2hXX0RWcyJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdLCJ0eXBlIjoiSnNvbldlYktleTIwMjAifSx7ImlkIjoiYXV0aCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJFZDI1NTE5Iiwia3R5IjoiT0tQIiwieCI6IkIwTDdEMmoydU9VMUNHeXR4aGtzOHVUU3hCZTFIWC1ISUtlWVUwVmZKelEiLCJ5IjoiIn0sInB1cnBvc2VzIjpbImFzc2VydGlvbk1ldGhvZCJdLCJ0eXBlIjoiRWQyNTUxOVZlcmlmaWNhdGlvbktleTIwMTgifV19LHsiYWN0aW9uIjoiYWRkLXNlcnZpY2VzIiwic2VydmljZXMiOlt7ImlkIjoiZGlkY29tbSIsInByaW9yaXR5IjowLCJyZWNpcGllbnRLZXlzIjpbIjlrUThXSzZtajMyZDN2NlNacDZiem5nUGFqdGEyS1BNZDkycWpjUVo0YkxHIl0sInNlcnZpY2VFbmRwb2ludCI6Imh0dHBzOi8vaHViLmV4YW1wbGUuY29tLy5pZGVudGl0eS9kaWQ6ZXhhbXBsZTowMTIzNDU2Nzg5YWJjZGVmLyIsInR5cGUiOiJkaWQtY29tbXVuaWNhdGlvbiJ9XX1dLCJ1cGRhdGVDb21taXRtZW50IjoiRWlCZkcxRjVjcmp6cE1pYzhZdG9DTXNPd0c2SlJ4eDhBZWx1dEhrVjV5UXp0ZyJ9LCJzdWZmaXhEYXRhIjp7ImFuY2hvck9yaWdpbiI6Imh0dHBzOi8vb3JiLmRvbWFpbjMuY29tIiwiZGVsdGFIYXNoIjoiRWlDYVc1SEI4MWxabWJYVE9FUDFLMVpLVmpGRmhoOVg4clZuSERMNHFBbzQxUSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQkJ0Ump3UmQ1N3JVYmlodGQ1NU5TMlVFR1Bvc0lqV3EtY1hWdHlQS0Z0OWcifSwidHlwZSI6ImNyZWF0ZSJ9", + "transactionTime": 1661973224, + "transactionNumber": 0, + "protocolVersion": 0, + "canonicalReference": "uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ", + "equivalentReferences": [ + "hl:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ:uoQ-BeEtodHRwczovL29yYi5kb21haW4zLmNvbS9jYXMvdUVpQ3hGR0N6Z2QwZ1Rrb0xobkV6UDZBT3d2TThGSG40UVRUYjVZamxXNHVRSFE", + "https:shared.domain.com:uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ" + ], + "anchorOrigin": "https://orb.domain3.com" + } + ], + "recoveryCommitment": "EiBBtRjwRd57rUbihtd55NS2UEGPosIjWq-cXVtyPKFt9g", + "updateCommitment": "EiBfG1F5crjzpMic8YtoCMsOwG6JRxx8AelutHkV5yQztg" + }, + "versionId": "uEiCxFGCzgd0gTkoLhnEzP6AOwvM8FHn4QTTb5YjlW4uQHQ" + } +}` diff --git a/test/bdd/did_orb_steps.go b/test/bdd/did_orb_steps.go index d9936c860..adbf853a2 100644 --- a/test/bdd/did_orb_steps.go +++ b/test/bdd/did_orb_steps.go @@ -16,7 +16,6 @@ import ( "crypto/rand" "encoding/json" "fmt" - "github.com/trustbloc/sidetree-core-go/pkg/docutil" "io" "io/ioutil" mrand "math/rand" @@ -39,6 +38,7 @@ import ( "github.com/trustbloc/sidetree-core-go/pkg/canonicalizer" "github.com/trustbloc/sidetree-core-go/pkg/commitment" "github.com/trustbloc/sidetree-core-go/pkg/document" + "github.com/trustbloc/sidetree-core-go/pkg/docutil" "github.com/trustbloc/sidetree-core-go/pkg/encoder" "github.com/trustbloc/sidetree-core-go/pkg/hashing" "github.com/trustbloc/sidetree-core-go/pkg/patch" @@ -52,6 +52,7 @@ import ( "github.com/trustbloc/orb/pkg/discovery/endpoint/restapi" "github.com/trustbloc/orb/pkg/mocks" "github.com/trustbloc/orb/pkg/orbclient/aoprovider" + "github.com/trustbloc/orb/pkg/orbclient/doctransformer" "github.com/trustbloc/orb/pkg/orbclient/resolutionverifier" ) @@ -339,6 +340,39 @@ func (d *DIDOrbSteps) clientVerifiesResolvedDocument() error { return verifier.Verify(d.resolutionResult) } +func (d *DIDOrbSteps) clientVerifiesWebDocumentFromOrbDocument(didWebVar, didOrbVar string) error { + logger.Info("verify that resolved web document is produced from orb resolution result") + + didWebResolutionResultStr, ok := d.state.getVar(didWebVar) + if !ok { + return fmt.Errorf("missing did:web resolution result from var[%s]", didWebVar) + } + + var didWebResolutionResult document.ResolutionResult + err := json.Unmarshal([]byte(didWebResolutionResultStr), &didWebResolutionResult) + if err != nil { + return err + } + + didOrbResolutionResultStr, ok := d.state.getVar(didOrbVar) + if !ok { + return fmt.Errorf("missing did:orb resolution result from var[%s]", didOrbVar) + } + + var didOrbResolutionResult document.ResolutionResult + err = json.Unmarshal([]byte(didOrbResolutionResultStr), &didOrbResolutionResult) + if err != nil { + return err + } + + return diddoctransformer.VerifyWebDocumentFromOrbDocument(&didWebResolutionResult, &didOrbResolutionResult) + if err != nil { + return err + } + + return nil +} + func (d *DIDOrbSteps) clientFailsToVerifyResolvedDocument() error { logger.Info("fail to verify resolved document (mis-configured client)") @@ -2084,6 +2118,7 @@ func (d *DIDOrbSteps) RegisterSteps(s *godog.Suite) { s.Step(`^client discover orb endpoints$`, d.discoverEndpoints) s.Step(`^client sends request to "([^"]*)" to request anchor origin$`, d.clientRequestsAnchorOrigin) s.Step(`^client verifies resolved document$`, d.clientVerifiesResolvedDocument) + s.Step(`^client verifies that web document from variable "([^"]*)" is produced from orb document from variable "([^"]*)"$`, d.clientVerifiesWebDocumentFromOrbDocument) s.Step(`^mis-configured client fails to verify resolved document$`, d.clientFailsToVerifyResolvedDocument) s.Step(`^check error response contains "([^"]*)"$`, d.checkErrorResp) s.Step(`^client sends request to "([^"]*)" to create DID document$`, d.createDIDDocument) diff --git a/test/bdd/features/did-sidetree.feature b/test/bdd/features/did-sidetree.feature index 84e6d77cc..fa9809ad2 100644 --- a/test/bdd/features/did-sidetree.feature +++ b/test/bdd/features/did-sidetree.feature @@ -485,16 +485,19 @@ Feature: When an HTTP GET is sent to "https://orb.domain3.com/1.0/identifiers/did:web:other.com:scid:suffix" and the returned status code is 404 When an HTTP GET is sent to "https://orb.domain3.com/1.0/identifiers/did:web:orb.domain3.com:scid:suffix" and the returned status code is 404 - When client sends request to "https://orb.domain3.com/sidetree/v1/operations" to create DID document and the suffix is saved to variable "didSuffix" When client sends request to "https://orb.domain3.com/sidetree/v1/identifiers" to resolve DID document with interim did Then check success response contains "uAAA" + Then the response is saved to variable "orbResponse" # test unpublished existing DID When an HTTP GET is sent to "https://orb.domain3.com/scid/${didSuffix}/did.json" When an HTTP GET is sent to "https://orb.domain3.com/1.0/identifiers/did:web:orb.domain3.com:scid:${didSuffix}" + Then the response is saved to variable "webResponse" + + Then client verifies that web document from variable "webResponse" is produced from orb document from variable "orbResponse" When client sends request to "https://orb.domain3.com/sidetree/v1/identifiers" to resolve DID document with interim did Then check success response contains "canonicalId"