Repository: TBD54566975/ssi-service
Pull request #183 (2022-11-28 17:45:59)

[OSE-170][OSE-169] Create Submission Implementation
* Refactored DID verification to a a new package called `jwt`. 
* Implemented the submission.
* Refactored test utils so creation of test services take the `ServiceStorage` interface.
* Added a test for the creation of a presentation submission. This also includes introducing a `ErrSubmissionNotFound` error type. 
* Implemented the bolt storage related to the `Operation` objects.


Patch:
diff --git a/doc/swagger.yaml b/doc/swagger.yaml
index 228f301..205f14c 100644
--- a/doc/swagger.yaml
+++ b/doc/swagger.yaml
@@ -8,6 +8,8 @@ definitions:
       id:
         description: Credential ID
         type: string
+      revoked:
+        type: boolean
     type: object
   credential.CredentialSchema:
     properties:
@@ -483,6 +485,8 @@ definitions:
         type: string
       issuer:
         type: string
+      revocable:
+        type: boolean
       schema:
         description: A schema is optional. If present, we'll attempt to look it up
           and validate the data against it.
@@ -627,6 +631,20 @@ definitions:
       id:
         type: string
     type: object
+  github.com_tbd54566975_ssi-service_pkg_server_router.GetCredentialStatusListResponse:
+    properties:
+      credential:
+        $ref: '#/definitions/credential.VerifiableCredential'
+      credentialJwt:
+        type: string
+      id:
+        type: string
+    type: object
+  github.com_tbd54566975_ssi-service_pkg_server_router.GetCredentialStatusResponse:
+    properties:
+      revoked:
+        type: boolean
+    type: object
   github.com_tbd54566975_ssi-service_pkg_server_router.GetCredentialsResponse:
     properties:
       credentials:
@@ -823,6 +841,18 @@ definitions:
         items: {}
         type: array
     type: object
+  github.com_tbd54566975_ssi-service_pkg_server_router.UpdateCredentialStatusRequest:
+    properties:
+      revoked:
+        type: boolean
+    required:
+    - revoked
+    type: object
+  github.com_tbd54566975_ssi-service_pkg_server_router.UpdateCredentialStatusResponse:
+    properties:
+      revoked:
+        type: boolean
+    type: object
   github.com_tbd54566975_ssi-service_pkg_server_router.VerifyCredentialRequest:
     properties:
       credential:
@@ -979,6 +1009,8 @@ definitions:
         type: string
       issuer:
         type: string
+      revocable:
+        type: boolean
       schema:
         description: A schema is optional. If present, we'll attempt to look it up
           and validate the data against it.
@@ -1123,6 +1155,20 @@ definitions:
       id:
         type: string
     type: object
+  pkg_server_router.GetCredentialStatusListResponse:
+    properties:
+      credential:
+        $ref: '#/definitions/credential.VerifiableCredential'
+      credentialJwt:
+        type: string
+      id:
+        type: string
+    type: object
+  pkg_server_router.GetCredentialStatusResponse:
+    properties:
+      revoked:
+        type: boolean
+    type: object
   pkg_server_router.GetCredentialsResponse:
     properties:
       credentials:
@@ -1319,6 +1365,18 @@ definitions:
         items: {}
         type: array
     type: object
+  pkg_server_router.UpdateCredentialStatusRequest:
+    properties:
+      revoked:
+        type: boolean
+    required:
+    - revoked
+    type: object
+  pkg_server_router.UpdateCredentialStatusResponse:
+    properties:
+      revoked:
+        type: boolean
+    type: object
   pkg_server_router.VerifyCredentialRequest:
     properties:
       credential:
@@ -1622,6 +1680,85 @@ paths:
       summary: Get Credential
       tags:
       - CredentialAPI
+  /v1/credentials/{id}/status:
+    get:
+      consumes:
+      - application/json
+      description: Get credential status by id
+      parameters:
+      - description: ID
+        in: path
+        name: id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetCredentialStatusResponse'
+        "400":
+          description: Bad request
+          schema:
+            type: string
+      summary: Get Credential Status
+      tags:
+      - CredentialAPI
+    put:
+      consumes:
+      - application/json
+      description: Update a credential's status
+      parameters:
+      - description: request body
+        in: body
+        name: request
+        required: true
+        schema:
+          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.UpdateCredentialStatusRequest'
+      produces:
+      - application/json
+      responses:
+        "201":
+          description: Created
+          schema:
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.UpdateCredentialStatusResponse'
+        "400":
+          description: Bad request
+          schema:
+            type: string
+        "500":
+          description: Internal server error
+          schema:
+            type: string
+      summary: Update Credential Status
+      tags:
+      - CredentialAPI
+  /v1/credentials/status/{id}:
+    get:
+      consumes:
+      - application/json
+      description: Get credential status list by id
+      parameters:
+      - description: ID
+        in: path
+        name: id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetCredentialStatusListResponse'
+        "400":
+          description: Bad request
+          schema:
+            type: string
+      summary: Get Credential Status List
+      tags:
+      - CredentialAPI
   /v1/credentials/verification:
     put:
       consumes:
@@ -1794,7 +1931,7 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.StoreKeyRequest'
+          $ref: '#/definitions/pkg_server_router.StoreKeyRequest'
       produces:
       - application/json
       responses:
@@ -1828,7 +1965,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetKeyDetailsResponse'
+            $ref: '#/definitions/pkg_server_router.GetKeyDetailsResponse'
         "400":
           description: Bad request
           schema:
@@ -2156,7 +2293,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.GetOperationsResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetOperationsResponse'
         "400":
           description: Bad request
           schema:
@@ -2185,7 +2322,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.Operation'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.Operation'
         "400":
           description: Bad request
           schema:
@@ -2208,14 +2345,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionRequest'
+          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionRequest'
       produces:
       - application/json
       responses:
         "201":
           description: Created
           schema:
-            $ref: '#/definitions/pkg_server_router.CreatePresentationDefinitionResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreatePresentationDefinitionResponse'
         "400":
           description: Bad request
           schema:
@@ -2272,7 +2409,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.GetPresentationDefinitionResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetPresentationDefinitionResponse'
         "400":
           description: Bad request
           schema:
@@ -2297,7 +2434,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.GetSubmissionResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetSubmissionResponse'
         "400":
           description: Bad request
           schema:
@@ -2318,14 +2455,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/pkg_server_router.ReviewSubmissionRequest'
+          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionRequest'
       produces:
       - application/json
       responses:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/pkg_server_router.ReviewSubmissionResponse'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.ReviewSubmissionResponse'
         "400":
           description: Bad request
           schema:
@@ -2347,14 +2484,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/pkg_server_router.CreateSubmissionRequest'
+          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateSubmissionRequest'
       produces:
       - application/json
       responses:
         "201":
           description: Created
           schema:
-            $ref: '#/definitions/pkg_server_router.Operation'
+            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.Operation'
         "400":
           description: Bad request
           schema:
@@ -2377,7 +2514,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetSchemasResponse'
+            $ref: '#/definitions/pkg_server_router.GetSchemasResponse'
         "500":
           description: Internal server error
           schema:
@@ -2395,14 +2532,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateSchemaRequest'
+          $ref: '#/definitions/pkg_server_router.CreateSchemaRequest'
       produces:
       - application/json
       responses:
         "201":
           description: Created
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.CreateSchemaResponse'
+            $ref: '#/definitions/pkg_server_router.CreateSchemaResponse'
         "400":
           description: Bad request
           schema:
@@ -2459,7 +2596,7 @@ paths:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.GetSchemaResponse'
+            $ref: '#/definitions/pkg_server_router.GetSchemaResponse'
         "400":
           description: Bad request
           schema:
@@ -2478,14 +2615,14 @@ paths:
         name: request
         required: true
         schema:
-          $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.VerifySchemaRequest'
+          $ref: '#/definitions/pkg_server_router.VerifySchemaRequest'
       produces:
       - application/json
       responses:
         "200":
           description: OK
           schema:
-            $ref: '#/definitions/github.com_tbd54566975_ssi-service_pkg_server_router.VerifySchemaResponse'
+            $ref: '#/definitions/pkg_server_router.VerifySchemaResponse'
         "400":
           description: Bad request
           schema:
diff --git a/go.mod b/go.mod
index 1867fb1..2df0190 100644
--- a/go.mod
+++ b/go.mod
@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/BurntSushi/toml v1.2.1
-	github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221110170444-a9e67907c8f9
+	github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221129172807-92be548d54be
 	github.com/ardanlabs/conf v1.5.0
 	github.com/dimfeld/httptreemux/v5 v5.5.0
 	github.com/go-playground/locales v0.14.0
@@ -31,7 +31,7 @@ require (
 )
 
 require (
-	github.com/bits-and-blooms/bitset v1.3.3 // indirect
+	github.com/bits-and-blooms/bitset v1.4.0 // 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
@@ -46,11 +46,11 @@ require (
 	github.com/multiformats/go-base32 v0.1.0 // indirect
 	github.com/multiformats/go-base36 v0.1.0 // indirect
 	github.com/multiformats/go-multicodec v0.7.0 // indirect
-	github.com/piprate/json-gold v0.4.2 // indirect
+	github.com/piprate/json-gold v0.5.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/cachecontrol v0.1.0 // indirect
 	github.com/rogpeppe/go-internal v1.8.1 // indirect
-	github.com/santhosh-tekuri/jsonschema/v5 v5.0.2 // indirect
+	github.com/santhosh-tekuri/jsonschema/v5 v5.1.0 // indirect
 	golang.org/x/sys v0.2.0 // indirect
 	golang.org/x/term v0.2.0 // indirect
 	golang.org/x/text v0.4.0 // indirect
diff --git a/go.sum b/go.sum
index b4a7c18..fc909af 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,11 @@
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221110170444-a9e67907c8f9 h1:46Xgt4DbZhaw4mOWYlABWuSMhZSGKsURlo2yzDjfVyA=
-github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221110170444-a9e67907c8f9/go.mod h1:c2wq4GipGxjAPXgnxNBvnuB7OqDNKjriKuD60H0Q6Ho=
+github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221129172807-92be548d54be h1:KK6Dbr1rFPQSAyeWQGRUm7ba1K3XoxAmlFicLeR/6Ek=
+github.com/TBD54566975/ssi-sdk v0.0.2-alpha.0.20221129172807-92be548d54be/go.mod h1:RFja7smnRZpi5OPDPpW35TDK3SbHuAGCHs8wbNTSBxY=
 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/bits-and-blooms/bitset v1.4.0 h1:+YZ8ePm+He2pU3dZlIZiOeAKfrBkXi1lSrXJ/Xzgbu8=
+github.com/bits-and-blooms/bitset v1.4.0/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=
@@ -78,29 +78,27 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n
 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
-github.com/piprate/json-gold v0.4.2 h1:Rq8V+637HOFcj20KdTqW/g/llCwX2qtau0g5d1pD79o=
-github.com/piprate/json-gold v0.4.2/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM=
+github.com/piprate/json-gold v0.5.0 h1:RmGh1PYboCFcchVFuh2pbSWAZy4XJaqTMU4KQYsApbM=
+github.com/piprate/json-gold v0.5.0/go.mod h1:WZ501QQMbZZ+3pXFPhQKzNwS1+jls0oqov3uQ2WasLs=
 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc=
 github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
 github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
-github.com/santhosh-tekuri/jsonschema/v5 v5.0.2 h1:zOYFITq/5SO7YOv39/Taw8s1skb0Py39K5V2XvCEP48=
-github.com/santhosh-tekuri/jsonschema/v5 v5.0.2/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
+github.com/santhosh-tekuri/jsonschema/v5 v5.1.0 h1:wSUNu/w/7OQ0Y3NVnfTU5uxzXY4uMpXW92VXEJKqBB0=
+github.com/santhosh-tekuri/jsonschema/v5 v5.1.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
diff --git a/internal/did/access.go b/internal/did/access.go
index 31e862a..2efbcd0 100644
--- a/internal/did/access.go
+++ b/internal/did/access.go
@@ -3,7 +3,6 @@ package did
 import (
 	"crypto"
 	"fmt"
-
 	"github.com/TBD54566975/ssi-sdk/cryptosuite"
 	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	"github.com/goccy/go-json"
@@ -66,11 +65,19 @@ func extractKeyFromVerificationMethod(method didsdk.VerificationMethod) (pubKey
 		return
 	case method.PublicKeyJWK != nil:
 		jwkBytes, jwkErr := json.Marshal(method.PublicKeyJWK)
-		if err != nil {
+		if jwkErr != nil {
 			err = jwkErr
 			return
 		}
-		pubKey, err = jwk.ParseKey(jwkBytes)
+		parsed, parseErr := jwk.ParseKey(jwkBytes)
+		if parseErr != nil {
+			err = parseErr
+			return
+		}
+		if err = parsed.Raw(&pubKey); err != nil {
+			return
+		}
+
 		return
 	}
 	err = errors.New("no public key found in verification method")
diff --git a/pkg/jwt/verification.go b/pkg/jwt/verification.go
new file mode 100644
index 0000000..4aa6299
--- /dev/null
+++ b/pkg/jwt/verification.go
@@ -0,0 +1,27 @@
+package jwt
+
+import (
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
+	"github.com/pkg/errors"
+	didint "github.com/tbd54566975/ssi-service/internal/did"
+	"github.com/tbd54566975/ssi-service/internal/keyaccess"
+	"github.com/tbd54566975/ssi-service/internal/util"
+)
+
+// VerifyTokenFromDID verifies that the information in the token was digitally signed by the public key associated with
+// the public key of the verification method of the did's document. The passed in resolver is used to map from the did
+// to the did document.
+func VerifyTokenFromDID(did string, token keyaccess.JWT, s *didsdk.Resolver) error {
+	kid, pubKey, err := didint.ResolveKeyForDID(s, did)
+	if err != nil {
+		return errors.Wrapf(err, "failed to resolve applicant's did: %s", did)
+	}
+	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
+	if err != nil {
+		return util.LoggingErrorMsg(err, "could not create application verifier")
+	}
+	if err := verifier.Verify(token); err != nil {
+		return util.LoggingErrorMsg(err, "could not verify the application's signature")
+	}
+	return nil
+}
diff --git a/pkg/server/router/presentation.go b/pkg/server/router/presentation.go
index 536309f..0111c5a 100644
--- a/pkg/server/router/presentation.go
+++ b/pkg/server/router/presentation.go
@@ -4,9 +4,13 @@ import (
 	"context"
 	"fmt"
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	"github.com/TBD54566975/ssi-sdk/credential/signing"
+	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
+	credint "github.com/tbd54566975/ssi-service/internal/credential"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
+	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/server/framework"
 	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/presentation"
@@ -179,6 +183,40 @@ type CreateSubmissionRequest struct {
 	SubmissionJWT keyaccess.JWT `json:"submissionJwt" validate:"required"`
 }
 
+func (r CreateSubmissionRequest) toServiceRequest() (*presentation.CreateSubmissionRequest, error) {
+	sdkVP, err := signing.ParseVerifiablePresentationFromJWT(r.SubmissionJWT.String())
+	if err != nil {
+		return nil, errors.Wrap(err, "parsing presentation from jwt")
+	}
+	if err := sdkVP.IsValid(); err != nil {
+		return nil, errors.Wrap(err, "verifying vp validity")
+	}
+
+	submissionData, err := json.Marshal(sdkVP.PresentationSubmission)
+	if err != nil {
+		return nil, errors.Wrap(err, "marshalling presentation_submission")
+	}
+	var s exchange.PresentationSubmission
+	if err := json.Unmarshal(submissionData, &s); err != nil {
+		return nil, errors.Wrap(err, "unmarshalling presentation submission")
+	}
+	if err := s.IsValid(); err != nil {
+		return nil, errors.Wrap(err, "verifying submission validity")
+	}
+	sdkVP.PresentationSubmission = s
+
+	credContainers, err := credint.NewCredentialContainerFromArray(sdkVP.VerifiableCredential)
+	if err != nil {
+		return nil, errors.Wrap(err, "parsing verifiable credential array")
+	}
+
+	return &presentation.CreateSubmissionRequest{
+		Presentation:  *sdkVP,
+		SubmissionJWT: r.SubmissionJWT,
+		Submission:    s,
+		Credentials:   credContainers}, nil
+}
+
 type Operation struct {
 	ID     string          `json:"id"`
 	Done   bool            `json:"done"`
@@ -202,7 +240,27 @@ type OperationResult struct {
 // @Failure      500      {string}  string  "Internal server error"
 // @Router       /v1/presentations/submissions [put]
 func (pr PresentationRouter) CreateSubmission(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	var resp Operation
+	var request CreateSubmissionRequest
+	if err := framework.Decode(r, &request); err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest)
+	}
+
+	req, err := request.toServiceRequest()
+	if err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "invalid create submission request"), http.StatusBadRequest)
+	}
+
+	operation, err := pr.service.CreateSubmission(*req)
+	if err != nil {
+		return framework.NewRequestError(
+			util.LoggingErrorMsg(err, "cannot create submission"), http.StatusInternalServerError)
+	}
+
+	resp := Operation{
+		ID: operation.ID,
+	}
 	return framework.Respond(ctx, w, resp, http.StatusCreated)
 }
 
diff --git a/pkg/service/manifest/application.go b/pkg/service/manifest/application.go
index edf9ecf..04b6cf9 100644
--- a/pkg/service/manifest/application.go
+++ b/pkg/service/manifest/application.go
@@ -2,31 +2,18 @@ package manifest
 
 import (
 	"fmt"
+	"github.com/tbd54566975/ssi-service/pkg/jwt"
 	"strings"
 
 	"github.com/TBD54566975/ssi-sdk/credential/manifest"
 	errresp "github.com/TBD54566975/ssi-sdk/error"
-	"github.com/pkg/errors"
-
-	didint "github.com/tbd54566975/ssi-service/internal/did"
 	"github.com/tbd54566975/ssi-service/internal/keyaccess"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/service/credential"
 )
 
 func (s Service) verifyApplicationJWT(did string, token keyaccess.JWT) error {
-	kid, pubKey, err := didint.ResolveKeyForDID(s.didResolver, did)
-	if err != nil {
-		return errors.Wrapf(err, "failed to resolve applicant's did: %s", did)
-	}
-	verifier, err := keyaccess.NewJWKKeyAccessVerifier(kid, pubKey)
-	if err != nil {
-		return util.LoggingErrorMsg(err, "could not create application verifier")
-	}
-	if err := verifier.Verify(token); err != nil {
-		return util.LoggingErrorMsg(err, "could not verify the application's signature")
-	}
-	return nil
+	return jwt.VerifyTokenFromDID(did, token, s.didResolver)
 }
 
 // validateCredentialApplication validates the credential application's signature(s) in addition to making sure it
diff --git a/pkg/service/operation/model.go b/pkg/service/operation/model.go
new file mode 100644
index 0000000..4dc1f35
--- /dev/null
+++ b/pkg/service/operation/model.go
@@ -0,0 +1,12 @@
+package operation
+
+type Result struct {
+	Error    string      `json:"error,omitempty"`
+	Response interface{} `json:"response,omitempty"`
+}
+
+type Operation struct {
+	ID     string `json:"json"`
+	Done   bool   `json:"done"`
+	Result Result `json:"result,omitempty"`
+}
diff --git a/pkg/service/operation/storage/bolt.go b/pkg/service/operation/storage/bolt.go
index 0198cfa..b74d0ca 100644
--- a/pkg/service/operation/storage/bolt.go
+++ b/pkg/service/operation/storage/bolt.go
@@ -1,32 +1,69 @@
 package storage
 
 import (
+	"github.com/goccy/go-json"
 	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
 
+const (
+	namespace = "operation"
+)
+
 type BoltOperationStorage struct {
 	db *storage.BoltDB
 }
 
 func (b BoltOperationStorage) StoreOperation(op StoredOperation) error {
-	// TODO(andres) implement me
-	panic("implement me")
+	id := op.ID
+	if id == "" {
+		return util.LoggingNewError("ID is required for storing operations")
+	}
+	jsonBytes, err := json.Marshal(op)
+	if err != nil {
+		return util.LoggingErrorMsgf(err, "marshalling operation with id: %s", id)
+	}
+	return b.db.Write(namespace, id, jsonBytes)
 }
 
 func (b BoltOperationStorage) GetOperation(id string) (*StoredOperation, error) {
-	// TODO(andres) implement me
-	panic("implement me")
+	jsonBytes, err := b.db.Read(namespace, id)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "reading operation with id: %s", id)
+	}
+	if len(jsonBytes) == 0 {
+		return nil, util.LoggingNewErrorf("operation not found with id: %s", id)
+	}
+	var stored StoredOperation
+	if err := json.Unmarshal(jsonBytes, &stored); err != nil {
+		return nil, util.LoggingErrorMsgf(err, "unmarshalling stored operation: %s", id)
+	}
+	return &stored, nil
 }
 
 func (b BoltOperationStorage) GetOperations() ([]StoredOperation, error) {
-	// TODO(andres) implement me
-	panic("implement me")
+	operations, err := b.db.ReadAll(namespace)
+	if err != nil {
+		return nil, util.LoggingErrorMsgf(err, "could not get all operations")
+	}
+	stored := make([]StoredOperation, 0, len(operations))
+	for i, manifestBytes := range operations {
+		var nextOp StoredOperation
+		if err = json.Unmarshal(manifestBytes, &nextOp); err != nil {
+			logrus.WithError(err).WithField("idx", i).Warnf("Skipping operation")
+		}
+		stored = append(stored, nextOp)
+	}
+	return stored, nil
 }
 
 func (b BoltOperationStorage) DeleteOperation(id string) error {
-	// TODO(andres) implement me
-	panic("implement me")
+	if err := b.db.Delete(namespace, id); err != nil {
+		return util.LoggingErrorMsgf(err, "deleting operation: %s", id)
+	}
+	return nil
 }
 
 func NewBoltOperationStorage(db *storage.BoltDB) (*BoltOperationStorage, error) {
diff --git a/pkg/service/presentation/model.go b/pkg/service/presentation/model.go
index 784effa..9d540bc 100644
--- a/pkg/service/presentation/model.go
+++ b/pkg/service/presentation/model.go
@@ -1,8 +1,11 @@
 package presentation
 
 import (
+	credsdk "github.com/TBD54566975/ssi-sdk/credential"
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
 	"github.com/TBD54566975/ssi-sdk/util"
+	"github.com/tbd54566975/ssi-service/internal/credential"
+	"github.com/tbd54566975/ssi-service/internal/keyaccess"
 )
 
 type CreatePresentationDefinitionRequest struct {
@@ -31,7 +34,10 @@ type DeletePresentationDefinitionRequest struct {
 }
 
 type CreateSubmissionRequest struct {
-	Submission exchange.PresentationSubmission `json:"submission" validate:"required"`
+	Presentation  credsdk.VerifiablePresentation  `json:"presentation" validate:"required"`
+	SubmissionJWT keyaccess.JWT                   `json:"submissionJwt,omitempty" validate:"required"`
+	Submission    exchange.PresentationSubmission `json:"submission" validate:"required"`
+	Credentials   []credential.Container          `json:"credentials,omitempty"`
 }
 
 func (csr CreateSubmissionRequest) IsValid() bool {
diff --git a/pkg/service/presentation/service.go b/pkg/service/presentation/service.go
index ce6875f..1181031 100644
--- a/pkg/service/presentation/service.go
+++ b/pkg/service/presentation/service.go
@@ -3,19 +3,30 @@ package presentation
 import (
 	"fmt"
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	"github.com/TBD54566975/ssi-sdk/credential/signing"
+	didsdk "github.com/TBD54566975/ssi-sdk/did"
 	sdkutil "github.com/TBD54566975/ssi-sdk/util"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/tbd54566975/ssi-service/config"
+	"github.com/tbd54566975/ssi-service/internal/credential"
 	"github.com/tbd54566975/ssi-service/internal/util"
+	"github.com/tbd54566975/ssi-service/pkg/jwt"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/operation"
+	opstorage "github.com/tbd54566975/ssi-service/pkg/service/operation/storage"
 	presentationstorage "github.com/tbd54566975/ssi-service/pkg/service/presentation/storage"
+	"github.com/tbd54566975/ssi-service/pkg/service/schema"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
 
 type Service struct {
-	storage presentationstorage.Storage
-	config  config.PresentationServiceConfig
+	storage    presentationstorage.Storage
+	opsStorage opstorage.Storage
+	config     config.PresentationServiceConfig
+	resolver   *didsdk.Resolver
+	schema     *schema.Service
+	verifier   *credential.Verifier
 }
 
 func (s Service) Type() framework.Type {
@@ -40,14 +51,26 @@ func (s Service) Config() config.PresentationServiceConfig {
 	return s.config
 }
 
-func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage) (*Service, error) {
+func NewPresentationService(config config.PresentationServiceConfig, s storage.ServiceStorage, resolver *didsdk.Resolver, schema *schema.Service) (*Service, error) {
 	presentationStorage, err := presentationstorage.NewPresentationStorage(s)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate definition storage for the presentation service")
 	}
+	opsStorage, err := opstorage.NewOperationStorage(s)
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not instantiate storage for the operations")
+	}
+	verifier, err := credential.NewCredentialVerifier(resolver, schema)
+	if err != nil {
+		return nil, util.LoggingErrorMsg(err, "could not instantiate verifier")
+	}
 	service := Service{
-		storage: presentationStorage,
-		config:  config,
+		storage:    presentationStorage,
+		opsStorage: opsStorage,
+		config:     config,
+		resolver:   resolver,
+		schema:     schema,
+		verifier:   verifier,
 	}
 	if !service.Status().IsReady() {
 		return nil, errors.New(service.Status().Message)
@@ -104,25 +127,72 @@ func (s Service) DeletePresentationDefinition(request DeletePresentationDefiniti
 
 // CreateSubmission houses the main service logic for presentation submission creation. It validates the input, and
 // produces a presentation submission value that conforms with the Submission specification.
-func (s Service) CreateSubmission(request CreateSubmissionRequest) (*CreateSubmissionResponse, error) {
-	logrus.Debugf("creating presentation submission: %+v", request)
-
+func (s Service) CreateSubmission(request CreateSubmissionRequest) (*operation.Operation, error) {
 	if !request.IsValid() {
-		return nil, util.LoggingNewErrorf("invalid create presentation submission request: %+v", request)
+		return nil, errors.Errorf("invalid create presentation submission request: %+v", request)
 	}
 
 	if err := exchange.IsValidPresentationSubmission(request.Submission); err != nil {
-		return nil, util.LoggingErrorMsg(err, "provided value is not a valid presentation submission")
+		return nil, errors.Wrap(err, "provided value is not a valid presentation submission")
+	}
+
+	sdkVP, err := signing.ParseVerifiablePresentationFromJWT(request.SubmissionJWT.String())
+	if err != nil {
+		return nil, errors.Wrap(err, "parsing vp from jwt")
+	}
+	if err := jwt.VerifyTokenFromDID(sdkVP.Holder, request.SubmissionJWT, s.resolver); err != nil {
+		return nil, errors.Wrap(err, "verifying token from did")
+	}
+
+	if _, err := s.storage.GetSubmission(request.Submission.ID); !errors.Is(err, presentationstorage.ErrSubmissionNotFound) {
+		return nil, errors.Errorf("submission with id %s already present", request.Submission.ID)
+	}
+
+	definition, err := s.storage.GetPresentation(request.Submission.DefinitionID)
+	if err != nil {
+		return nil, errors.Wrap(err, "getting presentation definition")
+	}
+
+	for _, cred := range request.Credentials {
+		if !cred.IsValid() {
+			return nil, errors.Errorf("invalid credential %+v", cred)
+		}
+		if cred.CredentialJWT != nil {
+			if err := s.verifier.VerifyJWTCredential(*cred.CredentialJWT); err != nil {
+				return nil, errors.Wrapf(err, "verifying jwt credential %s", cred.CredentialJWT)
+			}
+		} else {
+			if cred.Credential != nil && cred.Credential.Proof != nil {
+				if err := s.verifier.VerifyDataIntegrityCredential(*cred.Credential); err != nil {
+					return nil, errors.Wrapf(err, "verifying data integrity credential %+v", cred.Credential)
+				}
+			}
+		}
+	}
+
+	if err := exchange.VerifyPresentationSubmissionVP(definition.PresentationDefinition, request.Presentation); err != nil {
+		return nil, errors.Wrap(err, "verifying presentation submission vp")
 	}
 
 	storedSubmission := presentationstorage.StoredSubmission{Submission: request.Submission}
 
+	// TODO(andres): IO requests should be done in parallel, once we have context wired up.
 	if err := s.storage.StoreSubmission(storedSubmission); err != nil {
-		return nil, util.LoggingErrorMsg(err, "could not store presentation")
+		return nil, errors.Wrap(err, "could not store presentation")
+	}
+
+	opID := fmt.Sprintf("presentations/submissions/%s", storedSubmission.Submission.ID)
+	storedOp := opstorage.StoredOperation{
+		ID:   opID,
+		Done: false,
+	}
+	if err := s.opsStorage.StoreOperation(storedOp); err != nil {
+		return nil, errors.Wrap(err, "could not store operation")
 	}
 
-	return &CreateSubmissionResponse{
-		Submission: storedSubmission.Submission,
+	return &operation.Operation{
+		ID:   storedOp.ID,
+		Done: false,
 	}, nil
 }
 
diff --git a/pkg/service/presentation/storage/bolt.go b/pkg/service/presentation/storage/bolt.go
index 2c1f883..2c6b7c7 100644
--- a/pkg/service/presentation/storage/bolt.go
+++ b/pkg/service/presentation/storage/bolt.go
@@ -85,7 +85,7 @@ func (b BoltPresentationStorage) GetSubmission(id string) (*StoredSubmission, er
 		return nil, util.LoggingNewErrorf("could not get submission definition: %s", id)
 	}
 	if len(jsonBytes) == 0 {
-		return nil, util.LoggingNewErrorf("submission definition not found with id: %s", id)
+		return nil, util.LoggingErrorMsgf(ErrSubmissionNotFound, "submission not found with id: %s", id)
 	}
 	var stored StoredSubmission
 	if err := json.Unmarshal(jsonBytes, &stored); err != nil {
diff --git a/pkg/service/presentation/storage/storage.go b/pkg/service/presentation/storage/storage.go
index 28f0205..fb49eef 100644
--- a/pkg/service/presentation/storage/storage.go
+++ b/pkg/service/presentation/storage/storage.go
@@ -2,6 +2,7 @@ package storage
 
 import (
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	"github.com/pkg/errors"
 	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
@@ -48,3 +49,5 @@ type SubmissionStorage interface {
 	StoreSubmission(schema StoredSubmission) error
 	GetSubmission(id string) (*StoredSubmission, error)
 }
+
+var ErrSubmissionNotFound = errors.New("submission not found")
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 4b2e25d..15247a6 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -97,7 +97,7 @@ func instantiateServices(config config.ServicesConfig) ([]framework.Service, err
 		return nil, util.LoggingErrorMsg(err, "could not instantiate the manifest service")
 	}
 
-	presentationService, err := presentation.NewPresentationService(config.PresentationConfig, storageProvider)
+	presentationService, err := presentation.NewPresentationService(config.PresentationConfig, storageProvider, didResolver, schemaService)
 	if err != nil {
 		return nil, util.LoggingErrorMsg(err, "could not instantiate the presentation service")
 	}
diff --git a/sip/sips/sip6/README.md b/sip/sips/sip6/README.md
index b242961..7432f50 100644
--- a/sip/sips/sip6/README.md
+++ b/sip/sips/sip6/README.md
@@ -184,7 +184,7 @@ A `Submission` object:
 {
   "iss": "did:web:andresuribe.com",
   "vp": {
-		"presentation_submission": {
+    "presentation_submission": {
 	    "id": "a30e3b91-fb77-4d22-95fa-871689c322e2",
 	    "definition_id": "32f54163-7166-48f1-93d8-ff217bdb0653",
 	    "descriptor_map": [


Test patch:
diff --git a/pkg/server/router/presentation_test.go b/pkg/server/router/presentation_test.go
index dc9acb2..3f51be5 100644
--- a/pkg/server/router/presentation_test.go
+++ b/pkg/server/router/presentation_test.go
@@ -35,7 +35,11 @@ func TestPresentationDefinitionService(t *testing.T) {
 	s, err := storage.NewStorage(storage.Bolt)
 	assert.NoError(t, err)
 
-	service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s)
+	keyStoreService := testKeyStoreService(t, s)
+	didService := testDIDService(t, s, keyStoreService)
+	schemaService := testSchemaService(t, s, keyStoreService, didService)
+
+	service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService)
 	assert.NoError(t, err)
 
 	t.Run("Create returns the created definition", func(t *testing.T) {
diff --git a/pkg/server/router/testutils_test.go b/pkg/server/router/testutils_test.go
index 90bcd54..6bee356 100644
--- a/pkg/server/router/testutils_test.go
+++ b/pkg/server/router/testutils_test.go
@@ -14,7 +14,7 @@ import (
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
 
-func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
+func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service {
 	serviceConfig := config.KeyStoreServiceConfig{ServiceKeyPassword: "test-password"}
 	// create a keystore service
 	keystoreService, err := keystore.NewKeyStoreService(serviceConfig, db)
@@ -23,7 +23,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
 	return keystoreService
 }
 
-func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service) *did.Service {
+func testDIDService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service) *did.Service {
 	serviceConfig := config.DIDServiceConfig{
 		BaseServiceConfig: &config.BaseServiceConfig{
 			Name: "did",
@@ -38,7 +38,7 @@ func testDIDService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service
 	return didService
 }
 
-func testSchemaService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service {
+func testSchemaService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service {
 	serviceConfig := config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "schema"}}
 	// create a schema service
 	schemaService, err := schema.NewSchemaService(serviceConfig, db, keyStore, did.GetResolver())
diff --git a/pkg/server/server_did_test.go b/pkg/server/server_did_test.go
index 25a5120..4733cd2 100644
--- a/pkg/server/server_did_test.go
+++ b/pkg/server/server_did_test.go
@@ -298,7 +298,7 @@ func TestDIDAPI(t *testing.T) {
 		}
 		err = didService.ResolveDID(newRequestContextWithParams(badParams), w, req)
 		assert.Error(tt, err)
-		assert.Contains(tt, err.Error(), "did:key:abcd: selected encoding not supported")
+		assert.Contains(tt, err.Error(), "selected encoding not supported")
 
 		w.Flush()
 
diff --git a/pkg/server/server_presentation_test.go b/pkg/server/server_presentation_test.go
index 6e3422f..442dc1e 100644
--- a/pkg/server/server_presentation_test.go
+++ b/pkg/server/server_presentation_test.go
@@ -2,13 +2,17 @@ package server
 
 import (
 	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential"
 	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	"github.com/TBD54566975/ssi-sdk/credential/signing"
 	"github.com/TBD54566975/ssi-sdk/crypto"
 	"github.com/goccy/go-json"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	"github.com/tbd54566975/ssi-service/config"
+	"github.com/tbd54566975/ssi-service/internal/keyaccess"
 	"github.com/tbd54566975/ssi-service/pkg/server/router"
 	"github.com/tbd54566975/ssi-service/pkg/service/presentation"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
@@ -40,7 +44,11 @@ func TestPresentationAPI(t *testing.T) {
 	s, err := storage.NewStorage(storage.Bolt)
 	assert.NoError(t, err)
 
-	service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s)
+	keyStoreService := testKeyStoreService(t, s)
+	didService := testDIDService(t, s, keyStoreService)
+	schemaService := testSchemaService(t, s, keyStoreService, didService)
+
+	service, err := presentation.NewPresentationService(config.PresentationServiceConfig{}, s, didService.GetResolver(), schemaService)
 	assert.NoError(t, err)
 
 	pRouter, err := router.NewPresentationRouter(service)
@@ -136,4 +144,144 @@ func TestPresentationAPI(t *testing.T) {
 		assert.Error(t, pRouter.DeletePresentationDefinition(newRequestContext(), w, req))
 		w.Flush()
 	})
+
+	t.Run("well formed submission returns operation", func(t *testing.T) {
+
+		definition := createPresentationDefinition(t, pRouter)
+		vc := credential.VerifiableCredential{
+			Context:          []string{credential.VerifiableCredentialsLinkedDataContext},
+			ID:               "7035a7ec-66c8-4aec-9191-a34e8cf1e82b",
+			Type:             []string{credential.VerifiablePresentationType},
+			Issuer:           "did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs",
+			IssuanceDate:     "2022-11-07T21:28:57Z",
+			ExpirationDate:   "2051-10-05T14:48:00.000Z",
+			CredentialStatus: nil,
+			CredentialSubject: credential.CredentialSubject{
+				"additionalName": "Mclovin",
+				"dateOfBirth":    "1987-01-02",
+				"familyName":     "Andres",
+				"givenName":      "Uribe",
+				"id":             "did:web:andresuribe.com",
+			},
+			CredentialSchema: nil,
+			RefreshService:   nil,
+			TermsOfUse:       nil,
+			Evidence:         nil,
+			Proof:            nil,
+		}
+
+		signer0 := getTestVectorKey0Signer(t)
+		vcData, err := signing.SignVerifiableCredentialJWT(signer0, vc)
+		assert.NoError(t, err)
+		ps := exchange.PresentationSubmission{
+			ID:           "a30e3b91-fb77-4d22-95fa-871689c322e2",
+			DefinitionID: definition.PresentationDefinition.ID,
+			DescriptorMap: []exchange.SubmissionDescriptor{
+				{
+					ID:         "wa_driver_license",
+					Format:     string(exchange.JWTVPTarget),
+					Path:       "$.verifiableCredential[0]",
+					PathNested: nil,
+				},
+			},
+		}
+		vp := credential.VerifiablePresentation{
+			Context:                []string{credential.VerifiableCredentialsLinkedDataContext},
+			ID:                     "a9b575c7-bac2-47e7-a925-c432815ebb4c",
+			Holder:                 "did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU",
+			Type:                   []string{credential.VerifiablePresentationType},
+			PresentationSubmission: ps,
+			VerifiableCredential:   []interface{}{keyaccess.JWT(vcData)},
+			Proof:                  nil,
+		}
+
+		signer1 := getTestVectorKey1Signer(t)
+		signed, err := signing.SignVerifiablePresentationJWT(signer1, vp)
+		assert.NoError(t, err)
+
+		request := router.CreateSubmissionRequest{SubmissionJWT: keyaccess.JWT(signed)}
+
+		value := newRequestValue(t, request)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/submissions", value)
+		w := httptest.NewRecorder()
+
+		err = pRouter.CreateSubmission(newRequestContext(), w, req)
+
+		require.NoError(t, err)
+		var resp router.Operation
+		assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+		assert.Contains(t, resp.ID, "presentations/submissions/")
+		assert.False(t, resp.Done)
+		assert.Zero(t, resp.Result)
+	})
+}
+
+func createPresentationDefinition(t *testing.T, pRouter *router.PresentationRouter) router.CreatePresentationDefinitionResponse {
+	request := router.CreatePresentationDefinitionRequest{
+		Name:                   "name",
+		Purpose:                "purpose",
+		Format:                 nil,
+		SubmissionRequirements: nil,
+		InputDescriptors: []exchange.InputDescriptor{
+			{
+				ID:      "wa_driver_license",
+				Name:    "washington state business license",
+				Purpose: "some testing stuff",
+				Format:  nil,
+				Constraints: &exchange.Constraints{
+					Fields: []exchange.Field{
+						{
+							ID: "date_of_birth",
+							Path: []string{
+								"$.credentialSubject.dateOfBirth",
+								"$.credentialSubject.dob",
+								"$.vc.credentialSubject.dateOfBirth",
+								"$.vc.credentialSubject.dob",
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	value := newRequestValue(t, request)
+	req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/presentations/definitions", value)
+	w := httptest.NewRecorder()
+
+	assert.NoError(t, pRouter.CreatePresentationDefinition(newRequestContext(), w, req))
+	var resp router.CreatePresentationDefinitionResponse
+	assert.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
+	return resp
+}
+
+func getTestVectorKey0Signer(t *testing.T) crypto.JWTSigner {
+	// AKA the issuers key
+	// AKA did:key:z4oJ8bFEFv7E3omhuK5LrAtL29Nmd8heBey9HtJCSvodSb7nrfaMrd6zb7fjYSRxrfSgBSDeM6Bs59KRKFgXSDWJcfcjs
+	knownJWK := crypto.PrivateKeyJWK{
+		KTY: "EC",
+		CRV: "P-256",
+		X:   "SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74",
+		Y:   "lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI",
+		D:   "0g5vAEKzugrXaRbgKG0Tj2qJ5lMP4Bezds1_sTybkfk",
+	}
+
+	signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK)
+	assert.NoError(t, err)
+	return *signer
+}
+
+func getTestVectorKey1Signer(t *testing.T) crypto.JWTSigner {
+	// AKA the submitter of the presentation
+	// AKA did:key:z4oJ8eRi73fvkrXBgqTHZRTropESXLc7Vet8XpJrGUSBZAT2UHvQBYpBEPdAUiyKBi2XC2iFjgtn5Gw2Qd4WXHyj1LxjU
+	knownJWK := crypto.PrivateKeyJWK{
+		KTY: "EC",
+		CRV: "P-256",
+		X:   "6HEz8SLP7NgHPGp0bElryiD7u3_cO1EmX-ngsV_yLsI",
+		Y:   "QlIYaYyDLxLkybDan9LOSkfGvjzZsrdgAb_nQr_Li5M",
+		D:   "7m6c2Axy9OWi7-d9hFVhmMe22vQTfQDL_pG-3WFsjzc",
+	}
+
+	signer, err := crypto.NewJWTSignerFromJWK(knownJWK.KID, knownJWK)
+	assert.NoError(t, err)
+	return *signer
 }
diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go
index 0aa3948..ed449ee 100644
--- a/pkg/server/server_test.go
+++ b/pkg/server/server_test.go
@@ -195,7 +195,7 @@ func testKeyStore(t *testing.T, bolt *storage.BoltDB) (*router.KeyStoreRouter, *
 	return keyStoreRouter, keyStoreService
 }
 
-func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
+func testKeyStoreService(t *testing.T, db storage.ServiceStorage) *keystore.Service {
 	serviceConfig := config.KeyStoreServiceConfig{
 		BaseServiceConfig:  &config.BaseServiceConfig{Name: "test-keystore"},
 		ServiceKeyPassword: "test-password",
@@ -208,7 +208,7 @@ func testKeyStoreService(t *testing.T, db *storage.BoltDB) *keystore.Service {
 	return keystoreService
 }
 
-func testDIDService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service) *did.Service {
+func testDIDService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service) *did.Service {
 	serviceConfig := config.DIDServiceConfig{
 		BaseServiceConfig: &config.BaseServiceConfig{Name: "test-did"},
 		Methods:           []string{"key"},
@@ -232,7 +232,7 @@ func testDIDRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Servic
 	return didRouter
 }
 
-func testSchemaService(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Service, did *did.Service) *schema.Service {
+func testSchemaService(t *testing.T, bolt storage.ServiceStorage, keyStore *keystore.Service, did *did.Service) *schema.Service {
 	schemaService, err := schema.NewSchemaService(config.SchemaServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "test-schema"}}, bolt, keyStore, did.GetResolver())
 	require.NoError(t, err)
 	require.NotEmpty(t, schemaService)
@@ -249,7 +249,7 @@ func testSchemaRouter(t *testing.T, bolt *storage.BoltDB, keyStore *keystore.Ser
 	return schemaRouter
 }
 
-func testCredentialService(t *testing.T, db *storage.BoltDB, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service {
+func testCredentialService(t *testing.T, db storage.ServiceStorage, keyStore *keystore.Service, did *did.Service, schema *schema.Service) *credential.Service {
 	serviceConfig := config.CredentialServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "credential"}}
 
 	// create a credential service