diff --git a/cmd/polygonid/polygonid.go b/cmd/polygonid/polygonid.go index e5d98b8..c3dc4f2 100644 --- a/cmd/polygonid/polygonid.go +++ b/cmd/polygonid/polygonid.go @@ -737,6 +737,49 @@ func PLGNAtomicQueryMtpV2OnChainInputs(jsonResponse **C.char, in *C.char, jsonResponse, in, cfg, status) } +//export PLGNACredentialStatusCheck +func PLGNACredentialStatusCheck(jsonResponse **C.char, in *C.char, + cfg *C.char, status **C.PLGNStatus) bool { + + if jsonResponse == nil { + maybeCreateStatus(status, C.PLGNSTATUSCODE_NIL_POINTER, + "jsonResponse pointer is nil") + return false + } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + inData := C.GoBytes(unsafe.Pointer(in), C.int(C.strlen(in))) + + envCfg, err := createEnvConfig(cfg) + if err != nil { + maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) + return false + } + + isValid, err := c_polygonid.CredentialStatusCheck(ctx, envCfg, inData) + if err != nil { + maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, err.Error()) + return false + } + + var resp struct { + Valid bool `json:"valid"` + } + resp.Valid = isValid + + respBytes, err := json.Marshal(resp) + if err != nil { + maybeCreateStatus(status, C.PLGNSTATUSCODE_ERROR, + "error marshalling credential status check response: %v", err) + return false + } + + *jsonResponse = C.CString(string(respBytes)) + return true +} + //export PLGNFreeStatus func PLGNFreeStatus(status *C.PLGNStatus) { if status == nil { diff --git a/credential_status.go b/credential_status.go new file mode 100644 index 0000000..9465b09 --- /dev/null +++ b/credential_status.go @@ -0,0 +1,38 @@ +package c_polygonid + +import ( + "context" + "encoding/json" + "errors" + + core "github.com/iden3/go-iden3-core/v2" + "github.com/iden3/go-iden3-core/v2/w3c" +) + +func CredentialStatusCheck(ctx context.Context, cfg EnvConfig, + jsonReq []byte) (bool, error) { + + var req struct { + IssuerDID w3c.DID `json:"issuer"` + CredentialStatus jsonObj `json:"credentialStatus"` + } + + err := json.Unmarshal(jsonReq, &req) + if err != nil { + return false, err + } + + issuerID, err := core.IDFromDID(req.IssuerDID) + if err != nil { + return false, err + } + + _, err = buildAndValidateCredentialStatus(ctx, cfg, req.CredentialStatus, + &issuerID, false) + if errors.Is(err, errCredentialsRevoked) { + return false, nil + } else if err != nil { + return false, err + } + return true, nil +} diff --git a/credential_status_test.go b/credential_status_test.go new file mode 100644 index 0000000..ab29fa8 --- /dev/null +++ b/credential_status_test.go @@ -0,0 +1,29 @@ +package c_polygonid + +import ( + "context" + "os" + "testing" + + httpmock "github.com/0xPolygonID/c-polygonid/testing" + "github.com/stretchr/testify/require" +) + +func readJsonFile(t testing.TB, filename string) []byte { + jsonIn, err := os.ReadFile("testdata/" + filename) + require.NoError(t, err) + return jsonIn +} + +func TestCredentialStatusCheck(t *testing.T) { + defer httpmock.MockHTTPClient(t, map[string]string{ + "http://localhost:8001/api/v1/identities/did%3Aiden3%3Apolygon%3Amumbai%3AwuQT8NtFq736wsJahUuZpbA8otTzjKGyKj4i4yWtU/claims/revocation/status/0": "testdata/httpresp_rev_status_wuQT8NtFq736wsJahUuZpbA8otTzjKGyKj4i4yWtU_0.json", + }, httpmock.IgnoreUntouchedURLs())() + + jsonIn := readJsonFile(t, "credential_status_request.json") + + ctx := context.Background() + isValid, err := CredentialStatusCheck(ctx, EnvConfig{}, jsonIn) + require.NoError(t, err) + require.True(t, isValid) +} diff --git a/inputs_sig.go b/inputs_sig.go index 34d55a2..20fbd49 100644 --- a/inputs_sig.go +++ b/inputs_sig.go @@ -47,6 +47,8 @@ var httpClient = &http.Client{} //go:embed schemas/credentials-v1.json-ld var credentialsV1JsonLDBytes []byte +var errCredentialsRevoked = errors.New("credential is revoked") + func stringByPath(obj jsonObj, path string) (string, error) { v, err := getByPath(obj, path) if err != nil { @@ -276,7 +278,7 @@ func buildAndValidateCredentialStatus(ctx context.Context, cfg EnvConfig, } if proof.Proof.Existence { - return proof, errors.New("credential is revoked") + return proof, errCredentialsRevoked } return proof, nil diff --git a/testdata/credential_status_request.json b/testdata/credential_status_request.json new file mode 100644 index 0000000..4607c72 --- /dev/null +++ b/testdata/credential_status_request.json @@ -0,0 +1,8 @@ +{ + "issuer": "did:iden3:polygon:mumbai:wuQT8NtFq736wsJahUuZpbA8otTzjKGyKj4i4yWtU", + "credentialStatus": { + "id": "http://localhost:8001/api/v1/identities/did%3Aiden3%3Apolygon%3Amumbai%3AwuQT8NtFq736wsJahUuZpbA8otTzjKGyKj4i4yWtU/claims/revocation/status/0", + "revocationNonce": 0, + "type": "SparseMerkleTreeProof" + } +}