specifications/transactions/offchain-api/README.md
As defined in DIP-1, the Diem Off-Chain Protocol defines an API and payload specification to privately exchange information between two parties that cannot (easily) be achieved directly on a blockchain. This information includes the sensitive details of the parties involved in a payment that should remain off-chain. The information is exchanged within an authenticated and encrypted channel and only made available to the parties that are directly involved.
The two parties that participate in the off-chain protocol communicate through HTTP requests and responses within a TLS channel. Each request and response is signed via JSON Web Signature (JWS) to ensure authenticity and integrity. Consider an example in which VASP A sends a command to VASP B:
DualAttestation::Credential::base_url resourceCommandRequestObjectDualAttestation::Credential::compliance_public_keyCommandRequestObject
CommandResponseObjectDualAttestation::Credential::compliance_public_keyCommandResponseObject, validates the response, and concludes the exchangeIn order to participate in the an off-chain protocol exchange, a VASP must have a valid Parent VASP account, as defined in DIP-2. In addition, the VASP must specify a valid DualAttestation::Credential’s compliance_public_key and base_url.
The compliance_public_key maps to an Ed25519 private key with which the VASP uses to authenticate all off-chain requests and responses.
The base_url defines the endpoint for handling off-chain requests. The base_url should be unique for each Parent VASP account. That is the off-chain framework does not inherently support multiplexing across accounts or chains when distinct compliance_public_keys are used for each.
Each VASP exposes an HTTPS POST end point at https://hostname:<port>/<protocol_version>/command. The protocol_version is v2 for the currente off-chain APIs.
The base url value must be the url without the path /<protocol_version>/command: https://<hostname>:<port>.
All HTTP requests must contain:
X-REQUEST-ID with a unique UUID (according to RFC4122 with “-”'s included) for the request, used for tracking requests and debugging. Responses to requests must have the same value as the request's X-REQUEST-ID header.X-REQUEST-SENDER-ADDRESS with the HTTP request sender’s VASP DIP-5 address used in the command object.The HTTP request sender must use the compliance key of the VASP account linked with the sender's address to sign the request JWS body. The request receiver uses this address to find the appropriate compliance key to verify the signed request. For example: VASP A transfers funds to VASP B. The HTTP request A sends to B contains X-REQUEST-SENDER-ADDRESS as VASP A’s address.
All HTTP responses must contain:
X-REQUEST-ID copied from the HTTP request.The HTTP response sender must use the compliance key of the VASP account linked with the responder's address to sign the response JWS body.
The payloads between two endpoints must:
All requests between VASPs are structured as a CommandRequestObject and all responses are structured as a CommandResponseObject. The resulting request takes a form of the following (prior to JWS signing):
{
"_ObjectType": "CommandRequestObject",
"command_type": "SomeCommand", // Command type
"command": SomeCommandObject(), // Object of type as specified by command_type
"cid": "12ce83f6-6d18-0d6e-08b6-c00fdbbf085a",
}
A response would look like the following:
{
"_ObjectType": "CommandResponseObject",
"status": "success",
"cid": "12ce83f6-6d18-0d6e-08b6-c00fdbbf085a"
}
All requests between VASPs are structured as a CommandRequestObject.
| Field | Type | Required? | Description |
|---|---|---|---|
| _ObjectType | str | Y | Fixed value: CommandRequestObject |
| command_type | str | Y | A string representing the type of Command contained in the request. |
| command | Command object | Y | The Command to sequence. |
| cid | str | Y | A unique identifier for the Command. Must be a UUID according to RFC4122 with "-"'s included. |
{
"_ObjectType": "CommandRequestObject",
"command_type": CommandType,
"command": CommandObject(),
"cid": str,
}
All responses to a CommandRequestObject are in the form of a CommandResponseObject
| Field | Type | Required? | Description |
|---|---|---|---|
| _ObjectType | str | Y | The fixed string CommandResponseObject. |
| status | str | Y | Either success or failure. |
| error | OffChainErrorObject | N | Details of the error when status == "failure". |
| result | Object | N | An optional JSON object that may be defined when status == "success". |
| cid | str | N | The Command identifier to which this is a response. Must be a UUID according to RFC4122 with "-"'s included and must match the 'cid' of the CommandRequestObject. This field must be set unless the request to which this is responding is unparseable. |
Failure:
{
"_ObjectType": "CommandResponseObject",
"error": OffChainErrorObject(),
"status": "failure"
"cid": str,
}
Success:
{
"_ObjectType": "CommandResponseObject",
"result": Object(),
"status": "success",
"cid": str,
}
Represents an error that occurred in response to a Command.
| Field | Type | Required? | Description |
|---|---|---|---|
| type | str (enum) | Y | Either "command_error" or "protocol_error". |
| field | str | N | The field on which this error occurred. |
| code | str (enum) | Y | The error code of the corresponding error. |
| message | str | N | Additional details about this error. |
{
"type": "protocol_error",
"field": "cid",
"code": "missing_field",
"message": "",
}
Use the type protocol_error when:
CommandRequestObject's fields have validation errors. The CommandRequestObject#command_type must also match the CommandRequestObject#command, but further validation errors are flagged as command_errors.Use the type command_error when:
For example, if an off-chain service cannot access its Diem service, it should respond with a 500 status code along with the JWS ecndoed CommandResponseObject.
The following sections list all error codes for various validations when processing an inbound command request.
| Error code | Description |
|---|---|
invalid_http_header | X-REQUEST-SENDER-ADDRESS is not a valid account address, no such account exists on-chain, or the compliance_public_key is not a valid Ed25519 key |
X-REQUEST-ID is not a valid UUID | |
missing_http_header | Missing a required HTTP header X-REQUEST-ID or X-REQUEST-SENDER-ADDRESS |
invalid_jws | Invalid JWS |
invalid_jws_signature | JWS signature verification failed |
invalid_json | Decoded JWS body is not valid json |
invalid_object | Command request/response object json is not object, or the command object type does not match command_type |
missing_field | A required field contains no data or is missing |
unknown_field | An object contains an unknown field |
unknown_command_type | Received an invalid/unsupported command_type |
invalid_field_value | A field's value does not match the expected type |
In the case of network failure, the sending party for a Command is expected to re-send the Command until it gets a response from the counterparty VASP. An exponential backoff is suggested for Command re-sends.
Upon receipt of a Command that has already been processed (resulting in a success response or a Command error), the receiving side must reply with the same response as was previously issued (successful commands, or commands that fail with a command error, are idempotent). Requests that resulted in protocol errors may result in different responses.
For example:
In order to facilitate idempotent functionality, the requesting participant should use the same cid on all retry requests, so that the receiving participant can distinguish retries and provide idempotency where appropriate. Upon a cid match, the receiving participant should also verify that the CommandRequestObject is identical to the previous command.
All CommandRequestObject and CommandResponseObject messages exchanged on the off-chain channel between two services must be signed using a specific configuration of the JWS scheme.
The JSON Web Signature (JWS) scheme is specified in RFC 7515. Messages are signed with the following parameters:
Test Vector:
JWK key:
{"crv":"Ed25519","d":"vLtWeB7kt7fcMPlk01GhGmpWYTHYqnGRZUUN72AT1K4","kty":"OKP","x":"vUfj56-5Teu9guEKt9QQqIW1idtJE4YoVirC7IVyYSk"}
Corresponding verification key (hex, bytes), as the 32 bytes stored on the Diem blockchain:
"bd47e3e7afb94debbd82e10ab7d410a885b589db49138628562ac2ec85726129" (len=64)
Sample payload message to sign (str, utf8):
"Sample signed payload." (len=22)
Valid JWS Compact Signature (str, utf8):
"eyJhbGciOiJFZERTQSJ9.U2FtcGxlIHNpZ25lZCBwYXlsb2FkLg.dZvbycl2Jkl3H7NmQzL6P0_lDEW42s9FrZ8z-hXkLqYyxNq8yOlDjlP9wh3wyop5MU2sIOYvay-laBmpdW6OBQ" (len=138)