func CreateCustomCredential(vcData []byte, producers []CustomCredentialProducer,
	opts ...CredentialOpt) (interface{}, error) {
	vcBase, credErr := ParseCredential(vcData, opts...)
	if credErr != nil {
		return nil, fmt.Errorf("build base verifiable credential: %w", credErr)
	}
	vcBaseBytes, _ := vcBase.MarshalJSON()  
	for _, p := range producers {
		if p.Accept(vcBase) {
			customCred, err := p.Apply(vcBase, vcBaseBytes)
			if err != nil {
				return nil, fmt.Errorf("build extended verifiable credential: %w", err)
			}
			return customCred, nil
		}
	}
	return vcBase, nil
}
CreateCustomCredential creates custom extended credentials from bytes which could be marshalled JSON
or serialized JWT. It parses input bytes to the base Verifiable Credential using ParseCredential().
It then checks all producers to find the capable one to build extended Credential data model.
If none of producers accept the credential, the base credential is returned.
package verifiable
import (
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"reflect"
	"time"
	jsonld "github.com/piprate/json-gold/ld"
	"github.com/xeipuuv/gojsonschema"
	"github.com/hyperledger/aries-framework-go/pkg/common/log"
	"github.com/hyperledger/aries-framework-go/pkg/doc/jwt"
	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/verifier"
	"github.com/hyperledger/aries-framework-go/pkg/doc/util"
)
var logger = log.New("aries-framework/doc/verifiable")
const DefaultSchema = `{
  "required": [
    "@context",
    "type",
    "credentialSubject",
    "issuer",
    "issuanceDate"
  ],
  "properties": {
    "@context": {
      "anyOf": [
        {
          "type": "string",
          "const": "https://www.w3.org/2018/credentials/v1"
        },
        {
          "type": "array",
          "items": [
            {
              "type": "string",
              "const": "https://www.w3.org/2018/credentials/v1"
            }
          ],
          "uniqueItems": true,
          "additionalItems": {
            "anyOf": [
              {
                "type": "object"
              },
              {
                "type": "string"
              }
            ]
          }
        }
      ]
    },
    "id": {
      "type": "string",
      "format": "uri"
    },
    "type": {
      "oneOf": [
        {
          "type": "array",
          "minItems": 1,
          "contains": {
            "type": "string",
            "pattern": "^VerifiableCredential$"
          }
        },
        {
          "type": "string",
          "pattern": "^VerifiableCredential$"
        }
      ]
    },
    "credentialSubject": {
      "anyOf": [
        {
          "type": "array"
        },
        {
          "type": "object"
        },
        {
          "type": "string"
        }
      ]
    },
    "issuer": {
      "anyOf": [
        {
          "type": "string",
          "format": "uri"
        },
        {
          "type": "object",
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      ]
    },
    "issuanceDate": {
      "type": "string",
      "format": "date-time"
    },
    "proof": {
      "anyOf": [
        {
          "$ref": "#/definitions/proof"
        },
        {
          "type": "array",
          "items": {
            "$ref": "#/definitions/proof"
          }
        },
        {
          "type": "null"
        }
      ]
    },
    "expirationDate": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "credentialStatus": {
      "$ref": "#/definitions/typedID"
    },
    "credentialSchema": {
      "$ref": "#/definitions/typedIDs"
    },
    "evidence": {
      "$ref": "#/definitions/typedIDs"
    },
    "refreshService": {
      "$ref": "#/definitions/typedID"
    }
  },
  "definitions": {
    "typedID": {
      "anyOf": [
        {
          "type": "null"
        },
        {
          "type": "object",
          "required": [
            "id",
            "type"
          ],
          "properties": {
            "id": {
              "type": "string",
              "format": "uri"
            },
            "type": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            }
          }
        }
      ]
    },
    "typedIDs": {
      "anyOf": [
        {
          "$ref": "#/definitions/typedID"
        },
        {
          "type": "array",
          "items": {
            "$ref": "#/definitions/typedID"
          }
        },
        {
          "type": "null"
        }
      ]
    },
    "proof": {
      "type": "object",
      "required": [
        "type"
      ],
      "properties": {
        "type": {
          "type": "string"
        }
      }
    }
  }
}
`
const jsonSchema2018Type = "JsonSchemaValidator2018"
const (
	baseContext = "https://www.w3.org/2018/credentials/v1"
	vcType = "VerifiableCredential"
	vpType = "VerifiablePresentation"
)
type vcModelValidationMode int
const (
	combinedValidation vcModelValidationMode = iota
	jsonldValidation
	baseContextValidation
	baseContextExtendedValidation
)
type SchemaCache interface {
	Put(k string, v []byte)
	Get(k string) ([]byte, bool)
}
type cache interface {
	Set(k, v []byte)
	HasGet(dst, k []byte) ([]byte, bool)
	Del(k []byte)
}
type ExpirableSchemaCache struct {
	cache      cache
	expiration time.Duration
}
type CredentialSchemaLoader struct {
	schemaDownloadClient *http.Client
	cache                SchemaCache
	jsonLoader           gojsonschema.JSONLoader
}
type CredentialSchemaLoaderBuilder struct {
	loader *CredentialSchemaLoader
}
func NewCredentialSchemaLoaderBuilder() *CredentialSchemaLoaderBuilder {
	return &CredentialSchemaLoaderBuilder{
		loader: &CredentialSchemaLoader{},
	}
}
func (b *CredentialSchemaLoaderBuilder) SetSchemaDownloadClient(client *http.Client) *CredentialSchemaLoaderBuilder {
	b.loader.schemaDownloadClient = client
	return b
}
func (b *CredentialSchemaLoaderBuilder) SetCache(cache SchemaCache) *CredentialSchemaLoaderBuilder {
	b.loader.cache = cache
	return b
}
func (b *CredentialSchemaLoaderBuilder) SetJSONLoader(loader gojsonschema.JSONLoader) *CredentialSchemaLoaderBuilder {
	b.loader.jsonLoader = loader
	return b
}
func (b *CredentialSchemaLoaderBuilder) Build() *CredentialSchemaLoader {
	l := b.loader
	if l.schemaDownloadClient == nil {
		l.schemaDownloadClient = &http.Client{}
	}
	if l.jsonLoader == nil {
		l.jsonLoader = defaultSchemaLoader()
	}
	return l
}
func (sc *ExpirableSchemaCache) Put(k string, v []byte) {
	expires := time.Now().Add(sc.expiration).Unix()
	const numBytesTime = 8
	b := make([]byte, numBytesTime)
	binary.LittleEndian.PutUint64(b, uint64(expires))
	ve := make([]byte, numBytesTime+len(v))
	copy(ve[:numBytesTime], b)
	copy(ve[numBytesTime:], v)
	sc.cache.Set([]byte(k), ve)
}
func (sc *ExpirableSchemaCache) Get(k string) ([]byte, bool) {
	b, ok := sc.cache.HasGet(nil, []byte(k))
	if !ok {
		return nil, false
	}
	const numBytesTime = 8
	expires := int64(binary.LittleEndian.Uint64(b[:numBytesTime]))
	if expires < time.Now().Unix() {
		sc.cache.Del([]byte(k))
		return nil, false
	}
	return b[numBytesTime:], true
}
type Evidence interface{}
type Issuer struct {
	ID string `json:"id,omitempty"`
	CustomFields CustomFields `json:"-"`
}
func (i *Issuer) MarshalJSON() ([]byte, error) {
	if len(i.CustomFields) == 0 {
		return json.Marshal(i.ID)
	}
	type Alias Issuer
	alias := Alias(*i)
	data, err := marshalWithCustomFields(alias, i.CustomFields)
	if err != nil {
		return nil, fmt.Errorf("marshal Issuer: %w", err)
	}
	return data, nil
}
func (i *Issuer) UnmarshalJSON(bytes []byte) error {
	var issuerID string
	if err := json.Unmarshal(bytes, &issuerID); err == nil {
		i.ID = issuerID
		return nil
	}
	type Alias Issuer
	alias := (*Alias)(i)
	i.CustomFields = make(CustomFields)
	err := unmarshalWithCustomFields(bytes, alias, i.CustomFields)
	if err != nil {
		return fmt.Errorf("unmarshal Issuer: %w", err)
	}
	if i.ID == "" {
		return errors.New("issuer ID is not defined")
	}
	return nil
}
type Subject struct {
	ID string `json:"id,omitempty"`
	CustomFields CustomFields `json:"-"`
}
func (s *Subject) MarshalJSON() ([]byte, error) {
	if len(s.CustomFields) == 0 {
		return json.Marshal(s.ID)
	}
	type Alias Subject
	alias := Alias(*s)
	data, err := marshalWithCustomFields(alias, s.CustomFields)
	if err != nil {
		return nil, fmt.Errorf("marshal Subject: %w", err)
	}
	return data, nil
}
func (s *Subject) UnmarshalJSON(bytes []byte) error {
	var subjectID string
	if err := json.Unmarshal(bytes, &subjectID); err == nil {
		s.ID = subjectID
		return nil
	}
	type Alias Subject
	alias := (*Alias)(s)
	s.CustomFields = make(CustomFields)
	err := unmarshalWithCustomFields(bytes, alias, s.CustomFields)
	if err != nil {
		return fmt.Errorf("unmarshal Subject: %w", err)
	}
	return nil
}
type Credential struct {
	Context       []string
	CustomContext []interface{}
	ID            string
	Types         []string
	Subject        interface{}
	Issuer         Issuer
	Issued         *util.TimeWrapper
	Expired        *util.TimeWrapper
	Proofs         []Proof
	Status         *TypedID
	Schemas        []TypedID
	Evidence       Evidence
	TermsOfUse     []TypedID
	RefreshService []TypedID
	CustomFields CustomFields
}
type rawCredential struct {
	Context        interface{}       `json:"@context,omitempty"`
	ID             string            `json:"id,omitempty"`
	Type           interface{}       `json:"type,omitempty"`
	Subject        json.RawMessage   `json:"credentialSubject,omitempty"`
	Issued         *util.TimeWrapper `json:"issuanceDate,omitempty"`
	Expired        *util.TimeWrapper `json:"expirationDate,omitempty"`
	Proof          json.RawMessage   `json:"proof,omitempty"`
	Status         *TypedID          `json:"credentialStatus,omitempty"`
	Issuer         json.RawMessage   `json:"issuer,omitempty"`
	Schema         interface{}       `json:"credentialSchema,omitempty"`
	Evidence       Evidence          `json:"evidence,omitempty"`
	TermsOfUse     json.RawMessage   `json:"termsOfUse,omitempty"`
	RefreshService json.RawMessage   `json:"refreshService,omitempty"`
	CustomFields `json:"-"`
}
func (rc *rawCredential) MarshalJSON() ([]byte, error) {
	type Alias rawCredential
	alias := (*Alias)(rc)
	return marshalWithCustomFields(alias, rc.CustomFields)
}
func (rc *rawCredential) UnmarshalJSON(data []byte) error {
	type Alias rawCredential
	alias := (*Alias)(rc)
	rc.CustomFields = make(CustomFields)
	err := unmarshalWithCustomFields(data, alias, rc.CustomFields)
	if err != nil {
		return err
	}
	return nil
}
type CredentialDecoder func(dataJSON []byte, vc *Credential) error
type CredentialTemplate func() *Credential
type credentialOpts struct {
	publicKeyFetcher      PublicKeyFetcher
	disabledCustomSchema  bool
	schemaLoader          *CredentialSchemaLoader
	modelValidationMode   vcModelValidationMode
	allowedCustomContexts map[string]bool
	allowedCustomTypes    map[string]bool
	disabledProofCheck    bool
	strictValidation      bool
	ldpSuites             []verifier.SignatureSuite
	jsonldCredentialOpts
}
type CredentialOpt func(opts *credentialOpts)
func WithDisabledProofCheck() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.disabledProofCheck = true
	}
}
func WithNoCustomSchemaCheck() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.disabledCustomSchema = true
	}
}
func WithPublicKeyFetcher(fetcher PublicKeyFetcher) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.publicKeyFetcher = fetcher
	}
}
func WithCredentialSchemaLoader(loader *CredentialSchemaLoader) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.schemaLoader = loader
	}
}
func WithJSONLDValidation() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.modelValidationMode = jsonldValidation
	}
}
func WithBaseContextValidation() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.modelValidationMode = baseContextValidation
	}
}
func WithBaseContextExtendedValidation(customContexts, customTypes []string) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.modelValidationMode = baseContextExtendedValidation
		opts.allowedCustomContexts = make(map[string]bool)
		for _, context := range customContexts {
			opts.allowedCustomContexts[context] = true
		}
		opts.allowedCustomContexts[baseContext] = true
		opts.allowedCustomTypes = make(map[string]bool)
		for _, context := range customTypes {
			opts.allowedCustomTypes[context] = true
		}
		opts.allowedCustomTypes[vcType] = true
	}
}
func WithJSONLDDocumentLoader(documentLoader jsonld.DocumentLoader) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.jsonldDocumentLoader = documentLoader
	}
}
func WithStrictValidation() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.strictValidation = true
	}
}
func WithExternalJSONLDContext(context ...string) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.externalContext = context
	}
}
func WithJSONLDOnlyValidRDF() CredentialOpt {
	return func(opts *credentialOpts) {
		opts.jsonldOnlyValidRDF = true
	}
}
func WithEmbeddedSignatureSuites(suites ...verifier.SignatureSuite) CredentialOpt {
	return func(opts *credentialOpts) {
		opts.ldpSuites = suites
	}
}
func parseIssuer(issuerBytes json.RawMessage) (Issuer, error) {
	if len(issuerBytes) == 0 {
		return Issuer{}, nil
	}
	var issuer Issuer
	err := json.Unmarshal(issuerBytes, &issuer)
	if err != nil {
		return Issuer{}, err
	}
	return issuer, err
}
func parseSubject(subjectBytes json.RawMessage) ([]Subject, error) {
	if len(subjectBytes) == 0 {
		return nil, nil
	}
	var subjectID string
	err := json.Unmarshal(subjectBytes, &subjectID)
	if err == nil {
		return []Subject{{
			ID:           subjectID,
			CustomFields: make(CustomFields),
		}}, nil
	}
	var subject Subject
	err = json.Unmarshal(subjectBytes, &subject)
	if err == nil {
		return []Subject{subject}, nil
	}
	var subjects []Subject
	err = json.Unmarshal(subjectBytes, &subjects)
	if err == nil {
		return subjects, nil
	}
	return nil, errors.New("verifiable credential subject of unsupported format")
}
func decodeCredentialSchemas(data *rawCredential) ([]TypedID, error) {
	switch schema := data.Schema.(type) {
	case []interface{}:
		tids := make([]TypedID, len(schema))
		for i := range schema {
			tid, err := newTypedID(schema[i])
			if err != nil {
				return nil, err
			}
			tids[i] = tid
		}
		return tids, nil
	case interface{}:
		tid, err := newTypedID(schema)
		if err != nil {
			return nil, err
		}
		return []TypedID{tid}, nil
	default:
		return nil, errors.New("verifiable credential schema of unsupported format")
	}
}
func ParseCredential(vcData []byte, opts ...CredentialOpt) (*Credential, error) {
	vcOpts := getCredentialOpts(opts)
	vcDataDecoded, err := decodeRaw(vcData, vcOpts)
	if err != nil {
		return nil, fmt.Errorf("decode new credential: %w", err)
	}
	var raw rawCredential
	err = json.Unmarshal(vcDataDecoded, &raw)
	if err != nil {
		return nil, fmt.Errorf("unmarshal new credential: %w", err)
	}
	vc, err := newCredential(&raw)
	if err != nil {
		return nil, fmt.Errorf("build new credential: %w", err)
	}
	err = validateCredential(vc, vcDataDecoded, vcOpts)
	if err != nil {
		return nil, err
	}
	return vc, nil
}
func validateCredential(vc *Credential, vcBytes []byte, vcOpts *credentialOpts) error {
	switch vcOpts.modelValidationMode {
	case combinedValidation:
		err := vc.validateJSONSchema(vcBytes, vcOpts)
		if err != nil {
			return err
		}
		return vc.validateJSONLD(vcBytes, vcOpts)
	case jsonldValidation:
		return vc.validateJSONLD(vcBytes, vcOpts)
	case baseContextValidation:
		return vc.validateBaseContext(vcBytes, vcOpts)
	case baseContextExtendedValidation:
		return vc.validateBaseContextWithExtendedValidation(vcOpts, vcBytes)
	default:
		return fmt.Errorf("unsupported vcModelValidationMode: %v", vcOpts.modelValidationMode)
	}
}
func (vc *Credential) validateBaseContext(vcBytes []byte, vcOpts *credentialOpts) error {
	if len(vc.Types) > 1 || vc.Types[0] != vcType {
		return errors.New("violated type constraint: not base only type defined")
	}
	if len(vc.Context) > 1 || vc.Context[0] != baseContext {
		return errors.New("violated @context constraint: not base only @context defined")
	}
	return vc.validateJSONSchema(vcBytes, vcOpts)
}
func (vc *Credential) validateBaseContextWithExtendedValidation(vcOpts *credentialOpts, vcBytes []byte) error {
	for _, vcContext := range vc.Context {
		if _, ok := vcOpts.allowedCustomContexts[vcContext]; !ok {
			return fmt.Errorf("not allowed @context: %s", vcContext)
		}
	}
	for _, vcType := range vc.Types {
		if _, ok := vcOpts.allowedCustomTypes[vcType]; !ok {
			return fmt.Errorf("not allowed type: %s", vcType)
		}
	}
	return vc.validateJSONSchema(vcBytes, vcOpts)
}
func (vc *Credential) validateJSONLD(vcBytes []byte, vcOpts *credentialOpts) error {
	return compactJSONLD(string(vcBytes), &vcOpts.jsonldCredentialOpts, vcOpts.strictValidation)
}
type CustomCredentialProducer interface {
	Accept(vc *Credential) bool
	Apply(vc *Credential, dataJSON []byte) (interface{}, error)
}