Approve with API

When configured for API mode for approvals, the Virtual Signer will call your own API to let it decide when to approve, reject, or abstain from voting on requests.

When ApprovalMode is set to "API" in configuration, the Virtual Signer will call the URLs defined in ExternalTransactionApprovalURL and ExternalReshareApprovalURL for the approval of requests. The Virtual Signer request and the API response must implement the HTTP Message Signatures protocol, based on IETF HTTP Message Signatures and as outlined below.

The response signatures guarantee that indeed the original customer server generated the responses.

Preparing for Setup

As a customer, use a secure server to create an EdDSA key pair with openssl, and be sure to save these files safely and on a secure device.

openssl genpkey -algorithm ed25519 -out private_ed.pem
openssl pkey -in private_ed.pem -pubout -out public_ed.pem

Before running a Virtual Signer instance, set ApprovalHTTPPublicKeyHex in configuration to be the value of the public key has a hexadecimal encoding of the PEM format (public_ed.pem). For example, for this public key in PEM format:

-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAyS9WQDUN9LeyDc1nV6wyKgGPyngsHYNmdQaj6U2suuk=
-----END PUBLIC KEY-----

set in configuration its ASCII string value (including new lines) encoded in hexadecimal:

"ApprovalHTTPPublicKeyHex": "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d436f77425159444b32567741794541795339575144554e394c65794463316e563677794b674750796e677348594e6d6451616a3655327375756b3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d",
📘

To convert the PEM file to hex easily, you may use an online tool like the one here. This is a non sensitive file containing a public key. Be careful to encode the new-line character.

Keep in mind that you should only ever put the public part of a keypair (public_ed.pem in this case), marked with the words "BEGIN PUBLIC KEY" into an online form.

The private part of your keypair should be moved immediately into secure storage (like a secrets manager or HSM) and not exposed to your OS clipboard or the internet in any way.

The service that implements the customer API will receive requests from the Virtual Signer with the Accept-Signature and VS-Nonce headers (please note that some gateways may change these header keys to lower-case before the request reaches the server). Accept-Signature will have keyid set to eddsa-key and will contain the signature fields content-type and digest. The service must use the EdDSA private key (private_ed.pem) to sign its response and set signature in the header.

📘

When computing the digest based on the complete JSON string, be careful with new-line characters, with case sensitivity and with character byte encodings (such as UTF-8).

Once the customer API responds to the inbound HTTP request, the Virtual Signer (VS) will validate the response signature. If the validation fails, the Virtual Signer will encounter an error and will retry after a short delay.

The Virtual Signer includes a VS-Nonce header in its request. It is a random positive integer less than or equal
to JavaScript's Number.MAX_SAFE_INTEGER. The response must echo back the token in its JSON, as shown below.

Note that even though in section 6.2.2 of the HTTP Message Signatures spec the algorithm name is ed25519 the Virtual Signer expects the algorithm key in signature to be set to hs2019.

The Request from the VS

The Virtual Signer sends requests serialized as JSON to the customer's API. The Virtual Signer will only execute these requests if these are approved by the API. See Sample Reshare request and Sample Transfer Out Request for the general contents of these objects, but we recommend logging them out during development of your API.

An OperationReshare_v3 represents a vault creation or a request to reshare, that is, to change the party composition and/or weights of the vault. On the other hand, a OperationTransferOut_v3 represents a request to transfer tokens.

Example Request

POST https://<customer_service>/my-api/transaction-approval

Headers: 
"Accept-Signature": "sig1=(\"content-type\" \"digest\");nonce=83727271837;keyid=\"eddsa-key\""
"VS-Nonce": 83727271837

Body: {"__typename":"OperationReshare_v3", "id": "..." ...

The Response from your API

The request and the response follow the HTTP Message Signatures protocol. The server scripts have an Ed25519 private key. They take a JSON that is the body of the response to produce a content digest and a signature. This is a sample response body:

{ "status": "rejected", "nonce": 3827271 }

Possible values of status in your API response are approved, rejected, or abstain which will affect the voting performed by the Virtual Signer accordingly.

The response JSON must echo back the nonce originally included in the request (VS-Nonce header). Its maximum value is 0x7FFFFFFFFFFFFFFE.

Example Response

Headers:  
"Content-Type": "application/json",
  "digest": "SHA-512=OA3Yt3rU5+UHDI06FhRqZQtiKbwuytApQ+sVIS8hQ1p29i0q0yX4JXv2lhPwpZGVmv66X0ZkMBRimTq1EIiP3g==",
    "Signature": "keyId=\"eddsa-key\",algorithm=\"hs2019\",headers=\"content-type digest\",signature=\"zlJViX2CyB49266bswzk2zMbCc38y8N3iI1dXEoPJt95v2icTgZoFmI9cvtGFcRyycFfd3E/TUeuiGVs/eLfAQ==\"",

Body: `{ "status": "approved", "nonce": 83727271837 } `

Example

Here is a more detailed sample request/response pair, where the Python script within the Azure Container App
produces the signed response, following the HTTP Message Signatures protocol:

 % curl -v --location 'https://vsapprovaltestfunc.greenbay-b1c1bd0a.eastus.azurecontainerapps.io/api/httpvsapproval' \
--header 'Accept-Signature: sig1=("content-type" "digest");nonce=83727271;keyid="eddsa-key"' \
--header 'VS-Nonce: 83727271' \
--header 'Content-Type: application/json' \
--data-raw '{
    "__typename": "OperationTransferOut_v3",
     "id" : "xw8ufea4rdtaaaas1ck89htz",
}'
*   Trying 4.157.75.188:443...
* Connected to vsapprovaltestfunc.greenbay-b1c1bd0a.eastus.azurecontainerapps.io (4.157.75.188) port 443 (#0)
[...]
> POST /api/httpvsapproval HTTP/2
> Host: vsapprovaltestfunc.greenbay-b1c1bd0a.eastus.azurecontainerapps.io
> User-Agent: curl/8.1.2
> Accept: */*
> Accept-Signature: sig1=("content-type" "digest");nonce=83727271;keyid="eddsa-key"
> VS-Nonce: 83727271
> Content-Type: application/json
> Content-Length: 439
> 
* We are completely uploaded and fine
< HTTP/2 200 
< content-type: application/json
< date: Fri, 04 Aug 2023 21:30:53 GMT
< server: Kestrel
< request-context: appId=cid-v1:62fa0dbc-ae2b-4a3d-b3d4-593648a55c62
< signature: keyId="eddsa-key",algorithm="hs2019",headers="content-type digest",signature="Ow0Sh6VItuKtC6nqAeB7Qx5GPcVe3rvjbZeRlLoRbYZKLawOv3ruFdoiAWWiKdgyxwITDpmFO8TqSfvO9140BQ=="
< digest: SHA-512=b9e4zA1CER+mr+K+Pmn1G1OaFc1cWXY5lKtk6Mdh3fmyy0mwLVFKmdpNidd3apveLmiRXFSAywg9YzMHMJUN3g==
< 
* Connection #0 to host vsapprovaltestfunc.greenbay-b1c1bd0a.eastus.azurecontainerapps.io left intact
{"status": "approved", "nonce": 83727271}

Sample API Implementations

You may find some public sample scripts here: Approval Integration with the Virtual Signer on GitHub. There are samples for:

  • AWS Lambda
  • Azure Container App with Python (Readme)

There are three sample scripts that produce signed approval/abstention/rejection responses. NodeJS and Python lambdas intended to run on AWS, and a Azure Container App with a Python script. The former can be found in the doc/approval_API_samples/lambda* folders and the latter in doc/approval_API_samples/azure_container_app_proj. azure_container_app_proj is a Visual Studio Code project. See the Readme for more information, including deployment instructions.

In the samples, the lambda-js folder contains a sample NodeJS script for use as a AWS lambda. The lambda-pythonfolder, in turn, contains a sample Python script for AWS. The azure_container_app_proj folder contains a sample Azure Container App with a Python Function. This project will deploy the Python script within a Docker image.

📘

Note

Note that the NodeJS and Python scripts refer to cryptographic libraries. It is common for cryptographic libraries to contain native bindings that are CPU-specific. By deploying these projects as Docker containers, you will avoid cross-platform incompatibilities.

The Azure Container App above contains a Dockerfile for deployment with Docker.