From 8db407fb064ae66805cd2c3aa0ee069b97ef5de4 Mon Sep 17 00:00:00 2001 From: Andres Uribe Date: Fri, 28 Jul 2023 14:45:51 -0400 Subject: [PATCH] Fixes did ion long form resolution (#435) * Fixed resolution problems. * Add canonical ID when applying patch changes to a did doc * ptr --- credential/integrity/signature_test.go | 15 +++++- did/ion/did.go | 27 +++++++++-- did/ion/did_test.go | 64 ++++++++++++++++++++++++++ did/ion/operations_test.go | 3 +- did/ion/resolver.go | 19 ++++++-- did/resolution/model.go | 35 ++++++++------ 6 files changed, 137 insertions(+), 26 deletions(-) diff --git a/credential/integrity/signature_test.go b/credential/integrity/signature_test.go index 11ee6bdb..2b421439 100644 --- a/credential/integrity/signature_test.go +++ b/credential/integrity/signature_test.go @@ -2,10 +2,12 @@ package integrity import ( "context" + "net/http" "testing" "time" "github.com/TBD54566975/ssi-sdk/credential" + "github.com/TBD54566975/ssi-sdk/did/ion" "github.com/goccy/go-json" "github.com/TBD54566975/ssi-sdk/crypto" @@ -79,8 +81,8 @@ func TestVerifyCredentialSignature(t *testing.T) { resolver, err := resolution.NewResolver([]resolution.Resolver{key.Resolver{}}...) assert.NoError(tt, err) - credential := getTestCredential() - credBytes, err := json.Marshal(credential) + testCred := getTestCredential() + credBytes, err := json.Marshal(testCred) assert.NoError(tt, err) _, err = VerifyCredentialSignature(context.Background(), credBytes, resolver) assert.Error(tt, err) @@ -224,6 +226,15 @@ func TestVerifyJWTCredential(t *testing.T) { assert.NoError(tt, err) assert.True(tt, verified) }) + + t.Run("valid credential with long form ion did", func(t *testing.T) { + resolver, err := ion.NewIONResolver(http.DefaultClient, "https://ion.example.com") + assert.NoError(t, err) + const jwtCred = `eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6aW9uOkVpQmp6ZUtpVzdXU3lqRUdtai1Jc3NlZDVoTWVmbU0yX0h3eWJLN2RTckRfWEE6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKemFXZGZNalF6T0dOaU1tUWlMQ0p3ZFdKc2FXTkxaWGxLZDJzaU9uc2lZM0oySWpvaWMyVmpjREkxTm1zeElpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaVZFUkdVbWxPVTNkR2NHMTBjVFZYWDNrNVJIaG9NRGgwWlVKNlNGWkxWemxEYW5kMVJIUjVObGxKVlNJc0lua2lPaUpmVjNjMWNEUldjRVJvYkRGMmFVNUplVXRmTTFkVmJqVmlkSEowT0dWcmVqWm5OMHBIV1VaVVVVWTRJbjBzSW5CMWNuQnZjMlZ6SWpwYkltRjFkR2hsYm5ScFkyRjBhVzl1SWl3aVlYTnpaWEowYVc5dVRXVjBhRzlrSWwwc0luUjVjR1VpT2lKRlkyUnpZVk5sWTNBeU5UWnJNVlpsY21sbWFXTmhkR2x2Ymt0bGVUSXdNVGtpZlYwc0luTmxjblpwWTJWeklqcGJleUpwWkNJNklteHBibXRsWkdSdmJXRnBibk1pTENKelpYSjJhV05sUlc1a2NHOXBiblFpT25zaWIzSnBaMmx1Y3lJNld5Sm9kSFJ3Y3pvdkwyeHBibXRsWkdsdUxtTnZiUzhpWFgwc0luUjVjR1VpT2lKTWFXNXJaV1JFYjIxaGFXNXpJbjBzZXlKcFpDSTZJbWgxWWlJc0luTmxjblpwWTJWRmJtUndiMmx1ZENJNmV5SnBibk4wWVc1alpYTWlPbHNpYUhSMGNITTZMeTlpWlhSaExtaDFZaTV0YzJsa1pXNTBhWFI1TG1OdmJTOTJNUzR3THpVNE9XUTFNMkkxTFdSbFpqVXROREl6TlMxaU5qUXlMVGhsTVdNME1XVTRZbU5oTVNKZGZTd2lkSGx3WlNJNklrbGtaVzUwYVhSNVNIVmlJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCYlRVdGFFNXRWbmhTVVZkT1ptMTNRelpXTVRaaE4wYzNTbTVyVHpNNGFVZFVVRlkyTjNGNFEzTkRkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFqRlBRa2huYWt0amFrcGtMVkZRZWkxUllXWlFibGRCZW1SdWNtaDVXVkIyVUVsZlZuSnpUbTVYZDBFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVNMk5uTllTR1ZHTWtseWRuaElTM3BDY1dWRVRHd3pWRU5TUzJwVlpFSnNkbVJmYmtaS01GcDNZVFJuSW4xOSNzaWdfMjQzOGNiMmQifQ.eyJzdWIiOiJkaWQ6aW9uOkVpQmp6ZUtpVzdXU3lqRUdtai1Jc3NlZDVoTWVmbU0yX0h3eWJLN2RTckRfWEE6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKemFXZGZNalF6T0dOaU1tUWlMQ0p3ZFdKc2FXTkxaWGxLZDJzaU9uc2lZM0oySWpvaWMyVmpjREkxTm1zeElpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaVZFUkdVbWxPVTNkR2NHMTBjVFZYWDNrNVJIaG9NRGgwWlVKNlNGWkxWemxEYW5kMVJIUjVObGxKVlNJc0lua2lPaUpmVjNjMWNEUldjRVJvYkRGMmFVNUplVXRmTTFkVmJqVmlkSEowT0dWcmVqWm5OMHBIV1VaVVVVWTRJbjBzSW5CMWNuQnZjMlZ6SWpwYkltRjFkR2hsYm5ScFkyRjBhVzl1SWl3aVlYTnpaWEowYVc5dVRXVjBhRzlrSWwwc0luUjVjR1VpT2lKRlkyUnpZVk5sWTNBeU5UWnJNVlpsY21sbWFXTmhkR2x2Ymt0bGVUSXdNVGtpZlYwc0luTmxjblpwWTJWeklqcGJleUpwWkNJNklteHBibXRsWkdSdmJXRnBibk1pTENKelpYSjJhV05sUlc1a2NHOXBiblFpT25zaWIzSnBaMmx1Y3lJNld5Sm9kSFJ3Y3pvdkwyeHBibXRsWkdsdUxtTnZiUzhpWFgwc0luUjVjR1VpT2lKTWFXNXJaV1JFYjIxaGFXNXpJbjBzZXlKcFpDSTZJbWgxWWlJc0luTmxjblpwWTJWRmJtUndiMmx1ZENJNmV5SnBibk4wWVc1alpYTWlPbHNpYUhSMGNITTZMeTlpWlhSaExtaDFZaTV0YzJsa1pXNTBhWFI1TG1OdmJTOTJNUzR3THpVNE9XUTFNMkkxTFdSbFpqVXROREl6TlMxaU5qUXlMVGhsTVdNME1XVTRZbU5oTVNKZGZTd2lkSGx3WlNJNklrbGtaVzUwYVhSNVNIVmlJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCYlRVdGFFNXRWbmhTVVZkT1ptMTNRelpXTVRaaE4wYzNTbTVyVHpNNGFVZFVVRlkyTjNGNFEzTkRkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFqRlBRa2huYWt0amFrcGtMVkZRZWkxUllXWlFibGRCZW1SdWNtaDVXVkIyVUVsZlZuSnpUbTVYZDBFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVNMk5uTllTR1ZHTWtseWRuaElTM3BDY1dWRVRHd3pWRU5TUzJwVlpFSnNkbVJmYmtaS01GcDNZVFJuSW4xOSIsImlzcyI6ImRpZDppb246RWlCanplS2lXN1dTeWpFR21qLUlzc2VkNWhNZWZtTTJfSHd5Yks3ZFNyRF9YQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUp6YVdkZk1qUXpPR05pTW1RaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pYzJWamNESTFObXN4SWl3aWEzUjVJam9pUlVNaUxDSjRJam9pVkVSR1VtbE9VM2RHY0cxMGNUVlhYM2s1Ukhob01EaDBaVUo2U0ZaTFZ6bERhbmQxUkhSNU5sbEpWU0lzSW5raU9pSmZWM2MxY0RSV2NFUm9iREYyYVU1SmVVdGZNMWRWYmpWaWRISjBPR1ZyZWpabk4wcEhXVVpVVVVZNEluMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpGWTJSellWTmxZM0F5TlRack1WWmxjbWxtYVdOaGRHbHZia3RsZVRJd01Ua2lmVjBzSW5ObGNuWnBZMlZ6SWpwYmV5SnBaQ0k2SW14cGJtdGxaR1J2YldGcGJuTWlMQ0p6WlhKMmFXTmxSVzVrY0c5cGJuUWlPbnNpYjNKcFoybHVjeUk2V3lKb2RIUndjem92TDJ4cGJtdGxaR2x1TG1OdmJTOGlYWDBzSW5SNWNHVWlPaUpNYVc1clpXUkViMjFoYVc1ekluMHNleUpwWkNJNkltaDFZaUlzSW5ObGNuWnBZMlZGYm1Sd2IybHVkQ0k2ZXlKcGJuTjBZVzVqWlhNaU9sc2lhSFIwY0hNNkx5OWlaWFJoTG1oMVlpNXRjMmxrWlc1MGFYUjVMbU52YlM5Mk1TNHdMelU0T1dRMU0ySTFMV1JsWmpVdE5ESXpOUzFpTmpReUxUaGxNV00wTVdVNFltTmhNU0pkZlN3aWRIbHdaU0k2SWtsa1pXNTBhWFI1U0hWaUluMWRmWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbEJiVFV0YUU1dFZuaFNVVmRPWm0xM1F6WldNVFpoTjBjM1NtNXJUek00YVVkVVVGWTJOM0Y0UTNORGR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUWpGUFFraG5ha3RqYWtwa0xWRlFlaTFSWVdaUWJsZEJlbVJ1Y21oNVdWQjJVRWxmVm5KelRtNVhkMEVpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVU0yTm5OWVNHVkdNa2x5ZG5oSVMzcENjV1ZFVEd3elZFTlNTMnBWWkVKc2RtUmZia1pLTUZwM1lUUm5JbjE5IiwibmJmIjoxNjQ5Mjg2NzM3LCJleHAiOjI0MzgyMDUxMzcsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9pZGVudGl0eS5mb3VuZGF0aW9uLy53ZWxsLWtub3duL2NvbnRleHRzL2RpZC1jb25maWd1cmF0aW9uLXYwLjAuanNvbmxkIl0sImlzc3VlciI6ImRpZDppb246RWlCanplS2lXN1dTeWpFR21qLUlzc2VkNWhNZWZtTTJfSHd5Yks3ZFNyRF9YQTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKeVpYQnNZV05sSWl3aVpHOWpkVzFsYm5RaU9uc2ljSFZpYkdsalMyVjVjeUk2VzNzaWFXUWlPaUp6YVdkZk1qUXpPR05pTW1RaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pYzJWamNESTFObXN4SWl3aWEzUjVJam9pUlVNaUxDSjRJam9pVkVSR1VtbE9VM2RHY0cxMGNUVlhYM2s1Ukhob01EaDBaVUo2U0ZaTFZ6bERhbmQxUkhSNU5sbEpWU0lzSW5raU9pSmZWM2MxY0RSV2NFUm9iREYyYVU1SmVVdGZNMWRWYmpWaWRISjBPR1ZyZWpabk4wcEhXVVpVVVVZNEluMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpGWTJSellWTmxZM0F5TlRack1WWmxjbWxtYVdOaGRHbHZia3RsZVRJd01Ua2lmVjBzSW5ObGNuWnBZMlZ6SWpwYmV5SnBaQ0k2SW14cGJtdGxaR1J2YldGcGJuTWlMQ0p6WlhKMmFXTmxSVzVrY0c5cGJuUWlPbnNpYjNKcFoybHVjeUk2V3lKb2RIUndjem92TDJ4cGJtdGxaR2x1TG1OdmJTOGlYWDBzSW5SNWNHVWlPaUpNYVc1clpXUkViMjFoYVc1ekluMHNleUpwWkNJNkltaDFZaUlzSW5ObGNuWnBZMlZGYm1Sd2IybHVkQ0k2ZXlKcGJuTjBZVzVqWlhNaU9sc2lhSFIwY0hNNkx5OWlaWFJoTG1oMVlpNXRjMmxrWlc1MGFYUjVMbU52YlM5Mk1TNHdMelU0T1dRMU0ySTFMV1JsWmpVdE5ESXpOUzFpTmpReUxUaGxNV00wTVdVNFltTmhNU0pkZlN3aWRIbHdaU0k2SWtsa1pXNTBhWFI1U0hWaUluMWRmWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbEJiVFV0YUU1dFZuaFNVVmRPWm0xM1F6WldNVFpoTjBjM1NtNXJUek00YVVkVVVGWTJOM0Y0UTNORGR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUWpGUFFraG5ha3RqYWtwa0xWRlFlaTFSWVdaUWJsZEJlbVJ1Y21oNVdWQjJVRWxmVm5KelRtNVhkMEVpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVU0yTm5OWVNHVkdNa2x5ZG5oSVMzcENjV1ZFVEd3elZFTlNTMnBWWkVKc2RtUmZia1pLTUZwM1lUUm5JbjE5IiwiaXNzdWFuY2VEYXRlIjoiMjAyMi0wNC0wNlQyMzoxMjoxNy44MzVaIiwiZXhwaXJhdGlvbkRhdGUiOiIyMDQ3LTA0LTA2VDIzOjEyOjE3LjgzNVoiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiRG9tYWluTGlua2FnZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWQiOiJkaWQ6aW9uOkVpQmp6ZUtpVzdXU3lqRUdtai1Jc3NlZDVoTWVmbU0yX0h3eWJLN2RTckRfWEE6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSnlaWEJzWVdObElpd2laRzlqZFcxbGJuUWlPbnNpY0hWaWJHbGpTMlY1Y3lJNlczc2lhV1FpT2lKemFXZGZNalF6T0dOaU1tUWlMQ0p3ZFdKc2FXTkxaWGxLZDJzaU9uc2lZM0oySWpvaWMyVmpjREkxTm1zeElpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaVZFUkdVbWxPVTNkR2NHMTBjVFZYWDNrNVJIaG9NRGgwWlVKNlNGWkxWemxEYW5kMVJIUjVObGxKVlNJc0lua2lPaUpmVjNjMWNEUldjRVJvYkRGMmFVNUplVXRmTTFkVmJqVmlkSEowT0dWcmVqWm5OMHBIV1VaVVVVWTRJbjBzSW5CMWNuQnZjMlZ6SWpwYkltRjFkR2hsYm5ScFkyRjBhVzl1SWl3aVlYTnpaWEowYVc5dVRXVjBhRzlrSWwwc0luUjVjR1VpT2lKRlkyUnpZVk5sWTNBeU5UWnJNVlpsY21sbWFXTmhkR2x2Ymt0bGVUSXdNVGtpZlYwc0luTmxjblpwWTJWeklqcGJleUpwWkNJNklteHBibXRsWkdSdmJXRnBibk1pTENKelpYSjJhV05sUlc1a2NHOXBiblFpT25zaWIzSnBaMmx1Y3lJNld5Sm9kSFJ3Y3pvdkwyeHBibXRsWkdsdUxtTnZiUzhpWFgwc0luUjVjR1VpT2lKTWFXNXJaV1JFYjIxaGFXNXpJbjBzZXlKcFpDSTZJbWgxWWlJc0luTmxjblpwWTJWRmJtUndiMmx1ZENJNmV5SnBibk4wWVc1alpYTWlPbHNpYUhSMGNITTZMeTlpWlhSaExtaDFZaTV0YzJsa1pXNTBhWFI1TG1OdmJTOTJNUzR3THpVNE9XUTFNMkkxTFdSbFpqVXROREl6TlMxaU5qUXlMVGhsTVdNME1XVTRZbU5oTVNKZGZTd2lkSGx3WlNJNklrbGtaVzUwYVhSNVNIVmlJbjFkZlgxZExDSjFjR1JoZEdWRGIyMXRhWFJ0Wlc1MElqb2lSV2xCYlRVdGFFNXRWbmhTVVZkT1ptMTNRelpXTVRaaE4wYzNTbTVyVHpNNGFVZFVVRlkyTjNGNFEzTkRkeUo5TENKemRXWm1hWGhFWVhSaElqcDdJbVJsYkhSaFNHRnphQ0k2SWtWcFFqRlBRa2huYWt0amFrcGtMVkZRZWkxUllXWlFibGRCZW1SdWNtaDVXVkIyVUVsZlZuSnpUbTVYZDBFaUxDSnlaV052ZG1WeWVVTnZiVzFwZEcxbGJuUWlPaUpGYVVNMk5uTllTR1ZHTWtseWRuaElTM3BDY1dWRVRHd3pWRU5TUzJwVlpFSnNkbVJmYmtaS01GcDNZVFJuSW4xOSIsIm9yaWdpbiI6Imh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS8ifX19.oTFcVvKmYU1Mxh9Q4V5UNikddYANLjw-m3530PNDhFYmR1Dm8DOcjdU-p2rJ6vSZnUKatXV5VJLJxj1aJyuhlw` + verified, err := VerifyJWTCredential(context.Background(), jwtCred, resolver) + assert.NoError(t, err) + assert.True(t, verified) + }) } func getTestJWTCredential(t *testing.T, signer jwx.Signer) string { diff --git a/did/ion/did.go b/did/ion/did.go index 740404d3..03b50181 100644 --- a/did/ion/did.go +++ b/did/ion/did.go @@ -117,18 +117,24 @@ func PatchesToDIDDocument(shortFormDID, longFormDID string, patches []Patch) (*d return nil, errors.New("short form DID is required") } doc := did.Document{ - Context: []string{"https://www.w3.org/ns/did/v1"}, - ID: shortFormDID, - AlsoKnownAs: longFormDID, + Context: []any{"https://www.w3.org/ns/did/v1", map[string]any{ + "@base": longFormDID, + }}, + ID: longFormDID, } for _, patch := range patches { switch patch.GetAction() { case AddServices: addServicePatch := patch.(AddServicesAction) - doc.Services = append(doc.Services, addServicePatch.Services...) + for _, s := range addServicePatch.Services { + s := s + s.ID = canonicalID(s.ID) + doc.Services = append(doc.Services, s) + } case RemoveServices: removeServicePatch := patch.(RemoveServicesAction) for _, id := range removeServicePatch.IDs { + id := canonicalID(id) for i, service := range doc.Services { if service.ID == id { doc.Services = append(doc.Services[:i], doc.Services[i+1:]...) @@ -180,7 +186,9 @@ func replaceActionPatch(doc did.Document, patch ReplaceAction) (*did.Document, e } doc = *gotDoc for _, service := range patch.Document.Services { - doc.Services = append(doc.Services, service) + s := service + s.ID = canonicalID(s.ID) + doc.Services = append(doc.Services, s) } return &doc, nil } @@ -188,6 +196,7 @@ func replaceActionPatch(doc did.Document, patch ReplaceAction) (*did.Document, e func addPublicKeysPatch(doc did.Document, patch AddPublicKeysAction) (*did.Document, error) { for _, key := range patch.PublicKeys { currKey := key + currKey.ID = canonicalID(currKey.ID) doc.VerificationMethod = append(doc.VerificationMethod, did.VerificationMethod{ ID: currKey.ID, Type: cryptosuite.LDKeyType(currKey.Type), @@ -214,8 +223,16 @@ func addPublicKeysPatch(doc did.Document, patch AddPublicKeysAction) (*did.Docum return &doc, nil } +func canonicalID(id string) string { + if strings.Contains(id, "#") { + return id + } + return "#" + id +} + func removePublicKeysPatch(doc did.Document, patch RemovePublicKeysAction) (*did.Document, error) { for _, id := range patch.IDs { + id := canonicalID(id) removed := false for i, key := range doc.VerificationMethod { if key.ID != id { diff --git a/did/ion/did_test.go b/did/ion/did_test.go index e56f7dd2..e941cd3c 100644 --- a/did/ion/did_test.go +++ b/did/ion/did_test.go @@ -1,13 +1,77 @@ package ion import ( + "context" + "net/http" "testing" "github.com/TBD54566975/ssi-sdk/crypto/jwx" "github.com/TBD54566975/ssi-sdk/did" + "github.com/goccy/go-json" "github.com/stretchr/testify/assert" ) +// Test vector from https://identity.foundation/sidetree/spec/#long-form-response, adjusted by replacing s/sidetree/ion/ +func TestResolveLongFormDID(t *testing.T) { + longFormDID := `did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ` + resolver, err := NewIONResolver(http.DefaultClient, "https://example.com") + assert.NoError(t, err) + + resolutionResult, err := resolver.Resolve(context.Background(), longFormDID) + assert.NoError(t, err) + + jsonResolutionResult, err := json.Marshal(resolutionResult) + assert.NoError(t, err) + + expectedResolutionResultJSON := `{ + "@context": "https://w3id.org/did-resolution/v1", + "didDocument": { + "id": "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ", + "@context": [ + "https://www.w3.org/ns/did/v1", + { + "@base": "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ" + } + ], + "service": [ + { + "id": "#service1Id", + "type": "service1Type", + "serviceEndpoint": "http://www.service1.com" + } + ], + "verificationMethod": [ + { + "id": "#publicKeyModel1Id", + "controller": "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJwdWJsaWNLZXlNb2RlbDFJZCIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0WFNLQl9ydWJYUzdzQ2pYcXVwVkpFelRjVzNNc2ptRXZxMVlwWG45NlpnIiwieSI6ImRPaWNYcWJqRnhvR0otSzAtR0oxa0hZSnFpY19EX09NdVV3a1E3T2w2bmsifSwicHVycG9zZXMiOlsiYXV0aGVudGljYXRpb24iLCJrZXlBZ3JlZW1lbnQiXSwidHlwZSI6IkVjZHNhU2VjcDI1NmsxVmVyaWZpY2F0aW9uS2V5MjAxOSJ9XSwic2VydmljZXMiOlt7ImlkIjoic2VydmljZTFJZCIsInNlcnZpY2VFbmRwb2ludCI6Imh0dHA6Ly93d3cuc2VydmljZTEuY29tIiwidHlwZSI6InNlcnZpY2UxVHlwZSJ9XX19XSwidXBkYXRlQ29tbWl0bWVudCI6IkVpREtJa3dxTzY5SVBHM3BPbEhrZGI4Nm5ZdDBhTnhTSFp1MnItYmhFem5qZEEifSwic3VmZml4RGF0YSI6eyJkZWx0YUhhc2giOiJFaUNmRFdSbllsY0Q5RUdBM2RfNVoxQUh1LWlZcU1iSjluZmlxZHo1UzhWRGJnIiwicmVjb3ZlcnlDb21taXRtZW50IjoiRWlCZk9aZE10VTZPQnc4UGs4NzlRdFotMkotOUZiYmpTWnlvYUFfYnFENHpoQSJ9fQ", + "type": "EcdsaSecp256k1VerificationKey2019", + "publicKeyJwk": { + "crv": "secp256k1", + "kty": "EC", + "x": "tXSKB_rubXS7sCjXqupVJEzTcW3MsjmEvq1YpXn96Zg", + "y": "dOicXqbjFxoGJ-K0-GJ1kHYJqic_D_OMuUwkQ7Ol6nk" + } + } + ], + "authentication": [ + "#publicKeyModel1Id" + ], + "keyAgreement": [ + "#publicKeyModel1Id" + ] + }, + "didDocumentMetadata": { + "equivalentId": ["did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg"], + "method": { + "published": false, + "recoveryCommitment": "EiBfOZdMtU6OBw8Pk879QtZ-2J-9FbbjSZyoaA_bqD4zhA", + "updateCommitment": "EiDKIkwqO69IPG3pOlHkdb86nYt0aNxSHZu2r-bhEznjdA" + } + } +}` + assert.JSONEq(t, expectedResolutionResultJSON, string(jsonResolutionResult)) +} + // https://github.com/decentralized-identity/ion-sdk/blob/main/tests/IonDid.spec.ts#L18 func TestCreateLongFormDID(t *testing.T) { var recoveryKey jwx.PublicKeyJWK diff --git a/did/ion/operations_test.go b/did/ion/operations_test.go index 65cd394a..87fcde3b 100644 --- a/did/ion/operations_test.go +++ b/did/ion/operations_test.go @@ -125,8 +125,7 @@ func TestResolver(t *testing.T) { result, err := resolver.Resolve(context.Background(), longFormDID, nil) assert.NoError(ttt, err) assert.NotEmpty(ttt, result) - assert.Equal(ttt, "did:ion:EiDyOQbbZAa3aiRzeCkV7LOx3SERjjH93EXoIM3UoN4oWg", result.Document.ID) - assert.Equal(ttt, longFormDID, result.Document.AlsoKnownAs) + assert.Equal(ttt, longFormDID, result.Document.ID) }) }) diff --git a/did/ion/resolver.go b/did/ion/resolver.go index affabd15..e87d8165 100644 --- a/did/ion/resolver.go +++ b/did/ion/resolver.go @@ -64,7 +64,16 @@ func (i Resolver) Resolve(ctx context.Context, id string, _ ...resolution.Option if err != nil { return nil, errors.Wrap(err, "reconstructing document from long form DID") } - return &resolution.Result{Document: *didDoc}, nil + return &resolution.Result{ + Context: "https://w3id.org/did-resolution/v1", + Document: *didDoc, + DocumentMetadata: &resolution.DocumentMetadata{ + EquivalentID: []string{shortFormDID}, + Method: resolution.Method{ + Published: false, + RecoveryCommitment: initialState.SuffixData.RecoveryCommitment, + UpdateCommitment: initialState.Delta.UpdateCommitment}, + }}, nil } if i.baseURL.String() == "" { @@ -79,7 +88,9 @@ func (i Resolver) Resolve(ctx context.Context, id string, _ ...resolution.Option return nil, errors.Wrapf(err, "resolving, with URL: %s", i.baseURL.String()) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrapf(err, "resolving, with response %+v", resp) @@ -114,7 +125,9 @@ func (i Resolver) Anchor(ctx context.Context, op AnchorOperation) (*resolution.R return nil, errors.Wrapf(err, "posting anchor operation %+v", op) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() body, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrapf(err, "could not resolve with response %+v", resp) diff --git a/did/resolution/model.go b/did/resolution/model.go index 6e0112b8..65741d8c 100644 --- a/did/resolution/model.go +++ b/did/resolution/model.go @@ -9,10 +9,10 @@ import ( // Result encapsulates the tuple of a DID resolution https://www.w3.org/TR/did-core/#did-resolution type Result struct { - Context string `json:"@context,omitempty"` - Metadata `json:"didResolutionMetadata,omitempty"` - did.Document `json:"didDocument,omitempty"` - DocumentMetadata `json:"didDocumentMetadata,omitempty"` + Context string `json:"@context,omitempty"` + *Metadata `json:"didResolutionMetadata,omitempty"` + did.Document `json:"didDocument,omitempty"` + *DocumentMetadata `json:"didDocumentMetadata,omitempty"` } func (r *Result) IsEmpty() bool { @@ -22,16 +22,23 @@ func (r *Result) IsEmpty() bool { return reflect.DeepEqual(r, Result{}) } +type Method struct { + Published bool `json:"published"` + RecoveryCommitment string `json:"recoveryCommitment,omitempty"` + UpdateCommitment string `json:"updateCommitment,omitempty"` +} + // DocumentMetadata https://www.w3.org/TR/did-core/#did-document-metadata type DocumentMetadata struct { - Created string `json:"created,omitempty" validate:"omitempty,datetime=2006-01-02T15:04:05Z"` - Updated string `json:"updated,omitempty" validate:"omitempty,datetime=2006-01-02T15:04:05Z"` - Deactivated bool `json:"deactivated,omitempty"` - NextUpdate string `json:"nextUpdate,omitempty"` - VersionID string `json:"versionId,omitempty"` - NextVersionID string `json:"nextVersionId,omitempty"` - EquivalentID string `json:"equivalentId,omitempty"` - CanonicalID string `json:"canonicalId,omitempty"` + Created string `json:"created,omitempty" validate:"omitempty,datetime=2006-01-02T15:04:05Z"` + Updated string `json:"updated,omitempty" validate:"omitempty,datetime=2006-01-02T15:04:05Z"` + Deactivated bool `json:"deactivated,omitempty"` + NextUpdate string `json:"nextUpdate,omitempty"` + VersionID string `json:"versionId,omitempty"` + NextVersionID string `json:"nextVersionId,omitempty"` + EquivalentID []string `json:"equivalentId,omitempty"` + CanonicalID string `json:"canonicalId,omitempty"` + Method Method `json:"method,omitempty"` } func (s *DocumentMetadata) IsValid() bool { @@ -48,6 +55,6 @@ type Error struct { // Metadata https://www.w3.org/TR/did-core/#did-resolution-metadata type Metadata struct { - ContentType string - Error *Error + ContentType string `json:"contentType,omitempty"` + Error *Error `json:"error,omitempty"` }