Repository: TBD54566975/ssi-service
Pull request #169 (2022-11-10 14:29:28)

Adding credential revocation
This change allows you to create a credential that is revocable 



If you choose to ad  "revoked": true to the creation of a verifiable credential it will create a credential status

 ```
 "credentialStatus": {
            "id": "http://localhost:8080/v1/credentials/e70fd408-a00d-4908-b218-23695f74e18b/status",
            "statusListCredential": "http://localhost:8080/v1/credentials/status/6865a53c-c079-425b-a8bc-26ccc834021e",
            "statusListIndex": "91561",
            "statusPurpose": "revocation",
            "type": "StatusList2021Entry"
        },
```

This will create a statusListCredential for that existing issuer,schema pair


Patch:
diff --git a/config/compose.toml b/config/compose.toml
index a1be04d..735a3b5 100644
--- a/config/compose.toml
+++ b/config/compose.toml
@@ -21,6 +21,7 @@ log_level = "info"
 
 [services]
 storage = "bolt"
+service_endpoint = "http://localhost:8000"
 
 # per-service configuration
 [services.keystore]
diff --git a/config/config.go b/config/config.go
index 34a6502..666c824 100644
--- a/config/config.go
+++ b/config/config.go
@@ -18,6 +18,8 @@ const (
 	ConfigFileName    = "config.toml"
 	ServiceName       = "ssi-service"
 	ConfigExtension   = ".toml"
+
+	DefaultServiceEndpoint = "http://localhost:8000"
 )
 
 type SSIServiceConfig struct {
@@ -45,9 +47,9 @@ type ServicesConfig struct {
 	// in the future it may make sense to have per-service storage providers (e.g. mysql for one service,
 	// mongo for another)
 	StorageProvider string `toml:"storage"`
+	ServiceEndpoint string `toml:"service_endpoint"`
 
 	// Embed all service-specific configs here. The order matters: from which should be instantiated first, to last
-
 	KeyStoreConfig     KeyStoreServiceConfig     `toml:"keystore,omitempty"`
 	DIDConfig          DIDServiceConfig          `toml:"did,omitempty"`
 	SchemaConfig       SchemaServiceConfig       `toml:"schema,omitempty"`
@@ -59,7 +61,8 @@ type ServicesConfig struct {
 // BaseServiceConfig represents configurable properties for a specific component of the SSI Service
 // Can be wrapped and extended for any specific service config
 type BaseServiceConfig struct {
-	Name string `toml:"name"`
+	Name            string `toml:"name"`
+	ServiceEndpoint string `toml:"service_endpoint"`
 }
 
 type KeyStoreServiceConfig struct {
@@ -102,6 +105,7 @@ func (s *SchemaServiceConfig) IsEmpty() bool {
 
 type CredentialServiceConfig struct {
 	*BaseServiceConfig
+
 	// TODO(gabe) supported key and signature types
 }
 
@@ -177,6 +181,7 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
 	if defaultConfig {
 		config.Services = ServicesConfig{
 			StorageProvider: "bolt",
+			ServiceEndpoint: DefaultServiceEndpoint,
 			KeyStoreConfig: KeyStoreServiceConfig{
 				BaseServiceConfig:  &BaseServiceConfig{Name: "keystore"},
 				ServiceKeyPassword: "default-password",
@@ -190,7 +195,7 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
 				BaseServiceConfig: &BaseServiceConfig{Name: "schema"},
 			},
 			CredentialConfig: CredentialServiceConfig{
-				BaseServiceConfig: &BaseServiceConfig{Name: "credential"},
+				BaseServiceConfig: &BaseServiceConfig{Name: "credential", ServiceEndpoint: DefaultServiceEndpoint},
 			},
 			ManifestConfig: ManifestServiceConfig{
 				BaseServiceConfig: &BaseServiceConfig{Name: "manifest"},
@@ -204,6 +209,12 @@ func LoadConfig(path string) (*SSIServiceConfig, error) {
 		if _, err := toml.DecodeFile(path, &config); err != nil {
 			return nil, errors.Wrapf(err, "could not load config: %s", path)
 		}
+
+		// apply defaults if not included in toml file
+		if config.Services.CredentialConfig.BaseServiceConfig.ServiceEndpoint == "" {
+			config.Services.CredentialConfig.BaseServiceConfig.ServiceEndpoint = config.Services.ServiceEndpoint
+		}
+
 	}
 
 	return &config, nil
diff --git a/config/config.toml b/config/config.toml
index b176e72..e9417d0 100644
--- a/config/config.toml
+++ b/config/config.toml
@@ -19,6 +19,7 @@ log_level = "debug"
 
 [services]
 storage = "bolt"
+service_endpoint = "http://localhost:8000"
 
 # per-service configuration
 [services.keystore]
diff --git a/go.mod b/go.mod
index 5e7e3f7..df366f2 100644
--- a/go.mod
+++ b/go.mod
@@ -31,6 +31,7 @@ require (
 )
 
 require (
+	github.com/bits-and-blooms/bitset v1.3.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
 	github.com/go-logr/logr v1.2.3 // indirect
diff --git a/go.sum b/go.sum
index 1675631..9c5e8cd 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221110170444-a9e67907c8f9 h1:46X
 github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221110170444-a9e67907c8f9/go.mod h1:c2wq4GipGxjAPXgnxNBvnuB7OqDNKjriKuD60H0Q6Ho=
 github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c=
 github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
+github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg=
+github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
diff --git a/internal/credential/model.go b/internal/credential/model.go
index 406b465..a215885 100644
--- a/internal/credential/model.go
+++ b/internal/credential/model.go
@@ -19,6 +19,7 @@ type Container struct {
 	ID            string
 	Credential    *credential.VerifiableCredential
 	CredentialJWT *keyaccess.JWT
+	Revoked       bool
 }
 
 func (c Container) JWTString() string {
diff --git a/pkg/server/router/credential.go b/pkg/server/router/credential.go
index 5a3b3e9..d3e1d4d 100644
--- a/pkg/server/router/credential.go
+++ b/pkg/server/router/credential.go
@@ -46,9 +46,10 @@ type CreateCredentialRequest struct {
 	// A context is optional. If not present, we'll apply default, required context values.
 	Context string `json:"@context"`
 	// A schema is optional. If present, we'll attempt to look it up and validate the data against it.
-	Schema string                 `json:"schema"`
-	Data   map[string]interface{} `json:"data" validate:"required"`
-	Expiry string                 `json:"expiry"`
+	Schema    string                 `json:"schema"`
+	Data      map[string]interface{} `json:"data" validate:"required"`
+	Expiry    string                 `json:"expiry"`
+	Revocable bool                   `json:"revocable"`
 	// TODO(gabe) support more capabilities like signature type, format, status, and more.
 }
 
@@ -60,6 +61,7 @@ func (c CreateCredentialRequest) ToServiceRequest() credential.CreateCredentialR
 		JSONSchema: c.Schema,
 		Data:       c.Data,
 		Expiry:     c.Expiry,
+		Revocable:  c.Revocable,
 	}
 }
 
@@ -145,6 +147,146 @@ func (cr CredentialRouter) GetCredential(ctx context.Context, w http.ResponseWri
 	return framework.Respond(ctx, w, resp, http.StatusOK)
 }
 
+type GetCredentialStatusResponse struct {
+	Revoked bool `json:"revoked"`
+}
+
+// GetCredentialStatus godoc
+// @Summary      Get Credential Status
+// @Description  Get credential status by id
+// @Tags         CredentialAPI
+// @Accept       json
+// @Produce      json
+// @Param        id   path      string  true  "ID"
+// @Success      200  {object}  GetCredentialStatusResponse
+// @Failure      400  {string}  string  "Bad request"
+// @Router       /v1/credentials/{id}/status [get]
+func (cr CredentialRouter) GetCredentialStatus(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
+	id := framework.GetParam(ctx, IDParam)
+	if id == nil {
+		errMsg := "cannot get credential without ID parameter"
+		logrus.Error(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	gotCredential, err := cr.service.GetCredential(credential.GetCredentialRequest{ID: *id})
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get credential with id: %s", *id)
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	resp := GetCredentialStatusResponse{
+		Revoked: gotCredential.Revoked,
+	}
+
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
+type GetCredentialStatusListResponse struct {
+	ID            string                        `json:"id"`
+	Credential    *credsdk.VerifiableCredential `json:"credential,omitempty"`
+	CredentialJWT *keyaccess.JWT                `json:"credentialJwt,omitempty"`
+}
+
+// GetCredentialStatusList godoc
+// @Summary      Get Credential Status List
+// @Description  Get credential status list by id
+// @Tags         CredentialAPI
+// @Accept       json
+// @Produce      json
+// @Param        id   path      string  true  "ID"
+// @Success      200  {object}  GetCredentialStatusListResponse
+// @Failure      400  {string}  string  "Bad request"
+// @Router       /v1/credentials/status/{id} [get]
+func (cr CredentialRouter) GetCredentialStatusList(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
+	id := framework.GetParam(ctx, IDParam)
+	if id == nil {
+		errMsg := "cannot get credential without ID parameter"
+		logrus.Error(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	gotCredential, err := cr.service.GetCredentialStatusList(credential.GetCredentialStatusListRequest{ID: *id})
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get credential status list with id: %s", *id)
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	resp := GetCredentialStatusListResponse{
+		ID:            gotCredential.ID,
+		Credential:    gotCredential.Credential,
+		CredentialJWT: gotCredential.CredentialJWT,
+	}
+
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
+type UpdateCredentialStatusRequest struct {
+	Revoked bool `json:"revoked" validate:"required"`
+}
+
+func (c UpdateCredentialStatusRequest) ToServiceRequest(id string) credential.UpdateCredentialStatusRequest {
+	return credential.UpdateCredentialStatusRequest{
+		ID:      id,
+		Revoked: c.Revoked,
+	}
+}
+
+type UpdateCredentialStatusResponse struct {
+	Revoked bool `json:"revoked"`
+}
+
+// UpdateCredentialStatus godoc
+// @Summary      Update Credential Status
+// @Description  Update a credential's status
+// @Tags         CredentialAPI
+// @Accept       json
+// @Produce      json
+// @Param        request  body      UpdateCredentialStatusRequest  true  "request body"
+// @Success      201      {object}  UpdateCredentialStatusResponse
+// @Failure      400      {string}  string  "Bad request"
+// @Failure      500      {string}  string  "Internal server error"
+// @Router       /v1/credentials/{id}/status [put]
+func (cr CredentialRouter) UpdateCredentialStatus(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	id := framework.GetParam(ctx, IDParam)
+	if id == nil {
+		errMsg := "cannot get credential without ID parameter"
+		logrus.Error(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	var request UpdateCredentialStatusRequest
+	invalidCreateCredentialRequest := "invalid update credential request"
+	if err := framework.Decode(r, &request); err != nil {
+		errMsg := invalidCreateCredentialRequest
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	if err := framework.ValidateRequest(request); err != nil {
+		errMsg := invalidCreateCredentialRequest
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	req := request.ToServiceRequest(*id)
+	gotCredential, err := cr.service.UpdateCredentialStatus(req)
+
+	if err != nil {
+		errMsg := fmt.Sprintf("could not update credential with id: %s", req.ID)
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	resp := UpdateCredentialStatusResponse{
+		Revoked: gotCredential.Revoked,
+	}
+
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
 type VerifyCredentialRequest struct {
 	DataIntegrityCredential *credsdk.VerifiableCredential `json:"credential,omitempty"`
 	CredentialJWT           *keyaccess.JWT                `json:"credentialJwt,omitempty"`
diff --git a/pkg/server/server.go b/pkg/server/server.go
index a53a1aa..86046cd 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -27,6 +27,7 @@ const (
 	DIDsPrefix          = "/dids"
 	SchemasPrefix       = "/schemas"
 	CredentialsPrefix   = "/credentials"
+	StatusPrefix        = "/status"
 	PresentationsPrefix = "/presentations"
 	DefinitionsPrefix   = "/definitions"
 	SubmissionsPrefix   = "/submissions"
@@ -149,13 +150,20 @@ func (s *SSIServer) CredentialAPI(service svcframework.Service) (err error) {
 		return util.LoggingErrorMsg(err, "could not create credential router")
 	}
 
-	handlerPath := V1Prefix + CredentialsPrefix
+	credentialHandlerPath := V1Prefix + CredentialsPrefix
+	statusHandlerPath := V1Prefix + CredentialsPrefix + StatusPrefix
 
-	s.Handle(http.MethodPut, handlerPath, credRouter.CreateCredential)
-	s.Handle(http.MethodGet, handlerPath, credRouter.GetCredentials)
-	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), credRouter.GetCredential)
-	s.Handle(http.MethodPut, path.Join(handlerPath, VerificationPath), credRouter.VerifyCredential)
-	s.Handle(http.MethodDelete, path.Join(handlerPath, "/:id"), credRouter.DeleteCredential)
+	// Credentials
+	s.Handle(http.MethodPut, credentialHandlerPath, credRouter.CreateCredential)
+	s.Handle(http.MethodGet, credentialHandlerPath, credRouter.GetCredentials)
+	s.Handle(http.MethodGet, path.Join(credentialHandlerPath, "/:id"), credRouter.GetCredential)
+	s.Handle(http.MethodPut, path.Join(credentialHandlerPath, VerificationPath), credRouter.VerifyCredential)
+	s.Handle(http.MethodDelete, path.Join(credentialHandlerPath, "/:id"), credRouter.DeleteCredential)
+
+	// Credential Status
+	s.Handle(http.MethodGet, path.Join(credentialHandlerPath, "/:id", StatusPrefix), credRouter.GetCredentialStatus)
+	s.Handle(http.MethodPut, path.Join(credentialHandlerPath, "/:id", StatusPrefix), credRouter.UpdateCredentialStatus)
+	s.Handle(http.MethodGet, path.Join(statusHandlerPath, "/:id"), credRouter.GetCredentialStatusList)
 	return
 }
 
diff --git a/pkg/service/credential/model.go b/pkg/service/credential/model.go
index 68f9c10..b3a0208 100644
--- a/pkg/service/credential/model.go
+++ b/pkg/service/credential/model.go
@@ -17,6 +17,7 @@ type CreateCredentialRequest struct {
 	JSONSchema string                 `json:"jsonSchema,omitempty"`
 	Data       map[string]interface{} `json:"data,omitempty"`
 	Expiry     string                 `json:"expiry,omitempty"`
+	Revocable  bool                   `json:"revocable,omitempty"`
 	// TODO(gabe) support more capabilities like signature type, format, status, and more.
 }
 
@@ -53,3 +54,28 @@ type GetCredentialsResponse struct {
 type DeleteCredentialRequest struct {
 	ID string `json:"id" validate:"required"`
 }
+
+type GetCredentialStatusRequest struct {
+	ID string `json:"id" validate:"required"`
+}
+
+type GetCredentialStatusResponse struct {
+	Revoked bool `json:"revoked" validate:"required"`
+}
+
+type UpdateCredentialStatusRequest struct {
+	ID      string `json:"id" validate:"required"`
+	Revoked bool   `json:"revoked" validate:"required"`
+}
+
+type UpdateCredentialStatusResponse struct {
+	Revoked bool `json:"revoked" validate:"required"`
+}
+
+type GetCredentialStatusListRequest struct {
+	ID string `json:"id" validate:"required"`
+}
+
+type GetCredentialStatusListResponse struct {
+	credential.Container `json:"credential,omitempty"`
+}
diff --git a/pkg/service/credential/service.go b/pkg/service/credential/service.go
index 88f7d08..3bd2bf6 100644
--- a/pkg/service/credential/service.go
+++ b/pkg/service/credential/service.go
@@ -2,6 +2,9 @@ package credential
 
 import (
 	"fmt"
+	statussdk "github.com/TBD54566975/ssi-sdk/credential/status"
+	"github.com/google/uuid"
+	"strconv"
 	"time"
 
 	"github.com/TBD54566975/ssi-sdk/credential"
@@ -146,6 +149,34 @@ func (s Service) CreateCredential(request CreateCredentialRequest) (*CreateCrede
 		return nil, util.LoggingErrorMsg(err, errMsg)
 	}
 
+	if request.Revocable == true {
+		credID := builder.ID
+		issuerID := request.Issuer
+		schemaID := request.JSONSchema
+
+		statusListCredential, err := getStatusListCredential(s, issuerID, schemaID)
+		if err != nil {
+			return nil, util.LoggingErrorMsgf(err, "problem with getting status list credential")
+		}
+
+		statusListIndex, err := s.storage.GetNextStatusListRandomIndex()
+		if err != nil {
+			return nil, util.LoggingErrorMsg(err, "problem with getting status list index")
+		}
+
+		status := statussdk.StatusList2021Entry{
+			ID:                   fmt.Sprintf(`%s/v1/credentials/%s/status`, s.config.ServiceEndpoint, credID),
+			Type:                 statussdk.StatusList2021EntryType,
+			StatusPurpose:        statussdk.StatusRevocation,
+			StatusListIndex:      strconv.Itoa(statusListIndex),
+			StatusListCredential: statusListCredential.ID,
+		}
+
+		if err := builder.SetCredentialStatus(status); err != nil {
+			return nil, util.LoggingErrorMsg(err, "could not set credential status")
+		}
+	}
+
 	cred, err := builder.Build()
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not build credential")
@@ -170,10 +201,13 @@ func (s Service) CreateCredential(request CreateCredentialRequest) (*CreateCrede
 		ID:            cred.ID,
 		Credential:    cred,
 		CredentialJWT: credJWT,
+		Revoked:       false,
 	}
+
 	storageRequest := credstorage.StoreCredentialRequest{
 		Container: container,
 	}
+
 	if err = s.storage.StoreCredential(storageRequest); err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not store credential")
 	}
@@ -183,6 +217,56 @@ func (s Service) CreateCredential(request CreateCredentialRequest) (*CreateCrede
 	return &response, nil
 }
 
+func getStatusListCredential(s Service, issuerID string, schemaID string) (*credential.VerifiableCredential, error) {
+	storedStatusListCreds, err := s.storage.GetStatusListCredentialsByIssuerAndSchema(issuerID, schemaID)
+	if err != nil {
+		return nil, util.LoggingNewErrorf("problem with getting status list credential for issuer: %s schema: %s", issuerID, schemaID)
+	}
+
+	// This should never happen, there should always be only 1 status list credential per <issuer,schema> pair
+	if len(storedStatusListCreds) > 1 {
+		return nil, util.LoggingNewErrorf("only one status list credential per <issuer,schema> pair allowed. issuer: %s schema: %s", issuerID, schemaID)
+	}
+
+	var statusListCredential *credential.VerifiableCredential
+
+	// First time that this <issuer,schema> pair has a revocation or suspension credential issued
+	if len(storedStatusListCreds) == 0 {
+		statusListID := fmt.Sprintf("%s/v1/credentials/status/%s", s.config.ServiceEndpoint, uuid.New().String())
+		generatedStatusListCredential, err := statussdk.GenerateStatusList2021Credential(statusListID, issuerID, statussdk.StatusRevocation, []credential.VerifiableCredential{})
+		if err != nil {
+			return nil, util.LoggingErrorMsg(err, "could not generate status list")
+		}
+
+		statusListCredJWT, err := s.signCredentialJWT(issuerID, *generatedStatusListCredential)
+		if err != nil {
+			return nil, util.LoggingErrorMsg(err, "could not sign status list credential")
+		}
+
+		// store the credential
+		statusListContainer := credint.Container{
+			ID:            generatedStatusListCredential.ID,
+			Credential:    generatedStatusListCredential,
+			CredentialJWT: statusListCredJWT,
+		}
+
+		storageRequest := credstorage.StoreCredentialRequest{
+			Container: statusListContainer,
+		}
+
+		if err = s.storage.StoreStatusListCredential(storageRequest); err != nil {
+			return nil, util.LoggingErrorMsg(err, "could not store credential")
+		}
+
+		statusListCredential = generatedStatusListCredential
+
+	} else {
+		statusListCredential = storedStatusListCreds[0].Credential
+	}
+
+	return statusListCredential, nil
+}
+
 // signCredentialJWT signs a credential and returns it as a vc-jwt
 func (s Service) signCredentialJWT(issuer string, cred credential.VerifiableCredential) (*keyaccess.JWT, error) {
 	gotKey, err := s.keyStore.GetKey(keystore.GetKeyRequest{ID: issuer})
@@ -338,6 +422,146 @@ func (s Service) GetCredentialsBySchema(request GetCredentialBySchemaRequest) (*
 	return &response, nil
 }
 
+func (s Service) GetCredentialStatus(request GetCredentialStatusRequest) (*GetCredentialStatusResponse, error) {
+	logrus.Debugf("getting credential status: %s", request.ID)
+
+	gotCred, err := s.storage.GetCredential(request.ID)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not get credential: %s", request.ID)
+	}
+	if !gotCred.IsValid() {
+		return nil, util.LoggingNewErrorf("credential returned is not valid: %s", request.ID)
+	}
+	response := GetCredentialStatusResponse{
+		Revoked: gotCred.Revoked,
+	}
+	return &response, nil
+}
+
+func (s Service) GetCredentialStatusList(request GetCredentialStatusListRequest) (*GetCredentialStatusListResponse, error) {
+	logrus.Debugf("getting credential status list: %s", request.ID)
+
+	gotCred, err := s.storage.GetStatusListCredential(request.ID)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not get credential: %s", request.ID)
+	}
+	if !gotCred.IsValid() {
+		return nil, util.LoggingNewErrorf("credential returned is not valid: %s", request.ID)
+	}
+	response := GetCredentialStatusListResponse{
+		credint.Container{
+			ID:            gotCred.CredentialID,
+			Credential:    gotCred.Credential,
+			CredentialJWT: gotCred.CredentialJWT,
+		},
+	}
+	return &response, nil
+}
+
+func (s Service) UpdateCredentialStatus(request UpdateCredentialStatusRequest) (*UpdateCredentialStatusResponse, error) {
+	logrus.Debugf("updating credential status: %s to Revoked: %v", request.ID, request.Revoked)
+
+	gotCred, err := s.storage.GetCredential(request.ID)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not get credential: %s", request.ID)
+	}
+	if !gotCred.IsValid() {
+		return nil, util.LoggingNewErrorf("credential returned is not valid: %s", request.ID)
+	}
+
+	// if the request is the same as what the current credential is there is no action
+	if gotCred.Revoked == request.Revoked {
+		response := UpdateCredentialStatusResponse{Revoked: gotCred.Revoked}
+		return &response, nil
+	}
+
+	container, err := updateCredentialStatus(s, gotCred, request)
+	if err != nil {
+		return nil, util.LoggingNewError("problem updating credential")
+	}
+
+	response := UpdateCredentialStatusResponse{Revoked: container.Revoked}
+	return &response, nil
+}
+
+func updateCredentialStatus(s Service, gotCred *credstorage.StoredCredential, request UpdateCredentialStatusRequest) (*credint.Container, error) {
+	// store the credential with updated status
+	container := credint.Container{
+		ID:            gotCred.ID,
+		Credential:    gotCred.Credential,
+		CredentialJWT: gotCred.CredentialJWT,
+		Revoked:       request.Revoked,
+	}
+
+	storageRequest := credstorage.StoreCredentialRequest{
+		Container: container,
+	}
+
+	if err := s.storage.StoreCredential(storageRequest); err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not store credential")
+	}
+
+	storedStatusListCreds, err := s.storage.GetStatusListCredentialsByIssuerAndSchema(gotCred.Issuer, gotCred.Schema)
+	if err != nil {
+		return nil, util.LoggingNewErrorf("problem with getting status list credential for issuer: %s schema: %s", gotCred.Issuer, gotCred.Schema)
+	}
+
+	creds, err := s.storage.GetCredentialsByIssuerAndSchema(gotCred.Issuer, gotCred.Schema)
+	if err != nil {
+		return nil, util.LoggingNewErrorf("problem with getting status list credential for issuer: %s schema: %s", gotCred.Issuer, gotCred.Schema)
+	}
+
+	var revokedStatusCreds []credential.VerifiableCredential
+	for _, cred := range creds {
+		if cred.Credential.CredentialStatus != nil && cred.Revoked {
+			revokedStatusCreds = append(revokedStatusCreds, *cred.Credential)
+		}
+	}
+
+	generatedStatusListCredential, err := statussdk.GenerateStatusList2021Credential(storedStatusListCreds[0].ID, gotCred.Issuer, statussdk.StatusRevocation, revokedStatusCreds)
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not generate status list")
+	}
+
+	statusListCredJWT, err := s.signCredentialJWT(gotCred.Issuer, *generatedStatusListCredential)
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not sign status list credential")
+	}
+
+	// store the status list credential
+	statusListContainer := credint.Container{
+		ID:            generatedStatusListCredential.ID,
+		Credential:    generatedStatusListCredential,
+		CredentialJWT: statusListCredJWT,
+	}
+
+	storageRequest = credstorage.StoreCredentialRequest{
+		Container: statusListContainer,
+	}
+
+	if err = s.storage.StoreStatusListCredential(storageRequest); err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not store credential status list")
+	}
+
+	return &container, nil
+}
+
+func (s Service) GetCredentialsByIssuerAndSchemaWithStatus(issuer string, schema string) ([]credential.VerifiableCredential, error) {
+	gotCreds, err := s.storage.GetCredentialsByIssuerAndSchema(issuer, schema)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not get credential(s) for issuer: %s", issuer)
+	}
+
+	var creds []credential.VerifiableCredential
+	for _, cred := range gotCreds {
+		if cred.Credential.CredentialStatus != nil {
+			creds = append(creds, *cred.Credential)
+		}
+	}
+
+	return creds, nil
+}
+
 func (s Service) DeleteCredential(request DeleteCredentialRequest) error {
 
 	logrus.Debugf("deleting credential: %s", request.ID)
diff --git a/pkg/service/credential/storage/bolt.go b/pkg/service/credential/storage/bolt.go
index 8eddc8d..6b0ef9f 100644
--- a/pkg/service/credential/storage/bolt.go
+++ b/pkg/service/credential/storage/bolt.go
@@ -2,19 +2,29 @@ package storage
 
 import (
 	"fmt"
-	"strings"
-
 	"github.com/TBD54566975/ssi-sdk/credential/signing"
 	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	"math/rand"
+	"strings"
 
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
 
 const (
-	namespace                = "credential"
+	credentialNamespace           = "credential"
+	statusListCredentialNamespace = "status-list-credential"
+	statusListIndexNamespace      = "status-list-index"
+
+	fakeKey              = "fake-key"
+	statusListIndexesKey = "status-list-indexes"
+	currentListIndexKey  = "current-list-index"
+
+	// A a minimum revocation bitString length of 131,072, or 16KB uncompressed
+	bitStringLength = 8 * 1024 * 16
+
 	credentialNotFoundErrMsg = "credential not found"
 )
 
@@ -22,14 +32,103 @@ type BoltCredentialStorage struct {
 	db *storage.BoltDB
 }
 
+type StatusListIndex struct {
+	Index int `json:"index"`
+}
+
 func NewBoltCredentialStorage(db *storage.BoltDB) (*BoltCredentialStorage, error) {
 	if db == nil {
 		return nil, errors.New("bolt db reference is nil")
 	}
+
+	// TODO: (Neal) there is a current bug with our Bolt implementation where if we do a GET without anything in the db it will throw an error
+	// Doing initial writes and then deleting will "warm up" our database and when we do a GET after that it will not crash and return empty list
+	// https://github.com/TBD54566975/ssi-service/issues/176
+	if err := db.Write(credentialNamespace, fakeKey, nil); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem writing status initial write to db")
+
+	}
+	if err := db.Delete(credentialNamespace, fakeKey); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem with initial delete to db")
+	}
+
+	if err := db.Write(statusListCredentialNamespace, fakeKey, nil); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem writing status initial write to db")
+	}
+
+	if err := db.Delete(statusListCredentialNamespace, fakeKey); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem with initial delete to db")
+	}
+
+	randUniqueList := randomUniqueNum(bitStringLength)
+	uniqueNumBytes, err := json.Marshal(randUniqueList)
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not marshal random unique numbers")
+	}
+
+	if err := db.Write(statusListIndexNamespace, statusListIndexesKey, uniqueNumBytes); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem writing status list indexes to db")
+	}
+
+	statusListIndexBytes, err := json.Marshal(StatusListIndex{Index: 0})
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not marshal status list index bytes")
+	}
+
+	if err := db.Write(statusListIndexNamespace, currentListIndexKey, statusListIndexBytes); err != nil {
+		return nil, util.LoggingErrorMsg(err, "problem writing current list index to db")
+	}
+
 	return &BoltCredentialStorage{db: db}, nil
 }
 
+func (b BoltCredentialStorage) GetNextStatusListRandomIndex() (int, error) {
+
+	gotUniqueNumBytes, err := b.db.Read(statusListIndexNamespace, statusListIndexesKey)
+	if err != nil {
+		return -1, util.LoggingErrorMsgf(err, "reading status list")
+	}
+
+	if len(gotUniqueNumBytes) == 0 {
+		return -1, util.LoggingNewErrorf("could not get unique numbers from db")
+	}
+
+	var uniqueNums []int
+	if err = json.Unmarshal(gotUniqueNumBytes, &uniqueNums); err != nil {
+		return -1, util.LoggingErrorMsgf(err, "could not unmarshal unique numbers")
+	}
+
+	gotCurrentListIndexBytes, err := b.db.Read(statusListIndexNamespace, currentListIndexKey)
+	if err != nil {
+		return -1, util.LoggingErrorMsgf(err, "could not get list index")
+	}
+
+	var statusListIndex StatusListIndex
+	if err = json.Unmarshal(gotCurrentListIndexBytes, &statusListIndex); err != nil {
+		return -1, util.LoggingErrorMsgf(err, "could not unmarshal unique numbers")
+	}
+
+	statusListIndexBytes, err := json.Marshal(StatusListIndex{Index: statusListIndex.Index + 1})
+	if err != nil {
+		return -1, util.LoggingErrorMsg(err, "could not marshal status list index bytes")
+	}
+
+	if err := b.db.Write(statusListIndexNamespace, currentListIndexKey, statusListIndexBytes); err != nil {
+		return -1, util.LoggingErrorMsg(err, "problem writing current list index to db")
+	}
+
+	return uniqueNums[statusListIndex.Index], nil
+}
+
 func (b BoltCredentialStorage) StoreCredential(request StoreCredentialRequest) error {
+	return b.storeCredential(request, credentialNamespace)
+}
+
+func (b BoltCredentialStorage) StoreStatusListCredential(request StoreCredentialRequest) error {
+	return b.storeCredential(request, statusListCredentialNamespace)
+}
+
+func (b BoltCredentialStorage) storeCredential(request StoreCredentialRequest, namespace string) error {
 	if !request.IsValid() {
 		return util.LoggingNewError("store request request is not valid")
 	}
@@ -81,10 +180,19 @@ func buildStoredCredential(request StoreCredentialRequest) (*StoredCredential, e
 		Subject:       subject,
 		Schema:        schema,
 		IssuanceDate:  cred.IssuanceDate,
+		Revoked:       request.Revoked,
 	}, nil
 }
 
 func (b BoltCredentialStorage) GetCredential(id string) (*StoredCredential, error) {
+	return b.getCredential(id, credentialNamespace)
+}
+
+func (b BoltCredentialStorage) GetStatusListCredential(id string) (*StoredCredential, error) {
+	return b.getCredential(id, statusListCredentialNamespace)
+}
+
+func (b BoltCredentialStorage) getCredential(id string, namespace string) (*StoredCredential, error) {
 	prefixValues, err := b.db.ReadPrefix(namespace, id)
 	if err != nil {
 		return nil, util.LoggingErrorMsgf(err, "could not get credential from storage: %s", id)
@@ -115,10 +223,10 @@ func (b BoltCredentialStorage) GetCredential(id string) (*StoredCredential, erro
 // so this is not much of a concern.
 
 // GetCredentialsByIssuer gets all credentials stored with a prefix key containing the issuer value
-// The method is greedy, meaning if multiple values are found...and some fail during processing, we will
+// The method is greedy, meaning if multiple values are found and some fail during processing, we will
 // return only the successful values and log an error for the failures.
 func (b BoltCredentialStorage) GetCredentialsByIssuer(issuer string) ([]StoredCredential, error) {
-	keys, err := b.db.ReadAllKeys(namespace)
+	keys, err := b.db.ReadAllKeys(credentialNamespace)
 	if err != nil {
 		return nil, util.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for issuer: %s", issuer)
 	}
@@ -137,7 +245,7 @@ func (b BoltCredentialStorage) GetCredentialsByIssuer(issuer string) ([]StoredCr
 	// now get each credential by key
 	var storedCreds []StoredCredential
 	for _, key := range issuerKeys {
-		credBytes, err := b.db.Read(namespace, key)
+		credBytes, err := b.db.Read(credentialNamespace, key)
 		if err != nil {
 			logrus.WithError(err).Errorf("could not read credential with key: %s", key)
 		} else {
@@ -160,7 +268,7 @@ func (b BoltCredentialStorage) GetCredentialsByIssuer(issuer string) ([]StoredCr
 // The method is greedy, meaning if multiple values are found...and some fail during processing, we will
 // return only the successful values and log an error for the failures.
 func (b BoltCredentialStorage) GetCredentialsBySubject(subject string) ([]StoredCredential, error) {
-	keys, err := b.db.ReadAllKeys(namespace)
+	keys, err := b.db.ReadAllKeys(credentialNamespace)
 	if err != nil {
 		return nil, util.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for subject: %s", subject)
 	}
@@ -180,7 +288,7 @@ func (b BoltCredentialStorage) GetCredentialsBySubject(subject string) ([]Stored
 	// now get each credential by key
 	var storedCreds []StoredCredential
 	for _, key := range subjectKeys {
-		credBytes, err := b.db.Read(namespace, key)
+		credBytes, err := b.db.Read(credentialNamespace, key)
 		if err != nil {
 			logrus.WithError(err).Errorf("could not read credential with key: %s", key)
 		} else {
@@ -203,7 +311,7 @@ func (b BoltCredentialStorage) GetCredentialsBySubject(subject string) ([]Stored
 // The method is greedy, meaning if multiple values are found...and some fail during processing, we will
 // return only the successful values and log an error for the failures.
 func (b BoltCredentialStorage) GetCredentialsBySchema(schema string) ([]StoredCredential, error) {
-	keys, err := b.db.ReadAllKeys(namespace)
+	keys, err := b.db.ReadAllKeys(credentialNamespace)
 	if err != nil {
 		return nil, util.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for schema: %s", schema)
 	}
@@ -224,7 +332,7 @@ func (b BoltCredentialStorage) GetCredentialsBySchema(schema string) ([]StoredCr
 	// now get each credential by key
 	var storedCreds []StoredCredential
 	for _, key := range schemaKeys {
-		credBytes, err := b.db.Read(namespace, key)
+		credBytes, err := b.db.Read(credentialNamespace, key)
 		if err != nil {
 			logrus.WithError(err).Errorf("could not read credential with key: %s", key)
 		} else {
@@ -243,7 +351,67 @@ func (b BoltCredentialStorage) GetCredentialsBySchema(schema string) ([]StoredCr
 	return storedCreds, nil
 }
 
+// GetCredentialsByIssuerAndSchema gets all credentials stored with a prefix key containing the issuer value
+// The method is greedy, meaning if multiple values are found...and some fail during processing, we will
+// return only the successful values and log an error for the failures.
+func (b BoltCredentialStorage) GetCredentialsByIssuerAndSchema(issuer string, schema string) ([]StoredCredential, error) {
+	return b.getCredentialsByIssuerAndSchema(issuer, schema, credentialNamespace)
+}
+
+func (b BoltCredentialStorage) GetStatusListCredentialsByIssuerAndSchema(issuer string, schema string) ([]StoredCredential, error) {
+	return b.getCredentialsByIssuerAndSchema(issuer, schema, statusListCredentialNamespace)
+}
+
+func (b BoltCredentialStorage) getCredentialsByIssuerAndSchema(issuer string, schema string, namespace string) ([]StoredCredential, error) {
+	keys, err := b.db.ReadAllKeys(namespace)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not read credential storage while searching for creds for issuer: %s", issuer)
+	}
+
+	query := "sc:" + schema
+	var issuerSchemaKeys []string
+	for _, k := range keys {
+		if strings.Contains(k, issuer) && strings.HasSuffix(k, query) {
+			issuerSchemaKeys = append(issuerSchemaKeys, k)
+		}
+	}
+
+	if len(issuerSchemaKeys) == 0 {
+		logrus.Warnf("no credentials found for issuer: %s and schema %s", util.SanitizeLog(issuer), util.SanitizeLog(schema))
+		return nil, nil
+	}
+
+	// now get each credential by key
+	var storedCreds []StoredCredential
+	for _, key := range issuerSchemaKeys {
+		credBytes, err := b.db.Read(namespace, key)
+		if err != nil {
+			logrus.WithError(err).Errorf("could not read credential with key: %s", key)
+		} else {
+			var cred StoredCredential
+			if err = json.Unmarshal(credBytes, &cred); err != nil {
+				logrus.WithError(err).Errorf("could not unmarshal credential with key: %s", key)
+			}
+			storedCreds = append(storedCreds, cred)
+		}
+	}
+
+	if len(storedCreds) == 0 {
+		logrus.Warnf("no credentials able to be retrieved for issuer: %s", issuerSchemaKeys)
+	}
+
+	return storedCreds, nil
+}
+
 func (b BoltCredentialStorage) DeleteCredential(id string) error {
+	return b.deleteCredential(id, credentialNamespace)
+}
+
+func (b BoltCredentialStorage) DeleteStatusListCredential(id string) error {
+	return b.deleteCredential(id, statusListCredentialNamespace)
+}
+
+func (b BoltCredentialStorage) deleteCredential(id string, namespace string) error {
 	credDoesNotExistMsg := fmt.Sprintf("credential does not exist, cannot delete: %s", id)
 
 	// first get the credential to regenerate the prefix key
@@ -276,3 +444,17 @@ func (b BoltCredentialStorage) DeleteCredential(id string) error {
 func createPrefixKey(id, issuer, subject, schema string) string {
 	return strings.Join([]string{id, "is:" + issuer, "su:" + subject, "sc:" + schema}, "-")
 }
+
+func randomUniqueNum(count int) []int {
+	randomNumbers := make([]int, 0, count)
+
+	for i := 1; i <= count; i++ {
+		randomNumbers = append(randomNumbers, i)
+	}
+
+	rand.Shuffle(len(randomNumbers), func(i, j int) {
+		randomNumbers[i], randomNumbers[j] = randomNumbers[j], randomNumbers[i]
+	})
+
+	return randomNumbers
+}
diff --git a/pkg/service/credential/storage/storage.go b/pkg/service/credential/storage/storage.go
index cbeb36a..4b4b30b 100644
--- a/pkg/service/credential/storage/storage.go
+++ b/pkg/service/credential/storage/storage.go
@@ -27,6 +27,7 @@ type StoredCredential struct {
 	Subject      string `json:"subject"`
 	Schema       string `json:"schema"`
 	IssuanceDate string `json:"issuanceDate"`
+	Revoked      bool   `json:"revoked"`
 }
 
 func (sc StoredCredential) IsValid() bool {
@@ -47,7 +48,15 @@ type Storage interface {
 	GetCredentialsByIssuer(issuer string) ([]StoredCredential, error)
 	GetCredentialsBySubject(subject string) ([]StoredCredential, error)
 	GetCredentialsBySchema(schema string) ([]StoredCredential, error)
+	GetCredentialsByIssuerAndSchema(issuer, schema string) ([]StoredCredential, error)
 	DeleteCredential(id string) error
+
+	StoreStatusListCredential(request StoreCredentialRequest) error
+	GetStatusListCredential(id string) (*StoredCredential, error)
+	GetStatusListCredentialsByIssuerAndSchema(issuer, schema string) ([]StoredCredential, error)
+	DeleteStatusListCredential(id string) error
+
+	GetNextStatusListRandomIndex() (int, error)
 }
 
 func NewCredentialStorage(s storage.ServiceStorage) (Storage, error) {


Test patch:
diff --git a/pkg/server/router/credential_test.go b/pkg/server/router/credential_test.go
index 7bc573a..f7b1474 100644
--- a/pkg/server/router/credential_test.go
+++ b/pkg/server/router/credential_test.go
@@ -40,10 +40,17 @@ func TestCredentialRouter(t *testing.T) {
 	})
 
 	t.Run("Credential Service Test", func(tt *testing.T) {
+
 		bolt, err := storage.NewBoltDB()
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, bolt)
 
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
 		serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}}
 		keyStoreService := testKeyStoreService(tt, bolt)
 		didService := testDIDService(tt, bolt, keyStoreService)
@@ -194,4 +201,97 @@ func TestCredentialRouter(t *testing.T) {
 		assert.Error(tt, err)
 		assert.Contains(tt, err.Error(), fmt.Sprintf("credential not found with id: %s", cred.ID))
 	})
+
+	t.Run("Credential Status List Test", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, bolt)
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}}
+		keyStoreService := testKeyStoreService(tt, bolt)
+		didService := testDIDService(tt, bolt, keyStoreService)
+		schemaService := testSchemaService(tt, bolt, keyStoreService, didService)
+		credService, err := credential.NewCredentialService(serviceConfig, bolt, keyStoreService, didService.GetResolver(), schemaService)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, credService)
+
+		// check type and status
+		assert.Equal(tt, framework.Credential, credService.Type())
+		assert.Equal(tt, framework.StatusReady, credService.Status().Status)
+
+		// create a did
+		issuerDID, err := didService.CreateDIDByMethod(did.CreateDIDRequest{Method: didsdk.KeyMethod, KeyType: crypto.Ed25519})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, issuerDID)
+
+		// create a schema
+		emailSchema := map[string]interface{}{
+			"type": "object",
+			"properties": map[string]interface{}{
+				"email": map[string]interface{}{
+					"type": "string",
+				},
+			},
+			"required":             []interface{}{"email"},
+			"additionalProperties": false,
+		}
+
+		createdSchema, err := schemaService.CreateSchema(schema.CreateSchemaRequest{Author: "me", Name: "simple schema", Schema: emailSchema})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdSchema)
+
+		issuer := issuerDID.DID.ID
+		subject := "did:test:345"
+
+		createdCred, err := credService.CreateCredential(credential.CreateCredentialRequest{
+			Issuer:     issuer,
+			Subject:    subject,
+			JSONSchema: createdSchema.ID,
+			Data: map[string]interface{}{
+				"email": "Satoshi@Nakamoto.btc",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		})
+
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdCred)
+		assert.NotEmpty(tt, createdCred.CredentialJWT)
+
+		credStatusMap, ok := createdCred.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.Contains(tt, credStatusMap["id"], fmt.Sprintf("v1/credentials/%s/status", createdCred.ID))
+		assert.Contains(tt, credStatusMap["statusListCredential"], "v1/credentials/status")
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		createdCredTwo, err := credService.CreateCredential(credential.CreateCredentialRequest{
+			Issuer:     issuer,
+			Subject:    subject,
+			JSONSchema: createdSchema.ID,
+			Data: map[string]interface{}{
+				"email": "Satoshi2@Nakamoto2.btc",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		})
+
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdCredTwo)
+		assert.NotEmpty(tt, createdCredTwo.CredentialJWT)
+
+		credStatusMapTwo, ok := createdCredTwo.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.Contains(tt, credStatusMapTwo["id"], fmt.Sprintf("v1/credentials/%s/status", createdCredTwo.ID))
+		assert.Contains(tt, credStatusMapTwo["statusListCredential"], "v1/credentials/status")
+		assert.NotEmpty(tt, credStatusMapTwo["statusListIndex"])
+
+	})
 }
diff --git a/pkg/server/server_credential_test.go b/pkg/server/server_credential_test.go
index 49ce37d..1224ef6 100644
--- a/pkg/server/server_credential_test.go
+++ b/pkg/server/server_credential_test.go
@@ -621,4 +621,330 @@ func TestCredentialAPI(t *testing.T) {
 		assert.False(tt, verifyResp.Verified)
 		assert.Contains(tt, verifyResp.Reason, "could not parse credential from JWT")
 	})
+
+	t.Run("Test Create Revocable Credential", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+		require.NoError(tt, err)
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		keyStoreService := testKeyStoreService(tt, bolt)
+		didService := testDIDService(tt, bolt, keyStoreService)
+		schemaService := testSchemaService(tt, bolt, keyStoreService, didService)
+		credRouter := testCredentialRouter(tt, bolt, keyStoreService, didService, schemaService)
+
+		issuerDID, err := didService.CreateDIDByMethod(did.CreateDIDRequest{
+			Method:  didsdk.KeyMethod,
+			KeyType: crypto.Ed25519,
+		})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, issuerDID)
+
+		issuerDIDTwo, err := didService.CreateDIDByMethod(did.CreateDIDRequest{
+			Method:  didsdk.KeyMethod,
+			KeyType: crypto.Ed25519,
+		})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, issuerDIDTwo)
+
+		w := httptest.NewRecorder()
+
+		// good request One
+		createCredRequest := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry: time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		}
+		requestValue := newRequestValue(tt, createCredRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, resp.CredentialJWT)
+		assert.NoError(tt, err)
+		assert.Empty(tt, resp.Credential.CredentialStatus)
+		assert.Equal(tt, resp.Credential.Issuer, issuerDID.DID.ID)
+
+		// good revocable request One
+		createRevocableCredRequestOne := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue = newRequestValue(tt, createRevocableCredRequestOne)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var revocableRespOne router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&revocableRespOne)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, revocableRespOne.CredentialJWT)
+		assert.NotEmpty(tt, revocableRespOne.Credential.CredentialStatus)
+		assert.Equal(tt, revocableRespOne.Credential.Issuer, issuerDID.DID.ID)
+
+		credStatusMap, ok := revocableRespOne.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		// good revocable request Two
+		createRevocableCredRequestTwo := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue = newRequestValue(tt, createRevocableCredRequestTwo)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var revocableRespTwo router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&revocableRespTwo)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, revocableRespTwo.CredentialJWT)
+		assert.NotEmpty(tt, revocableRespTwo.Credential.CredentialStatus)
+		assert.Equal(tt, revocableRespTwo.Credential.Issuer, issuerDID.DID.ID)
+
+		credStatusMap, ok = revocableRespTwo.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		// good revocable request Three
+		createRevocableCredRequestThree := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue = newRequestValue(tt, createRevocableCredRequestThree)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var revocableRespThree router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&revocableRespThree)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, revocableRespThree.CredentialJWT)
+		assert.NotEmpty(tt, revocableRespThree.Credential.CredentialStatus)
+		assert.Equal(tt, revocableRespThree.Credential.Issuer, issuerDID.DID.ID)
+
+		credStatusMap, ok = revocableRespThree.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		// good revocable request Four (different issuer / schema)
+		createRevocableCredRequestFour := router.CreateCredentialRequest{
+			Issuer:  issuerDIDTwo.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue = newRequestValue(tt, createRevocableCredRequestFour)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var revocableRespFour router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&revocableRespFour)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, revocableRespFour.CredentialJWT)
+		assert.NotEmpty(tt, revocableRespFour.Credential.CredentialStatus)
+		assert.Equal(tt, revocableRespFour.Credential.Issuer, issuerDIDTwo.DID.ID)
+
+		credStatusMap, ok = revocableRespFour.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+	})
+
+	t.Run("Test Get Revoked Status Of Credential", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+		require.NoError(tt, err)
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		keyStoreService := testKeyStoreService(tt, bolt)
+		didService := testDIDService(tt, bolt, keyStoreService)
+		schemaService := testSchemaService(tt, bolt, keyStoreService, didService)
+		credRouter := testCredentialRouter(tt, bolt, keyStoreService, didService, schemaService)
+
+		issuerDID, err := didService.CreateDIDByMethod(did.CreateDIDRequest{
+			Method:  didsdk.KeyMethod,
+			KeyType: crypto.Ed25519,
+		})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, issuerDID)
+
+		w := httptest.NewRecorder()
+
+		// good request number one
+		createCredRequest := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue := newRequestValue(tt, createCredRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, resp.CredentialJWT)
+		assert.NotEmpty(tt, resp.Credential.CredentialStatus)
+		assert.Equal(tt, resp.Credential.Issuer, issuerDID.DID.ID)
+
+		credStatusMap, ok := resp.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/credentials/%s/status", resp.Credential.ID), nil)
+		err = credRouter.GetCredentialStatus(newRequestContextWithParams(map[string]string{"id": resp.Credential.ID}), w, req)
+		assert.NoError(tt, err)
+
+		var credStatusResponse = router.GetCredentialStatusResponse{}
+		err = json.NewDecoder(w.Body).Decode(&credStatusResponse)
+		assert.NoError(tt, err)
+		assert.Equal(tt, false, credStatusResponse.Revoked)
+
+		// good request number one
+		updateCredStatusRequest := router.UpdateCredentialStatusRequest{Revoked: true}
+
+		requestValue = newRequestValue(tt, updateCredStatusRequest)
+		req = httptest.NewRequest(http.MethodPut, fmt.Sprintf("https://ssi-service.com/v1/credentials/%s/status", resp.Credential.ID), requestValue)
+		err = credRouter.UpdateCredentialStatus(newRequestContextWithParams(map[string]string{"id": resp.Credential.ID}), w, req)
+		assert.NoError(tt, err)
+
+		var credStatusUpdateResponse = router.UpdateCredentialStatusResponse{}
+		err = json.NewDecoder(w.Body).Decode(&credStatusUpdateResponse)
+		assert.NoError(tt, err)
+		assert.Equal(tt, true, credStatusUpdateResponse.Revoked)
+
+	})
+
+	t.Run("Test Get Status List Credential", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+		require.NoError(tt, err)
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		keyStoreService := testKeyStoreService(tt, bolt)
+		didService := testDIDService(tt, bolt, keyStoreService)
+		schemaService := testSchemaService(tt, bolt, keyStoreService, didService)
+		credRouter := testCredentialRouter(tt, bolt, keyStoreService, didService, schemaService)
+
+		issuerDID, err := didService.CreateDIDByMethod(did.CreateDIDRequest{
+			Method:  didsdk.KeyMethod,
+			KeyType: crypto.Ed25519,
+		})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, issuerDID)
+
+		w := httptest.NewRecorder()
+
+		// good request number one
+		createCredRequest := router.CreateCredentialRequest{
+			Issuer:  issuerDID.DID.ID,
+			Subject: "did:abc:456",
+			Data: map[string]interface{}{
+				"firstName": "Jack",
+				"lastName":  "Dorsey",
+			},
+			Expiry:    time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+			Revocable: true,
+		}
+
+		requestValue := newRequestValue(tt, createCredRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/credentials", requestValue)
+		err = credRouter.CreateCredential(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, resp.CredentialJWT)
+		assert.NotEmpty(tt, resp.Credential.CredentialStatus)
+		assert.Equal(tt, resp.Credential.Issuer, issuerDID.DID.ID)
+
+		credStatusMap, ok := resp.Credential.CredentialStatus.(map[string]interface{})
+		assert.True(tt, ok)
+
+		assert.NotEmpty(tt, credStatusMap["statusListIndex"])
+
+		credStatusListID := (credStatusMap["statusListCredential"]).(string)
+
+		assert.NotEmpty(tt, credStatusListID)
+		fmt.Println(credStatusListID)
+
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://localhost:8080/v1/credentials/status/%s", credStatusListID), nil)
+		err = credRouter.GetCredentialStatusList(newRequestContextWithParams(map[string]string{"id": credStatusListID}), w, req)
+		assert.NoError(tt, err)
+
+		var credListResp router.GetCredentialStatusListResponse
+		err = json.NewDecoder(w.Body).Decode(&credListResp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, credListResp.CredentialJWT)
+		assert.Empty(tt, credListResp.Credential.CredentialStatus)
+		assert.Equal(tt, credListResp.Credential.ID, credStatusListID)
+	})
 }