diff --git a/json/parser.go b/json/parser.go index 0ec7da1..8fcf301 100644 --- a/json/parser.go +++ b/json/parser.go @@ -3,389 +3,27 @@ package json import ( "context" "encoding/json" - "fmt" - "strings" core "github.com/iden3/go-iden3-core/v2" - "github.com/iden3/go-iden3-core/v2/w3c" - "github.com/iden3/go-schema-processor/v2/merklize" "github.com/iden3/go-schema-processor/v2/processor" - "github.com/iden3/go-schema-processor/v2/utils" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/piprate/json-gold/ld" "github.com/pkg/errors" ) -const ( - credentialSubjectKey = "credentialSubject" - //nolint:gosec // G101: this is not a hardcoded credential - credentialSubjectFullKey = "https://www.w3.org/2018/credentials#credentialSubject" - //nolint:gosec // G101: this is not a hardcoded credential - verifiableCredentialFullKey = "https://www.w3.org/2018/credentials#VerifiableCredential" - typeFullKey = "@type" - contextFullKey = "@context" - serializationFullKey = "iden3_serialization" -) +const contextFullKey = "@context" // Parser can parse claim data according to specification type Parser struct { } // ParseClaim creates Claim object from W3CCredential +// Deprecated: use credential.ToCoreClaim instead func (s Parser) ParseClaim(ctx context.Context, credential verifiable.W3CCredential, opts *processor.CoreClaimOptions) (*core.Claim, error) { - - if opts == nil { - opts = &processor.CoreClaimOptions{ - RevNonce: 0, - Version: 0, - SubjectPosition: verifiable.CredentialSubjectPositionIndex, - MerklizedRootPosition: verifiable.CredentialMerklizedRootPositionNone, - Updatable: false, - MerklizerOpts: nil, - } - } - - mz, err := credential.Merklize(ctx, opts.MerklizerOpts...) - if err != nil { - return nil, err - } - - credentialType, err := findCredentialType(mz) - if err != nil { - return nil, err - } - - subjectID := credential.CredentialSubject["id"] - - slots, nonMerklized, err := s.parseSlots(mz, credential, credentialType) - if err != nil { - return nil, err - } - - // if schema is for non merklized credential, root position must be set to none ('') - // otherwise default position for merklized position is index. - if !nonMerklized { - if opts.MerklizedRootPosition == verifiable.CredentialMerklizedRootPositionNone { - opts.MerklizedRootPosition = verifiable.CredentialMerklizedRootPositionIndex - } - } else { - if opts.MerklizedRootPosition != verifiable.CredentialMerklizedRootPositionNone { - return nil, errors.New( - "merklized root position is not supported for non-merklized claims") - } - } - - claim, err := core.NewClaim( - utils.CreateSchemaHash([]byte(credentialType)), - core.WithIndexDataBytes(slots.IndexA, slots.IndexB), - core.WithValueDataBytes(slots.ValueA, slots.ValueB), - core.WithRevocationNonce(opts.RevNonce), - core.WithVersion(opts.Version)) - if err != nil { - return nil, err - } - if opts.Updatable { - claim.SetFlagUpdatable(opts.Updatable) - } - - if credential.Expiration != nil { - claim.SetExpirationDate(*credential.Expiration) - } - if subjectID != nil { - var did *w3c.DID - did, err = w3c.ParseDID(fmt.Sprintf("%v", subjectID)) - if err != nil { - return nil, err - } - - var id core.ID - id, err = core.IDFromDID(*did) - if err != nil { - return nil, err - } - - switch opts.SubjectPosition { - case "", verifiable.CredentialSubjectPositionIndex: - claim.SetIndexID(id) - case verifiable.CredentialSubjectPositionValue: - claim.SetValueID(id) - default: - return nil, errors.New("unknown subject position") - } - } - - switch opts.MerklizedRootPosition { - case verifiable.CredentialMerklizedRootPositionIndex: - err = claim.SetIndexMerklizedRoot(mz.Root().BigInt()) - if err != nil { - return nil, err - } - case verifiable.CredentialMerklizedRootPositionValue: - err = claim.SetValueMerklizedRoot(mz.Root().BigInt()) - if err != nil { - return nil, err - } - case verifiable.CredentialMerklizedRootPositionNone: - // Slots where filled earlier. Nothing to do here. - break - default: - return nil, errors.New("unknown merklized root position") - } - - return claim, nil -} - -func getSerializationAttrFromParsedContext(ldCtx *ld.Context, - tp string) (string, error) { - - termDef, ok := ldCtx.AsMap()["termDefinitions"] - if !ok { - return "", errors.New("types now found in context") - } - - termDefM, ok := termDef.(map[string]any) - if !ok { - return "", errors.New("terms definitions is not of correct type") - } - - for typeName, typeDef := range termDefM { - typeDefM, ok := typeDef.(map[string]any) - if !ok { - // not a type - continue - } - typeCtx, ok := typeDefM[contextFullKey] - if !ok { - // not a type - continue - } - typeCtxM, ok := typeCtx.(map[string]any) - if !ok { - return "", errors.New("type @context is not of correct type") - } - typeID, _ := typeDefM["@id"].(string) - if typeName != tp && typeID != tp { - continue - } - - serStr, _ := typeCtxM[serializationFullKey].(string) - return serStr, nil - } - - return "", nil -} - -// Get `iden3_serialization` attr definition from context document either using -// type name like DeliverAddressMultiTestForked or by type id like -// urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100. -func getSerializationAttr(credential verifiable.W3CCredential, - opts *ld.JsonLdOptions, tp string) (string, error) { - - ldCtx, err := ld.NewContext(nil, opts).Parse(anySlice(credential.Context)) - if err != nil { - return "", err - } - - return getSerializationAttrFromParsedContext(ldCtx, tp) -} - -type slotsPaths struct { - indexAPath string - indexBPath string - valueAPath string - valueBPath string -} - -func (p slotsPaths) isEmpty() bool { - return p.indexAPath == "" && p.indexBPath == "" && - p.valueAPath == "" && p.valueBPath == "" -} - -func parseSerializationAttr(serAttr string) (slotsPaths, error) { - prefix := "iden3:v1:" - if !strings.HasPrefix(serAttr, prefix) { - return slotsPaths{}, - errors.New("serialization attribute does not have correct prefix") - } - parts := strings.Split(serAttr[len(prefix):], "&") - if len(parts) > 4 { - return slotsPaths{}, - errors.New("serialization attribute has too many parts") - } - var paths slotsPaths - for _, part := range parts { - kv := strings.Split(part, "=") - if len(kv) != 2 { - return slotsPaths{}, errors.New( - "serialization attribute part does not have correct format") - } - switch kv[0] { - case "slotIndexA": - paths.indexAPath = kv[1] - case "slotIndexB": - paths.indexBPath = kv[1] - case "slotValueA": - paths.valueAPath = kv[1] - case "slotValueB": - paths.valueBPath = kv[1] - default: - return slotsPaths{}, - errors.New("unknown serialization attribute slot") - } - } - return paths, nil -} - -func fillSlot(slotData []byte, mz *merklize.Merklizer, path string) error { - if path == "" { - return nil - } - - path = credentialSubjectKey + "." + path - p, err := mz.ResolveDocPath(path) - if err != nil { - return errors.Wrapf(err, "field not found in credential %s", path) - } - - entry, err := mz.Entry(p) - if errors.Is(err, merklize.ErrorEntryNotFound) { - return errors.Wrapf(err, "field not found in credential %s", path) - } else if err != nil { - return err - } - - intVal, err := entry.ValueMtEntry() - if err != nil { - return err - } - - bytesVal := utils.SwapEndianness(intVal.Bytes()) - copy(slotData, bytesVal) - return nil -} - -func findCredentialType(mz *merklize.Merklizer) (string, error) { - opts := mz.Options() - - // try to look into credentialSubject.@type to get type of credentials - path1, err := opts.NewPath(credentialSubjectFullKey, typeFullKey) - if err == nil { - var e any - e, err = mz.RawValue(path1) - if err == nil { - tp, ok := e.(string) - if ok { - return tp, nil - } - } - } - - // if type of credentials not found in credentialSubject.@type, loop at - // top level @types if it contains two elements: type we are looking for - // and "VerifiableCredential" type. - path2, err := opts.NewPath(typeFullKey) - if err != nil { - return "", err - } - - e, err := mz.RawValue(path2) - if err != nil { - return "", err - } - - eArr, ok := e.([]any) - if !ok { - return "", fmt.Errorf("top level @type expected to be an array") - } - topLevelTypes, err := toStringSlice(eArr) - if err != nil { - return "", err - } - if len(topLevelTypes) != 2 { - return "", fmt.Errorf("top level @type expected to be of length 2") - } - - switch verifiableCredentialFullKey { - case topLevelTypes[0]: - return topLevelTypes[1], nil - case topLevelTypes[1]: - return topLevelTypes[0], nil - default: - return "", fmt.Errorf( - "@type(s) are expected to contain VerifiableCredential type") - } -} - -// parsedSlots is struct that represents iden3 claim specification -type parsedSlots struct { - IndexA, IndexB []byte - ValueA, ValueB []byte -} - -// parseSlots converts payload to claim slots using provided schema -func (s Parser) parseSlots(mz *merklize.Merklizer, - credential verifiable.W3CCredential, - credentialType string) (parsedSlots, bool, error) { - - slots := parsedSlots{ - IndexA: make([]byte, 32), - IndexB: make([]byte, 32), - ValueA: make([]byte, 32), - ValueB: make([]byte, 32), - } - - jsonLDOpts := mz.Options().JSONLDOptions() - serAttr, err := getSerializationAttr(credential, jsonLDOpts, - credentialType) - if err != nil { - return slots, false, err - } - - if serAttr == "" { - return slots, false, nil - } - - sPaths, err := parseSerializationAttr(serAttr) - if err != nil { - return slots, true, err - } - - if sPaths.isEmpty() { - return slots, true, nil - } - - err = fillSlot(slots.IndexA, mz, sPaths.indexAPath) - if err != nil { - return slots, true, err - } - err = fillSlot(slots.IndexB, mz, sPaths.indexBPath) - if err != nil { - return slots, true, err - } - err = fillSlot(slots.ValueA, mz, sPaths.valueAPath) - if err != nil { - return slots, true, err - } - err = fillSlot(slots.ValueB, mz, sPaths.valueBPath) - if err != nil { - return slots, true, err - } - - return slots, true, nil -} - -// convert from the slice of concrete type to the slice of interface{} -func anySlice[T any](in []T) []any { - if in == nil { - return nil - } - s := make([]any, len(in)) - for i := range in { - s[i] = in[i] - } - return s + verifiableOpts := verifiable.CoreClaimOptions(*opts) + return credential.ToCoreClaim(ctx, &verifiableOpts) } // GetFieldSlotIndex return index of slot from 0 to 7 (each claim has by default 8 slots) @@ -413,7 +51,7 @@ func (s Parser) GetFieldSlotIndex(field string, typeName string, return -1, err } - serAttr, err := getSerializationAttrFromParsedContext(ldCtx, typeName) + serAttr, err := verifiable.GetSerializationAttrFromParsedContext(ldCtx, typeName) if err != nil { return -1, err } @@ -422,34 +60,22 @@ func (s Parser) GetFieldSlotIndex(field string, typeName string, "field `%s` not specified in serialization info", field) } - sPaths, err := parseSerializationAttr(serAttr) + sPaths, err := verifiable.ParseSerializationAttr(serAttr) if err != nil { return -1, err } switch field { - case sPaths.indexAPath: + case sPaths.IndexAPath: return 2, nil - case sPaths.indexBPath: + case sPaths.IndexBPath: return 3, nil - case sPaths.valueAPath: + case sPaths.ValueAPath: return 6, nil - case sPaths.valueBPath: + case sPaths.ValueBPath: return 7, nil default: return -1, errors.Errorf( "field `%s` not specified in serialization info", field) } } - -func toStringSlice(in []any) ([]string, error) { - out := make([]string, len(in)) - for i, v := range in { - s, ok := v.(string) - if !ok { - return nil, fmt.Errorf("element #%v is not a string", i) - } - out[i] = s - } - return out, nil -} diff --git a/json/parser_test.go b/json/parser_test.go index a6966c8..a257b35 100644 --- a/json/parser_test.go +++ b/json/parser_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "os" - "strings" "testing" core "github.com/iden3/go-iden3-core/v2" @@ -12,86 +11,9 @@ import ( "github.com/iden3/go-schema-processor/v2/processor" tst "github.com/iden3/go-schema-processor/v2/testing" "github.com/iden3/go-schema-processor/v2/verifiable" - "github.com/piprate/json-gold/ld" "github.com/stretchr/testify/require" ) -func TestParser_parseSlots(t *testing.T) { - defer tst.MockHTTPClient(t, - map[string]string{ - "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", - "https://example.com/schema-delivery-address.json-ld": "testdata/schema-delivery-address.json-ld", - }, - tst.IgnoreUntouchedURLs())() - - credentialBytes, err := os.ReadFile("testdata/non-merklized-1.json-ld") - require.NoError(t, err) - - var credential verifiable.W3CCredential - err = json.Unmarshal(credentialBytes, &credential) - require.NoError(t, err) - - nullSlot := make([]byte, 32) - ctx := context.Background() - - mz, err := credential.Merklize(ctx) - require.NoError(t, err) - - credentialType, err := findCredentialType(mz) - require.NoError(t, err) - - parser := Parser{} - slots, nonMerklized, err := parser.parseSlots(mz, credential, credentialType) - require.True(t, nonMerklized) - require.NoError(t, err) - require.NotEqual(t, nullSlot, slots.IndexA) - require.Equal(t, nullSlot, slots.IndexB) - require.Equal(t, nullSlot, slots.ValueA) - require.NotEqual(t, nullSlot, slots.ValueB) -} - -func TestGetSerializationAttr(t *testing.T) { - defer tst.MockHTTPClient(t, - map[string]string{ - "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", - "https://example.com/schema-delivery-address.json-ld": "testdata/schema-delivery-address.json-ld", - }, - tst.IgnoreUntouchedURLs())() - - vc := verifiable.W3CCredential{ - Context: []string{ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld", - }, - } - - options := ld.NewJsonLdOptions("") - - t.Run("by type name", func(t *testing.T) { - serAttr, err := getSerializationAttr(vc, options, - "DeliverAddressMultiTestForked") - require.NoError(t, err) - require.Equal(t, - "iden3:v1:slotIndexA=price&slotValueB=postalProviderInformation.insured", - serAttr) - }) - - t.Run("by type id", func(t *testing.T) { - serAttr, err := getSerializationAttr(vc, options, - "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100") - require.NoError(t, err) - require.Equal(t, - "iden3:v1:slotIndexA=price&slotValueB=postalProviderInformation.insured", - serAttr) - }) - - t.Run("unknown type", func(t *testing.T) { - serAttr, err := getSerializationAttr(vc, options, "bla-bla") - require.NoError(t, err) - require.Equal(t, "", serAttr) - }) -} - func TestParser_ParseClaimWithDataSlots(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ @@ -227,143 +149,3 @@ func Test_GetFieldSlotIndex(t *testing.T) { require.NoError(t, err) require.Equal(t, 7, slotIndex) } - -func TestFindCredentialType(t *testing.T) { - mockHTTP := func(t testing.TB) func() { - return tst.MockHTTPClient(t, - map[string]string{ - "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", - "https://example.com/schema-delivery-address.json-ld": "testdata/schema-delivery-address.json-ld", - }, - // requests are cached, so we don't check them on second and - // further runs - tst.IgnoreUntouchedURLs(), - ) - } - - ctx := context.Background() - - t.Run("type from internal field", func(t *testing.T) { - defer mockHTTP(t)() - rdr := strings.NewReader(` -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld" - ], - "@type": [ - "VerifiableCredential", - "DeliverAddressMultiTestForked" - ], - "credentialSubject": { - "isPostalProvider": false, - "postalProviderInformation": { - "insured": true, - "weight": "1.3" - }, - "price": "123.52", - "type": "DeliverAddressMultiTestForked" - } -}`) - mz, err := merklize.MerklizeJSONLD(ctx, rdr) - require.NoError(t, err) - typeID, err := findCredentialType(mz) - require.NoError(t, err) - require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) - }) - - t.Run("type from top level", func(t *testing.T) { - defer mockHTTP(t)() - rdr := strings.NewReader(` -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld" - ], - "@type": [ - "VerifiableCredential", - "DeliverAddressMultiTestForked" - ], - "credentialSubject": { - "isPostalProvider": false, - "postalProviderInformation": { - "insured": true, - "weight": "1.3" - }, - "price": "123.52" - } -}`) - mz, err := merklize.MerklizeJSONLD(ctx, rdr) - require.NoError(t, err) - typeID, err := findCredentialType(mz) - require.NoError(t, err) - require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) - }) - - t.Run("type from top level when internal incorrect", func(t *testing.T) { - defer mockHTTP(t)() - rdr := strings.NewReader(` -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld" - ], - "@type": [ - "VerifiableCredential", - "DeliverAddressMultiTestForked" - ], - "credentialSubject": { - "isPostalProvider": false, - "postalProviderInformation": { - "insured": true, - "weight": "1.3" - }, - "price": "123.52", - "type": ["EcdsaSecp256k1Signature2019", "EcdsaSecp256r1Signature2019"] - } -}`) - mz, err := merklize.MerklizeJSONLD(ctx, rdr) - require.NoError(t, err) - typeID, err := findCredentialType(mz) - require.NoError(t, err) - require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) - }) - - t.Run("unexpected top level 1", func(t *testing.T) { - defer mockHTTP(t)() - rdr := strings.NewReader(` -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld" - ], - "@type": [ - "VerifiableCredential", - "DeliverAddressMultiTestForked", - "EcdsaSecp256k1Signature2019" - ] -}`) - mz, err := merklize.MerklizeJSONLD(ctx, rdr) - require.NoError(t, err) - _, err = findCredentialType(mz) - require.EqualError(t, err, "top level @type expected to be of length 2") - }) - - t.Run("unexpected top level 2", func(t *testing.T) { - defer mockHTTP(t)() - rdr := strings.NewReader(` -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://example.com/schema-delivery-address.json-ld" - ], - "@type": ["DeliverAddressMultiTestForked", "EcdsaSecp256k1Signature2019"] -}`) - mz, err := merklize.MerklizeJSONLD(ctx, rdr) - require.NoError(t, err) - _, err = findCredentialType(mz) - require.EqualError(t, err, - "@type(s) are expected to contain VerifiableCredential type") - }) - -} diff --git a/processor/processor.go b/processor/processor.go index c63f707..048fe11 100644 --- a/processor/processor.go +++ b/processor/processor.go @@ -5,7 +5,6 @@ import ( "encoding/json" core "github.com/iden3/go-iden3-core/v2" - "github.com/iden3/go-schema-processor/v2/merklize" "github.com/iden3/go-schema-processor/v2/verifiable" "github.com/piprate/json-gold/ld" "github.com/pkg/errors" @@ -25,20 +24,15 @@ type Validator interface { // Parser is an interface to parse claim slots type Parser interface { + // Deprecated: use credential.ToCoreClaim instead ParseClaim(ctx context.Context, credential verifiable.W3CCredential, options *CoreClaimOptions) (*core.Claim, error) GetFieldSlotIndex(field string, typeName string, schema []byte) (int, error) } // CoreClaimOptions is params for core claim parsing -type CoreClaimOptions struct { - RevNonce uint64 `json:"revNonce"` - Version uint32 `json:"version"` - SubjectPosition string `json:"subjectPosition"` - MerklizedRootPosition string `json:"merklizedRootPosition"` - Updatable bool `json:"updatable"` - MerklizerOpts []merklize.MerklizeOption -} +// Deprecated: use verifiable.CoreClaimOptions instead +type CoreClaimOptions verifiable.CoreClaimOptions var ( errParserNotDefined = errors.New("parser is not defined") diff --git a/verifiable/core_utils.go b/verifiable/core_utils.go new file mode 100644 index 0000000..89f6134 --- /dev/null +++ b/verifiable/core_utils.go @@ -0,0 +1,295 @@ +package verifiable + +import ( + "fmt" + "strings" + + "github.com/iden3/go-schema-processor/v2/merklize" + "github.com/iden3/go-schema-processor/v2/utils" + "github.com/piprate/json-gold/ld" + "github.com/pkg/errors" +) + +const ( + credentialSubjectKey = "credentialSubject" + //nolint:gosec // G101: this is not a hardcoded credential + credentialSubjectFullKey = "https://www.w3.org/2018/credentials#credentialSubject" + //nolint:gosec // G101: this is not a hardcoded credential + verifiableCredentialFullKey = "https://www.w3.org/2018/credentials#VerifiableCredential" + typeFullKey = "@type" + contextFullKey = "@context" + serializationFullKey = "iden3_serialization" +) + +// CoreClaimOptions is params for core claim parsing +type CoreClaimOptions struct { + RevNonce uint64 `json:"revNonce"` + Version uint32 `json:"version"` + SubjectPosition string `json:"subjectPosition"` + MerklizedRootPosition string `json:"merklizedRootPosition"` + Updatable bool `json:"updatable"` + MerklizerOpts []merklize.MerklizeOption +} + +func findCredentialType(mz *merklize.Merklizer) (string, error) { + opts := mz.Options() + + // try to look into credentialSubject.@type to get type of credentials + path1, err := opts.NewPath(credentialSubjectFullKey, typeFullKey) + if err == nil { + var e any + e, err = mz.RawValue(path1) + if err == nil { + tp, ok := e.(string) + if ok { + return tp, nil + } + } + } + + // if type of credentials not found in credentialSubject.@type, loop at + // top level @types if it contains two elements: type we are looking for + // and "VerifiableCredential" type. + path2, err := opts.NewPath(typeFullKey) + if err != nil { + return "", err + } + + e, err := mz.RawValue(path2) + if err != nil { + return "", err + } + + eArr, ok := e.([]any) + if !ok { + return "", fmt.Errorf("top level @type expected to be an array") + } + topLevelTypes, err := toStringSlice(eArr) + if err != nil { + return "", err + } + if len(topLevelTypes) != 2 { + return "", fmt.Errorf("top level @type expected to be of length 2") + } + + switch verifiableCredentialFullKey { + case topLevelTypes[0]: + return topLevelTypes[1], nil + case topLevelTypes[1]: + return topLevelTypes[0], nil + default: + return "", fmt.Errorf( + "@type(s) are expected to contain VerifiableCredential type") + } +} + +func toStringSlice(in []any) ([]string, error) { + out := make([]string, len(in)) + for i, v := range in { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("element #%v is not a string", i) + } + out[i] = s + } + return out, nil +} + +// parsedSlots is struct that represents iden3 claim specification +type parsedSlots struct { + IndexA, IndexB []byte + ValueA, ValueB []byte +} + +// parseSlots converts payload to claim slots using provided schema +func parseSlots(mz *merklize.Merklizer, + credential W3CCredential, + credentialType string) (parsedSlots, bool, error) { + + slots := parsedSlots{ + IndexA: make([]byte, 32), + IndexB: make([]byte, 32), + ValueA: make([]byte, 32), + ValueB: make([]byte, 32), + } + + jsonLDOpts := mz.Options().JSONLDOptions() + serAttr, err := getSerializationAttr(credential, jsonLDOpts, + credentialType) + if err != nil { + return slots, false, err + } + + if serAttr == "" { + return slots, false, nil + } + + sPaths, err := ParseSerializationAttr(serAttr) + if err != nil { + return slots, true, err + } + + if sPaths.isEmpty() { + return slots, true, nil + } + + err = fillSlot(slots.IndexA, mz, sPaths.IndexAPath) + if err != nil { + return slots, true, err + } + err = fillSlot(slots.IndexB, mz, sPaths.IndexBPath) + if err != nil { + return slots, true, err + } + err = fillSlot(slots.ValueA, mz, sPaths.ValueAPath) + if err != nil { + return slots, true, err + } + err = fillSlot(slots.ValueB, mz, sPaths.ValueBPath) + if err != nil { + return slots, true, err + } + + return slots, true, nil +} + +// Get `iden3_serialization` attr definition from context document either using +// type name like DeliverAddressMultiTestForked or by type id like +// urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100. +func getSerializationAttr(credential W3CCredential, + opts *ld.JsonLdOptions, tp string) (string, error) { + + ldCtx, err := ld.NewContext(nil, opts).Parse(anySlice(credential.Context)) + if err != nil { + return "", err + } + + return GetSerializationAttrFromParsedContext(ldCtx, tp) +} + +// convert from the slice of concrete type to the slice of interface{} +func anySlice[T any](in []T) []any { + if in == nil { + return nil + } + s := make([]any, len(in)) + for i := range in { + s[i] = in[i] + } + return s +} + +func GetSerializationAttrFromParsedContext(ldCtx *ld.Context, + tp string) (string, error) { + + termDef, ok := ldCtx.AsMap()["termDefinitions"] + if !ok { + return "", errors.New("types now found in context") + } + + termDefM, ok := termDef.(map[string]any) + if !ok { + return "", errors.New("terms definitions is not of correct type") + } + + for typeName, typeDef := range termDefM { + typeDefM, ok := typeDef.(map[string]any) + if !ok { + // not a type + continue + } + typeCtx, ok := typeDefM[contextFullKey] + if !ok { + // not a type + continue + } + typeCtxM, ok := typeCtx.(map[string]any) + if !ok { + return "", errors.New("type @context is not of correct type") + } + typeID, _ := typeDefM["@id"].(string) + if typeName != tp && typeID != tp { + continue + } + + serStr, _ := typeCtxM[serializationFullKey].(string) + return serStr, nil + } + + return "", nil +} + +type slotsPaths struct { + IndexAPath string + IndexBPath string + ValueAPath string + ValueBPath string +} + +func ParseSerializationAttr(serAttr string) (slotsPaths, error) { + prefix := "iden3:v1:" + if !strings.HasPrefix(serAttr, prefix) { + return slotsPaths{}, + errors.New("serialization attribute does not have correct prefix") + } + parts := strings.Split(serAttr[len(prefix):], "&") + if len(parts) > 4 { + return slotsPaths{}, + errors.New("serialization attribute has too many parts") + } + var paths slotsPaths + for _, part := range parts { + kv := strings.Split(part, "=") + if len(kv) != 2 { + return slotsPaths{}, errors.New( + "serialization attribute part does not have correct format") + } + switch kv[0] { + case "slotIndexA": + paths.IndexAPath = kv[1] + case "slotIndexB": + paths.IndexBPath = kv[1] + case "slotValueA": + paths.ValueAPath = kv[1] + case "slotValueB": + paths.ValueBPath = kv[1] + default: + return slotsPaths{}, + errors.New("unknown serialization attribute slot") + } + } + return paths, nil +} + +func (p slotsPaths) isEmpty() bool { + return p.IndexAPath == "" && p.IndexBPath == "" && + p.ValueAPath == "" && p.ValueBPath == "" +} + +func fillSlot(slotData []byte, mz *merklize.Merklizer, path string) error { + if path == "" { + return nil + } + + path = credentialSubjectKey + "." + path + p, err := mz.ResolveDocPath(path) + if err != nil { + return errors.Wrapf(err, "field not found in credential %s", path) + } + + entry, err := mz.Entry(p) + if errors.Is(err, merklize.ErrorEntryNotFound) { + return errors.Wrapf(err, "field not found in credential %s", path) + } else if err != nil { + return err + } + + intVal, err := entry.ValueMtEntry() + if err != nil { + return err + } + + bytesVal := utils.SwapEndianness(intVal.Bytes()) + copy(slotData, bytesVal) + return nil +} diff --git a/verifiable/core_utils_test.go b/verifiable/core_utils_test.go new file mode 100644 index 0000000..2d51c31 --- /dev/null +++ b/verifiable/core_utils_test.go @@ -0,0 +1,229 @@ +package verifiable + +import ( + "context" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/iden3/go-schema-processor/v2/merklize" + tst "github.com/iden3/go-schema-processor/v2/testing" + "github.com/piprate/json-gold/ld" + "github.com/stretchr/testify/require" +) + +func TestParser_parseSlots(t *testing.T) { + defer tst.MockHTTPClient(t, + map[string]string{ + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", + "https://example.com/schema-delivery-address.json-ld": "../json/testdata/schema-delivery-address.json-ld", + }, + tst.IgnoreUntouchedURLs())() + + credentialBytes, err := os.ReadFile("../json/testdata/non-merklized-1.json-ld") + require.NoError(t, err) + + var credential W3CCredential + err = json.Unmarshal(credentialBytes, &credential) + require.NoError(t, err) + + nullSlot := make([]byte, 32) + ctx := context.Background() + + mz, err := credential.Merklize(ctx) + require.NoError(t, err) + + credentialType, err := findCredentialType(mz) + require.NoError(t, err) + + slots, nonMerklized, err := parseSlots(mz, credential, credentialType) + require.True(t, nonMerklized) + require.NoError(t, err) + require.NotEqual(t, nullSlot, slots.IndexA) + require.Equal(t, nullSlot, slots.IndexB) + require.Equal(t, nullSlot, slots.ValueA) + require.NotEqual(t, nullSlot, slots.ValueB) +} + +func TestGetSerializationAttr(t *testing.T) { + defer tst.MockHTTPClient(t, + map[string]string{ + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", + "https://example.com/schema-delivery-address.json-ld": "../json/testdata/schema-delivery-address.json-ld", + }, + tst.IgnoreUntouchedURLs())() + + vc := W3CCredential{ + Context: []string{ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld", + }, + } + + options := ld.NewJsonLdOptions("") + + t.Run("by type name", func(t *testing.T) { + serAttr, err := getSerializationAttr(vc, options, + "DeliverAddressMultiTestForked") + require.NoError(t, err) + require.Equal(t, + "iden3:v1:slotIndexA=price&slotValueB=postalProviderInformation.insured", + serAttr) + }) + + t.Run("by type id", func(t *testing.T) { + serAttr, err := getSerializationAttr(vc, options, + "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100") + require.NoError(t, err) + require.Equal(t, + "iden3:v1:slotIndexA=price&slotValueB=postalProviderInformation.insured", + serAttr) + }) + + t.Run("unknown type", func(t *testing.T) { + serAttr, err := getSerializationAttr(vc, options, "bla-bla") + require.NoError(t, err) + require.Equal(t, "", serAttr) + }) +} + +func TestFindCredentialType(t *testing.T) { + mockHTTP := func(t testing.TB) func() { + return tst.MockHTTPClient(t, + map[string]string{ + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", + "https://example.com/schema-delivery-address.json-ld": "../json/testdata/schema-delivery-address.json-ld", + }, + // requests are cached, so we don't check them on second and + // further runs + tst.IgnoreUntouchedURLs(), + ) + } + + ctx := context.Background() + + t.Run("type from internal field", func(t *testing.T) { + defer mockHTTP(t)() + rdr := strings.NewReader(` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld" + ], + "@type": [ + "VerifiableCredential", + "DeliverAddressMultiTestForked" + ], + "credentialSubject": { + "isPostalProvider": false, + "postalProviderInformation": { + "insured": true, + "weight": "1.3" + }, + "price": "123.52", + "type": "DeliverAddressMultiTestForked" + } +}`) + mz, err := merklize.MerklizeJSONLD(ctx, rdr) + require.NoError(t, err) + typeID, err := findCredentialType(mz) + require.NoError(t, err) + require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) + }) + + t.Run("type from top level", func(t *testing.T) { + defer mockHTTP(t)() + rdr := strings.NewReader(` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld" + ], + "@type": [ + "VerifiableCredential", + "DeliverAddressMultiTestForked" + ], + "credentialSubject": { + "isPostalProvider": false, + "postalProviderInformation": { + "insured": true, + "weight": "1.3" + }, + "price": "123.52" + } +}`) + mz, err := merklize.MerklizeJSONLD(ctx, rdr) + require.NoError(t, err) + typeID, err := findCredentialType(mz) + require.NoError(t, err) + require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) + }) + + t.Run("type from top level when internal incorrect", func(t *testing.T) { + defer mockHTTP(t)() + rdr := strings.NewReader(` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld" + ], + "@type": [ + "VerifiableCredential", + "DeliverAddressMultiTestForked" + ], + "credentialSubject": { + "isPostalProvider": false, + "postalProviderInformation": { + "insured": true, + "weight": "1.3" + }, + "price": "123.52", + "type": ["EcdsaSecp256k1Signature2019", "EcdsaSecp256r1Signature2019"] + } +}`) + mz, err := merklize.MerklizeJSONLD(ctx, rdr) + require.NoError(t, err) + typeID, err := findCredentialType(mz) + require.NoError(t, err) + require.Equal(t, "urn:uuid:ac2ede19-b3b9-454d-b1a9-a7b3d5763100", typeID) + }) + + t.Run("unexpected top level 1", func(t *testing.T) { + defer mockHTTP(t)() + rdr := strings.NewReader(` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld" + ], + "@type": [ + "VerifiableCredential", + "DeliverAddressMultiTestForked", + "EcdsaSecp256k1Signature2019" + ] +}`) + mz, err := merklize.MerklizeJSONLD(ctx, rdr) + require.NoError(t, err) + _, err = findCredentialType(mz) + require.EqualError(t, err, "top level @type expected to be of length 2") + }) + + t.Run("unexpected top level 2", func(t *testing.T) { + defer mockHTTP(t)() + rdr := strings.NewReader(` +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://example.com/schema-delivery-address.json-ld" + ], + "@type": ["DeliverAddressMultiTestForked", "EcdsaSecp256k1Signature2019"] +}`) + mz, err := merklize.MerklizeJSONLD(ctx, rdr) + require.NoError(t, err) + _, err = findCredentialType(mz) + require.EqualError(t, err, + "@type(s) are expected to contain VerifiableCredential type") + }) + +} diff --git a/verifiable/credential.go b/verifiable/credential.go index 047272c..f77e829 100644 --- a/verifiable/credential.go +++ b/verifiable/credential.go @@ -15,6 +15,7 @@ import ( "github.com/iden3/go-iden3-crypto/poseidon" "github.com/iden3/go-merkletree-sql/v2" "github.com/iden3/go-schema-processor/v2/merklize" + "github.com/iden3/go-schema-processor/v2/utils" "github.com/pkg/errors" ) @@ -60,6 +61,11 @@ func (vc *W3CCredential) VerifyProof(ctx context.Context, proofType ProofType, return errors.New("can't get core claim") } + err = vc.verifyCredentialCoreClaim(ctx, coreClaim, verifyConfig.merklizeOptions) + if err != nil { + return errors.WithStack(err) + } + switch proofType { case BJJSignatureProofType: var proof BJJSignatureProof2021 @@ -82,6 +88,66 @@ func (vc *W3CCredential) VerifyProof(ctx context.Context, proofType ProofType, } } +func (vc *W3CCredential) verifyCredentialCoreClaim(ctx context.Context, proofCoreClaim *core.Claim, merklizeOptions []merklize.MerklizeOption) error { + merklizedPosition, err := proofCoreClaim.GetMerklizedPosition() + if err != nil { + return errors.New("can't get core claim merklized position") + } + var merklizedPositionString string + switch merklizedPosition { + case core.MerklizedRootPositionNone: + merklizedPositionString = CredentialMerklizedRootPositionNone + case core.MerklizedRootPositionIndex: + merklizedPositionString = CredentialMerklizedRootPositionIndex + case core.MerklizedRootPositionValue: + merklizedPositionString = CredentialMerklizedRootPositionValue + } + + idPosition, err := proofCoreClaim.GetIDPosition() + if err != nil { + return errors.New("can't get core claim id position") + } + + var subjectPositionString string + switch idPosition { + case core.IDPositionNone: + subjectPositionString = "" + case core.IDPositionIndex: + subjectPositionString = CredentialSubjectPositionIndex + case core.IDPositionValue: + subjectPositionString = CredentialSubjectPositionValue + } + + coreClaimOpts := CoreClaimOptions{ + RevNonce: proofCoreClaim.GetRevocationNonce(), + Version: proofCoreClaim.GetVersion(), + SubjectPosition: subjectPositionString, + MerklizedRootPosition: merklizedPositionString, + Updatable: proofCoreClaim.GetFlagUpdatable(), + MerklizerOpts: merklizeOptions, + } + credentialClaim, err := vc.ToCoreClaim(ctx, &coreClaimOpts) + if err != nil { + return errors.WithStack(err) + } + + coreClaimHex, err := proofCoreClaim.Hex() + if err != nil { + return errors.New("can't get core claim hex") + } + + credentialClaimHex, err := credentialClaim.Hex() + if err != nil { + return errors.New("can't get credential claim hex") + } + + if coreClaimHex != credentialClaimHex { + return errors.New("proof generated for another credential") + } + + return nil +} + func verifyBJJSignatureProof(ctx context.Context, proof BJJSignatureProof2021, coreClaim *core.Claim, didResolver DIDResolver, verifyConfig w3CProofVerificationConfig) error { @@ -348,6 +414,109 @@ func (vc *W3CCredential) GetCoreClaimFromProof(proofType ProofType) (*core.Claim return nil, ErrProofNotFound } +// ToCoreClaim returns Claim object from W3CCredential +func (vc *W3CCredential) ToCoreClaim(ctx context.Context, opts *CoreClaimOptions) (*core.Claim, error) { + if opts == nil { + opts = &CoreClaimOptions{ + RevNonce: 0, + Version: 0, + SubjectPosition: CredentialSubjectPositionIndex, + MerklizedRootPosition: CredentialMerklizedRootPositionNone, + Updatable: false, + MerklizerOpts: nil, + } + } + + mz, err := vc.Merklize(ctx, opts.MerklizerOpts...) + if err != nil { + return nil, err + } + + credentialType, err := findCredentialType(mz) + if err != nil { + return nil, err + } + + subjectID := vc.CredentialSubject["id"] + + slots, nonMerklized, err := parseSlots(mz, *vc, credentialType) + if err != nil { + return nil, err + } + + // if schema is for non merklized credential, root position must be set to none ('') + // otherwise default position for merklized position is index. + if !nonMerklized { + if opts.MerklizedRootPosition == CredentialMerklizedRootPositionNone { + opts.MerklizedRootPosition = CredentialMerklizedRootPositionIndex + } + } else { + if opts.MerklizedRootPosition != CredentialMerklizedRootPositionNone { + return nil, errors.New( + "merklized root position is not supported for non-merklized claims") + } + } + + claim, err := core.NewClaim( + utils.CreateSchemaHash([]byte(credentialType)), + core.WithIndexDataBytes(slots.IndexA, slots.IndexB), + core.WithValueDataBytes(slots.ValueA, slots.ValueB), + core.WithRevocationNonce(opts.RevNonce), + core.WithVersion(opts.Version)) + if err != nil { + return nil, err + } + if opts.Updatable { + claim.SetFlagUpdatable(opts.Updatable) + } + + if vc.Expiration != nil { + claim.SetExpirationDate(*vc.Expiration) + } + if subjectID != nil { + var did *w3c.DID + did, err = w3c.ParseDID(fmt.Sprintf("%v", subjectID)) + if err != nil { + return nil, err + } + + var id core.ID + id, err = core.IDFromDID(*did) + if err != nil { + return nil, err + } + + switch opts.SubjectPosition { + case "", CredentialSubjectPositionIndex: + claim.SetIndexID(id) + case CredentialSubjectPositionValue: + claim.SetValueID(id) + default: + return nil, errors.New("unknown subject position") + } + } + + switch opts.MerklizedRootPosition { + case CredentialMerklizedRootPositionIndex: + err = claim.SetIndexMerklizedRoot(mz.Root().BigInt()) + if err != nil { + return nil, err + } + case CredentialMerklizedRootPositionValue: + err = claim.SetValueMerklizedRoot(mz.Root().BigInt()) + if err != nil { + return nil, err + } + case CredentialMerklizedRootPositionNone: + // Slots where filled earlier. Nothing to do here. + break + default: + return nil, errors.New("unknown merklized root position") + } + + return claim, nil +} + // CredentialSchema represent the information about credential schema type CredentialSchema struct { ID string `json:"id"` @@ -401,4 +570,5 @@ type W3CProofVerificationOpt func(opts *w3CProofVerificationConfig) // w3CProofVerificationConfig options for W3C proof verification type w3CProofVerificationConfig struct { credStatusValidationOpts []CredentialStatusValidationOption + merklizeOptions []merklize.MerklizeOption } diff --git a/verifiable/credential_test.go b/verifiable/credential_test.go index 0f32cbe..e8c6857 100644 --- a/verifiable/credential_test.go +++ b/verifiable/credential_test.go @@ -93,8 +93,11 @@ func TestW3CCredential_ValidateBJJSignatureProof(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld": "../merklize/testdata/httpresp/kyc-v3.json-ld", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld": "../merklize/testdata/httpresp/iden3proofs.json-ld", + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf?state=f9dd6aa4e1abef52b6c94ab7eb92faf1a283b371d263e25ac835c9c04894741e": `./testdata/verifycred//my-universal-resolver-1.json`, - })() + }, tst.IgnoreUntouchedURLs())() resolverRegisty := CredentialStatusResolverRegistry{} rhsResolver := test1Resolver{} resolverRegisty.Register(Iden3ReverseSparseMerkleTreeProof, rhsResolver) @@ -185,8 +188,11 @@ func TestW3CCredential_ValidateBJJSignatureProofGenesis(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld": "../merklize/testdata/httpresp/kyc-v3.json-ld", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld": "../merklize/testdata/httpresp/iden3proofs.json-ld", + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qLx3hTJBV8REpNDK2RiG7eNBVzXMoZdPfi2uhF7Ks?state=da6184809dbad90ccc52bb4dbfe2e8ff3f516d87c74d75bcc68a67101760b817": `./testdata/verifycred//my-universal-resolver-2.json`, - })() + }, tst.IgnoreUntouchedURLs())() resolverRegisty := CredentialStatusResolverRegistry{} rhsResolver := test2Resolver{} @@ -294,8 +300,11 @@ func TestW3CCredential_ValidateIden3SparseMerkleTreeProof(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld": "../merklize/testdata/httpresp/kyc-v3.json-ld", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld": "../merklize/testdata/httpresp/iden3proofs.json-ld", + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qLGnFZiHrhdNh5KwdkGvbCN1sR2pUaBpBahAXC3zf?state=34824a8e1defc326f935044e32e9f513377dbfc031d79475a0190830554d4409": `./testdata/verifycred//my-universal-resolver-3.json`, - })() + }, tst.IgnoreUntouchedURLs())() err = vc.VerifyProof(context.Background(), Iden3SparseMerkleTreeProofType, HTTPDIDResolver{resolverURL: resolverURL}) @@ -396,8 +405,11 @@ func TestW3CCredential_ValidateBJJSignatureProofAgentStatus(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld": "../merklize/testdata/httpresp/kyc-v3.json-ld", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld": "../merklize/testdata/httpresp/iden3proofs.json-ld", + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qJp131YoXVu8iLNGfL3TkQAWEr3pqimh2iaPgH3BJ?state=2de39210318bbc7fc79e24150c2790089c8385d7acffc0f0ebf1641b95087e0f": `./testdata/verifycred//my-universal-resolver-4.json`, - })() + }, tst.IgnoreUntouchedURLs())() resolverRegisty := CredentialStatusResolverRegistry{} resolverRegisty.Register(Iden3commRevocationStatusV1, test3Resolver{}) @@ -470,9 +482,12 @@ func TestW3CCredential_ValidateBJJSignatureProofIssuerStatus(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ + "https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v3.json-ld": "../merklize/testdata/httpresp/kyc-v3.json-ld", + "https://schema.iden3.io/core/jsonld/iden3proofs.jsonld": "../merklize/testdata/httpresp/iden3proofs.json-ld", + "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "http://my-universal-resolver/1.0/identifiers/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn?state=95e4f8437be5d50a569bb532713110e4f5d2ac97765fae54041dddae9638a119": `./testdata/verifycred/my-universal-resolver-5.json`, "http://localhost:8001/api/v1/identities/did%3Apolygonid%3Apolygon%3Amumbai%3A2qNuE5Jxmvrx6EithQ5bMs4DcWN91SjxepUzdQtddn/claims/revocation/status/0": `./testdata/verifycred/issuer-state-response.json`, - })() + }, tst.IgnoreUntouchedURLs())() resolverRegisty := CredentialStatusResolverRegistry{} resolverRegisty.Register(SparseMerkleTreeProof, IssuerResolver{}) @@ -652,7 +667,7 @@ func TestW3CCredential_MerklizationWithEmptyID(t *testing.T) { defer tst.MockHTTPClient(t, map[string]string{ "https://www.w3.org/2018/credentials/v1": "../merklize/testdata/httpresp/credentials-v1.jsonld", "https://example.com/schema-delivery-address.json-ld": "../json/testdata/schema-delivery-address.json-ld", - })() + }, tst.IgnoreUntouchedURLs())() vcData, err := os.ReadFile("../json/testdata/non-merklized-1.json-ld") require.NoError(t, err)