diff --git a/cmd/policy/main.go b/cmd/policy/main.go
index 982e3625abf22dc5ffb06c6c0f05bd6c1a69193b..3fd6a720ff8ea2691b8d01cc3aaa04e7bb18e77e 100644
--- a/cmd/policy/main.go
+++ b/cmd/policy/main.go
@@ -84,16 +84,17 @@ func main() {
 		cacheFuncs := regofunc.NewCacheFuncs(cfg.Cache.Addr, httpClient)
 		didResolverFuncs := regofunc.NewDIDResolverFuncs(cfg.DIDResolver.Addr, httpClient)
 		taskFuncs := regofunc.NewTaskFuncs(cfg.Task.Addr, httpClient)
-		keysFuncs := regofunc.NewPubkeyFuncs(cfg.Signer.Addr, httpClient)
+		signerFuncs := regofunc.NewSignerFuncs(cfg.Signer.Addr, httpClient)
 		regofunc.Register("cacheGet", rego.Function3(cacheFuncs.CacheGetFunc()))
 		regofunc.Register("cacheSet", rego.Function4(cacheFuncs.CacheSetFunc()))
 		regofunc.Register("didResolve", rego.Function1(didResolverFuncs.ResolveFunc()))
 		regofunc.Register("taskCreate", rego.Function2(taskFuncs.CreateTaskFunc()))
 		regofunc.Register("taskListCreate", rego.Function2(taskFuncs.CreateTaskListFunc()))
-		regofunc.Register("getKey", rego.Function1(keysFuncs.GetKeyFunc()))
-		regofunc.Register("getAllKeys", rego.FunctionDyn(keysFuncs.GetAllKeysFunc()))
-		regofunc.Register("getAllKeys", rego.FunctionDyn(keysFuncs.GetAllKeysFunc()))
-		regofunc.Register("issuer", rego.FunctionDyn(keysFuncs.IssuerDID()))
+		regofunc.Register("getKey", rego.Function1(signerFuncs.GetKeyFunc()))
+		regofunc.Register("getAllKeys", rego.FunctionDyn(signerFuncs.GetAllKeysFunc()))
+		regofunc.Register("issuer", rego.FunctionDyn(signerFuncs.IssuerDID()))
+		regofunc.Register("createProof", rego.Function1(signerFuncs.CreateProof()))
+		regofunc.Register("verifyProof", rego.Function1(signerFuncs.VerifyProof()))
 	}
 
 	// subscribe the cache for policy data changes
diff --git a/internal/regofunc/pubkeys.go b/internal/regofunc/pubkeys.go
deleted file mode 100644
index c9873c6b9c246df6205b2d0d781a60a8de5e1617..0000000000000000000000000000000000000000
--- a/internal/regofunc/pubkeys.go
+++ /dev/null
@@ -1,141 +0,0 @@
-package regofunc
-
-import (
-	"fmt"
-	"net/http"
-	"net/url"
-	"strings"
-
-	"github.com/open-policy-agent/opa/ast"
-	"github.com/open-policy-agent/opa/rego"
-	"github.com/open-policy-agent/opa/types"
-)
-
-type PubkeyFuncs struct {
-	signerAddr string
-	httpClient *http.Client
-}
-
-func NewPubkeyFuncs(signerAddr string, httpClient *http.Client) *PubkeyFuncs {
-	return &PubkeyFuncs{
-		signerAddr: signerAddr,
-		httpClient: httpClient,
-	}
-}
-
-func (pf *PubkeyFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
-	return &rego.Function{
-			Name:    "keys.get",
-			Decl:    types.NewFunction(types.Args(types.S), types.A),
-			Memoize: true,
-		},
-		func(bctx rego.BuiltinContext, keyname *ast.Term) (*ast.Term, error) {
-			var key string
-			if err := ast.As(keyname.Value, &key); err != nil {
-				return nil, fmt.Errorf("invalid keyname: %s", err)
-			}
-
-			if strings.TrimSpace(key) == "" {
-				return nil, fmt.Errorf("empty keyname")
-			}
-
-			uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys/" + key)
-			if err != nil {
-				return nil, err
-			}
-
-			req, err := http.NewRequest("GET", uri.String(), nil)
-			if err != nil {
-				return nil, err
-			}
-
-			resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
-			if err != nil {
-				return nil, err
-			}
-			defer resp.Body.Close() // nolint:errcheck
-
-			if resp.StatusCode != http.StatusOK {
-				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
-			}
-
-			v, err := ast.ValueFromReader(resp.Body)
-			if err != nil {
-				return nil, err
-			}
-
-			return ast.NewTerm(v), nil
-		}
-}
-
-func (pf *PubkeyFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
-	return &rego.Function{
-			Name:    "keys.getAll",
-			Decl:    types.NewFunction(nil, types.A),
-			Memoize: true,
-		},
-		func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
-			uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/keys")
-			if err != nil {
-				return nil, err
-			}
-
-			req, err := http.NewRequest("GET", uri.String(), nil)
-			if err != nil {
-				return nil, err
-			}
-
-			resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
-			if err != nil {
-				return nil, err
-			}
-			defer resp.Body.Close() // nolint:errcheck
-
-			if resp.StatusCode != http.StatusOK {
-				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
-			}
-
-			v, err := ast.ValueFromReader(resp.Body)
-			if err != nil {
-				return nil, err
-			}
-
-			return ast.NewTerm(v), nil
-		}
-}
-
-func (pf *PubkeyFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
-	return &rego.Function{
-			Name:    "issuer",
-			Decl:    types.NewFunction(nil, types.A),
-			Memoize: true,
-		},
-		func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
-			uri, err := url.ParseRequestURI(pf.signerAddr + "/v1/issuerDID")
-			if err != nil {
-				return nil, err
-			}
-
-			req, err := http.NewRequest("GET", uri.String(), nil)
-			if err != nil {
-				return nil, err
-			}
-
-			resp, err := pf.httpClient.Do(req.WithContext(bctx.Context))
-			if err != nil {
-				return nil, err
-			}
-			defer resp.Body.Close() // nolint:errcheck
-
-			if resp.StatusCode != http.StatusOK {
-				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
-			}
-
-			v, err := ast.ValueFromReader(resp.Body)
-			if err != nil {
-				return nil, err
-			}
-
-			return ast.NewTerm(v), nil
-		}
-}
diff --git a/internal/regofunc/pubkeys_test.go b/internal/regofunc/pubkeys_test.go
deleted file mode 100644
index 11a7a7c5c084c99522ad4bb4febd865f980e0c49..0000000000000000000000000000000000000000
--- a/internal/regofunc/pubkeys_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package regofunc_test
-
-import (
-	"context"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/open-policy-agent/opa/rego"
-	"github.com/stretchr/testify/assert"
-
-	"code.vereign.com/gaiax/tsa/policy/internal/regofunc"
-)
-
-func TestGetKeyFunc(t *testing.T) {
-	expected := `{"key1":"key1 data"}`
-	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		_, _ = fmt.Fprint(w, expected)
-	}))
-	defer signerSrv.Close()
-
-	keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
-	r := rego.New(
-		rego.Query(`keys.get("key1")`),
-		rego.Function1(keysFuncs.GetKeyFunc()),
-		rego.StrictBuiltinErrors(true),
-	)
-	resultSet, err := r.Eval(context.Background())
-	assert.NoError(t, err)
-
-	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
-	assert.NoError(t, err)
-	assert.Equal(t, expected, string(resultBytes))
-}
-
-func TestGetKeyFuncError(t *testing.T) {
-	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		w.WriteHeader(http.StatusNotFound)
-	}))
-	defer signerSrv.Close()
-
-	keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
-	r := rego.New(
-		rego.Query(`keys.get("key1")`),
-		rego.Function1(keysFuncs.GetKeyFunc()),
-		rego.StrictBuiltinErrors(true),
-	)
-	resultSet, err := r.Eval(context.Background())
-	assert.Nil(t, resultSet)
-	assert.Error(t, err)
-
-	expectedError := `keys.get("key1"): eval_builtin_error: keys.get: unexpected response from signer: 404 Not Found`
-	assert.Equal(t, expectedError, err.Error())
-}
-
-func TestGetAllKeysFunc(t *testing.T) {
-	expected := `[{"key1":"key1 data"},{"key2":"key2 data"}]`
-	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		_, _ = fmt.Fprint(w, expected)
-	}))
-	defer signerSrv.Close()
-
-	keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
-	r := rego.New(
-		rego.Query(`keys.getAll()`),
-		rego.FunctionDyn(keysFuncs.GetAllKeysFunc()),
-		rego.StrictBuiltinErrors(true),
-	)
-	resultSet, err := r.Eval(context.Background())
-	assert.NoError(t, err)
-
-	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
-	assert.NoError(t, err)
-	assert.Equal(t, expected, string(resultBytes))
-}
-
-func TestIssuerDID(t *testing.T) {
-	expected := `{"did":"did:web:123"}`
-	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		_, _ = fmt.Fprint(w, expected)
-	}))
-	defer signerSrv.Close()
-
-	keysFuncs := regofunc.NewPubkeyFuncs(signerSrv.URL, http.DefaultClient)
-	r := rego.New(
-		rego.Query(`issuer()`),
-		rego.FunctionDyn(keysFuncs.IssuerDID()),
-		rego.StrictBuiltinErrors(true),
-	)
-	resultSet, err := r.Eval(context.Background())
-	assert.NoError(t, err)
-
-	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
-	assert.NoError(t, err)
-	assert.Equal(t, expected, string(resultBytes))
-}
diff --git a/internal/regofunc/signer.go b/internal/regofunc/signer.go
new file mode 100644
index 0000000000000000000000000000000000000000..734585723aeb17ffaf8015ccef2a2b342fa87ad0
--- /dev/null
+++ b/internal/regofunc/signer.go
@@ -0,0 +1,275 @@
+package regofunc
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/open-policy-agent/opa/ast"
+	"github.com/open-policy-agent/opa/rego"
+	"github.com/open-policy-agent/opa/types"
+)
+
+type SignerFuncs struct {
+	signerAddr string
+	httpClient *http.Client
+}
+
+func NewSignerFuncs(signerAddr string, httpClient *http.Client) *SignerFuncs {
+	return &SignerFuncs{
+		signerAddr: signerAddr,
+		httpClient: httpClient,
+	}
+}
+
+func (sf *SignerFuncs) GetKeyFunc() (*rego.Function, rego.Builtin1) {
+	return &rego.Function{
+			Name:    "keys.get",
+			Decl:    types.NewFunction(types.Args(types.S), types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, keyname *ast.Term) (*ast.Term, error) {
+			var key string
+			if err := ast.As(keyname.Value, &key); err != nil {
+				return nil, fmt.Errorf("invalid keyname: %s", err)
+			}
+
+			if strings.TrimSpace(key) == "" {
+				return nil, fmt.Errorf("empty keyname")
+			}
+
+			uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys/" + key)
+			if err != nil {
+				return nil, err
+			}
+
+			req, err := http.NewRequest("GET", uri.String(), nil)
+			if err != nil {
+				return nil, err
+			}
+
+			resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
+			if err != nil {
+				return nil, err
+			}
+			defer resp.Body.Close() // nolint:errcheck
+
+			if resp.StatusCode != http.StatusOK {
+				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+			}
+
+			v, err := ast.ValueFromReader(resp.Body)
+			if err != nil {
+				return nil, err
+			}
+
+			return ast.NewTerm(v), nil
+		}
+}
+
+func (sf *SignerFuncs) GetAllKeysFunc() (*rego.Function, rego.BuiltinDyn) {
+	return &rego.Function{
+			Name:    "keys.getAll",
+			Decl:    types.NewFunction(nil, types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
+			uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/keys")
+			if err != nil {
+				return nil, err
+			}
+
+			req, err := http.NewRequest("GET", uri.String(), nil)
+			if err != nil {
+				return nil, err
+			}
+
+			resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
+			if err != nil {
+				return nil, err
+			}
+			defer resp.Body.Close() // nolint:errcheck
+
+			if resp.StatusCode != http.StatusOK {
+				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+			}
+
+			v, err := ast.ValueFromReader(resp.Body)
+			if err != nil {
+				return nil, err
+			}
+
+			return ast.NewTerm(v), nil
+		}
+}
+
+func (sf *SignerFuncs) IssuerDID() (*rego.Function, rego.BuiltinDyn) {
+	return &rego.Function{
+			Name:    "issuer",
+			Decl:    types.NewFunction(nil, types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, terms []*ast.Term) (*ast.Term, error) {
+			uri, err := url.ParseRequestURI(sf.signerAddr + "/v1/issuerDID")
+			if err != nil {
+				return nil, err
+			}
+
+			req, err := http.NewRequest("GET", uri.String(), nil)
+			if err != nil {
+				return nil, err
+			}
+
+			resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
+			if err != nil {
+				return nil, err
+			}
+			defer resp.Body.Close() // nolint:errcheck
+
+			if resp.StatusCode != http.StatusOK {
+				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+			}
+
+			v, err := ast.ValueFromReader(resp.Body)
+			if err != nil {
+				return nil, err
+			}
+
+			return ast.NewTerm(v), nil
+		}
+}
+
+func (sf *SignerFuncs) CreateProof() (*rego.Function, rego.Builtin1) {
+	return &rego.Function{
+			Name:    "proof.create",
+			Decl:    types.NewFunction(types.Args(types.S), types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) {
+			// cred represents verifiable credential or presentation
+			var cred map[string]interface{}
+			if err := ast.As(credential.Value, &cred); err != nil {
+				return nil, fmt.Errorf("invalid credential: %s", err)
+			}
+
+			if cred["type"] == nil {
+				return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential")
+			}
+
+			credType, ok := cred["type"].(string)
+			if !ok {
+				return nil, fmt.Errorf("invalid credential type, string is expected")
+			}
+
+			var createProofPath string
+			switch credType {
+			case "VerifiableCredential":
+				createProofPath = "/v1/credential/proof"
+			case "VerifiablePresentation":
+				createProofPath = "/v1/presentation/proof"
+			default:
+				return nil, fmt.Errorf("unknown credential type: %q", credType)
+			}
+
+			jsonCred, err := json.Marshal(cred)
+			if err != nil {
+				return nil, err
+			}
+
+			req, err := http.NewRequest("POST", sf.signerAddr+createProofPath, bytes.NewReader(jsonCred))
+			if err != nil {
+				return nil, err
+			}
+
+			resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
+			if err != nil {
+				return nil, err
+			}
+			defer resp.Body.Close() // nolint:errcheck
+
+			if resp.StatusCode != http.StatusOK {
+				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+			}
+
+			v, err := ast.ValueFromReader(resp.Body)
+			if err != nil {
+				return nil, err
+			}
+
+			return ast.NewTerm(v), nil
+		}
+}
+
+func (sf *SignerFuncs) VerifyProof() (*rego.Function, rego.Builtin1) {
+	return &rego.Function{
+			Name:    "proof.verify",
+			Decl:    types.NewFunction(types.Args(types.S), types.A),
+			Memoize: true,
+		},
+		func(bctx rego.BuiltinContext, credential *ast.Term) (*ast.Term, error) {
+			// cred represents verifiable credential or presentation
+			var cred map[string]interface{}
+			if err := ast.As(credential.Value, &cred); err != nil {
+				return nil, fmt.Errorf("invalid credential: %s", err)
+			}
+
+			if cred["type"] == nil {
+				return nil, fmt.Errorf("credential data does not specify type: must be VerifiablePresentation or VerifiableCredential")
+			}
+
+			credType, ok := cred["type"].(string)
+			if !ok {
+				return nil, fmt.Errorf("invalid credential type, string is expected")
+			}
+
+			if cred["proof"] == nil {
+				return nil, fmt.Errorf("credential data does contain proof section")
+			}
+
+			var verifyProofPath string
+			switch credType {
+			case "VerifiableCredential":
+				verifyProofPath = "/v1/credential/verify"
+			case "VerifiablePresentation":
+				verifyProofPath = "/v1/presentation/verify"
+			default:
+				return nil, fmt.Errorf("unknown credential type: %q", credType)
+			}
+
+			jsonCred, err := json.Marshal(cred)
+			if err != nil {
+				return nil, err
+			}
+
+			req, err := http.NewRequest("POST", sf.signerAddr+verifyProofPath, bytes.NewReader(jsonCred))
+			if err != nil {
+				return nil, err
+			}
+
+			resp, err := sf.httpClient.Do(req.WithContext(bctx.Context))
+			if err != nil {
+				return nil, err
+			}
+			defer resp.Body.Close() // nolint:errcheck
+
+			if resp.StatusCode != http.StatusOK {
+				return nil, fmt.Errorf("unexpected response from signer: %s", resp.Status)
+			}
+
+			var result struct {
+				Valid bool `json:"valid"`
+			}
+			if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+				return nil, fmt.Errorf("failed to decode response from signer: %v", err)
+			}
+
+			if !result.Valid {
+				return nil, fmt.Errorf("proof is invalid")
+			}
+
+			return ast.NewTerm(ast.Boolean(true)), nil
+		}
+}
diff --git a/internal/regofunc/signer_test.go b/internal/regofunc/signer_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1adb9fb66abfc1152fe89faa3031a412fb0dbb9e
--- /dev/null
+++ b/internal/regofunc/signer_test.go
@@ -0,0 +1,249 @@
+package regofunc_test
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/open-policy-agent/opa/rego"
+	"github.com/stretchr/testify/assert"
+
+	"code.vereign.com/gaiax/tsa/policy/internal/regofunc"
+)
+
+func TestGetKeyFunc(t *testing.T) {
+	expected := `{"key1":"key1 data"}`
+	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		_, _ = fmt.Fprint(w, expected)
+	}))
+	defer signerSrv.Close()
+
+	keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+	r := rego.New(
+		rego.Query(`keys.get("key1")`),
+		rego.Function1(keysFuncs.GetKeyFunc()),
+		rego.StrictBuiltinErrors(true),
+	)
+	resultSet, err := r.Eval(context.Background())
+	assert.NoError(t, err)
+
+	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, string(resultBytes))
+}
+
+func TestGetKeyFuncError(t *testing.T) {
+	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusNotFound)
+	}))
+	defer signerSrv.Close()
+
+	keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+	r := rego.New(
+		rego.Query(`keys.get("key1")`),
+		rego.Function1(keysFuncs.GetKeyFunc()),
+		rego.StrictBuiltinErrors(true),
+	)
+	resultSet, err := r.Eval(context.Background())
+	assert.Nil(t, resultSet)
+	assert.Error(t, err)
+
+	expectedError := `keys.get("key1"): eval_builtin_error: keys.get: unexpected response from signer: 404 Not Found`
+	assert.Equal(t, expectedError, err.Error())
+}
+
+func TestGetAllKeysFunc(t *testing.T) {
+	expected := `[{"key1":"key1 data"},{"key2":"key2 data"}]`
+	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		_, _ = fmt.Fprint(w, expected)
+	}))
+	defer signerSrv.Close()
+
+	keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+	r := rego.New(
+		rego.Query(`keys.getAll()`),
+		rego.FunctionDyn(keysFuncs.GetAllKeysFunc()),
+		rego.StrictBuiltinErrors(true),
+	)
+	resultSet, err := r.Eval(context.Background())
+	assert.NoError(t, err)
+
+	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, string(resultBytes))
+}
+
+func TestIssuerDID(t *testing.T) {
+	expected := `{"did":"did:web:123"}`
+	signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		_, _ = fmt.Fprint(w, expected)
+	}))
+	defer signerSrv.Close()
+
+	keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+	r := rego.New(
+		rego.Query(`issuer()`),
+		rego.FunctionDyn(keysFuncs.IssuerDID()),
+		rego.StrictBuiltinErrors(true),
+	)
+	resultSet, err := r.Eval(context.Background())
+	assert.NoError(t, err)
+
+	resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+	assert.NoError(t, err)
+	assert.Equal(t, expected, string(resultBytes))
+}
+
+func TestCreateProof(t *testing.T) {
+	tests := []struct {
+		name               string
+		input              map[string]interface{}
+		signerResponseCode int
+		errtext            string
+	}{
+		{
+			name:    "missing credential type",
+			input:   map[string]interface{}{"vc": "data"},
+			errtext: "credential data does not specify type",
+		},
+		{
+			name:    "unknown credential type",
+			input:   map[string]interface{}{"type": "non-existing-type"},
+			errtext: "unknown credential type",
+		},
+		{
+			name:               "signer returns error for VC",
+			input:              map[string]interface{}{"type": "VerifiableCredential"},
+			signerResponseCode: http.StatusBadRequest,
+			errtext:            "400 Bad Request",
+		},
+		{
+			name:               "signer returns error for VP",
+			input:              map[string]interface{}{"type": "VerifiablePresentation"},
+			signerResponseCode: http.StatusBadRequest,
+			errtext:            "400 Bad Request",
+		},
+		{
+			name:               "signer returns successfully",
+			input:              map[string]interface{}{"type": "VerifiableCredential"},
+			signerResponseCode: http.StatusOK,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			expected := `{"vc":"data"}`
+			signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(test.signerResponseCode)
+				_, _ = fmt.Fprint(w, expected)
+			}))
+			defer signerSrv.Close()
+
+			keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+			query, err := rego.New(
+				rego.Query(`proof.create(input)`),
+				rego.Function1(keysFuncs.CreateProof()),
+				rego.StrictBuiltinErrors(true),
+			).PrepareForEval(context.Background())
+			assert.NoError(t, err)
+
+			resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input))
+			if err != nil {
+				assert.Contains(t, err.Error(), test.errtext)
+			} else {
+				assert.NotEmpty(t, resultSet)
+				assert.NotEmpty(t, resultSet[0].Expressions)
+				resultBytes, err := json.Marshal(resultSet[0].Expressions[0].Value)
+				assert.NoError(t, err)
+				assert.Equal(t, expected, string(resultBytes))
+			}
+		})
+	}
+}
+
+func TestVerifyProof(t *testing.T) {
+	tests := []struct {
+		name               string
+		input              map[string]interface{}
+		signerResponseCode int
+		errtext            string
+	}{
+		{
+			name:    "invalid credential",
+			input:   nil,
+			errtext: "credential data does not specify type",
+		},
+		{
+			name:    "missing credential type",
+			input:   map[string]interface{}{"vc": "data"},
+			errtext: "credential data does not specify type",
+		},
+		{
+			name:               "credential type is not string",
+			input:              map[string]interface{}{"type": 123},
+			signerResponseCode: http.StatusBadRequest,
+			errtext:            "invalid credential type, string is expected",
+		},
+		{
+			name:    "missing proof section",
+			input:   map[string]interface{}{"type": "VerifiableCredential"},
+			errtext: "credential data does contain proof section",
+		},
+		{
+			name:    "unknown credential type",
+			input:   map[string]interface{}{"proof": "iamhere", "type": "non-existing-type"},
+			errtext: "unknown credential type",
+		},
+		{
+			name:               "signer returns error for VC",
+			input:              map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"},
+			signerResponseCode: http.StatusBadRequest,
+			errtext:            "400 Bad Request",
+		},
+		{
+			name:               "signer returns error for VP",
+			input:              map[string]interface{}{"proof": "iamhere", "type": "VerifiablePresentation"},
+			signerResponseCode: http.StatusBadRequest,
+			errtext:            "400 Bad Request",
+		},
+		{
+			name:               "signer returns successfully",
+			input:              map[string]interface{}{"proof": "iamhere", "type": "VerifiableCredential"},
+			signerResponseCode: http.StatusOK,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			expected := `{"valid":true}`
+			signerSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(test.signerResponseCode)
+				_, _ = fmt.Fprint(w, expected)
+			}))
+			defer signerSrv.Close()
+
+			keysFuncs := regofunc.NewSignerFuncs(signerSrv.URL, http.DefaultClient)
+			query, err := rego.New(
+				rego.Query(`proof.verify(input)`),
+				rego.Function1(keysFuncs.VerifyProof()),
+				rego.StrictBuiltinErrors(true),
+			).PrepareForEval(context.Background())
+			assert.NoError(t, err)
+
+			resultSet, err := query.Eval(context.Background(), rego.EvalInput(test.input))
+			if err != nil {
+				assert.NotEmpty(t, test.errtext, "test case must contain error, but doesn't")
+				assert.Contains(t, err.Error(), test.errtext)
+			} else {
+				assert.NotEmpty(t, resultSet)
+				assert.NotEmpty(t, resultSet[0].Expressions)
+				valid, ok := resultSet[0].Expressions[0].Value.(bool)
+				assert.True(t, ok)
+				assert.True(t, valid)
+			}
+		})
+	}
+}