Encrypted PIN Transfer

This guide describes how to implement secure PIN transfer for your end-users.

📘

Applicable only for the request that accepts the Signing-Method header.

Encrypted PIN Transfer

This feature allows you to transfer your end-users signing methods (PIN, Emergency Code, Biometrics) in a secure and encrypted way so that it is not visible on your system in any way. Your system won't be able to view or read the end-user signing method's value.

This feature also ensures that the original end-user request body is not tampered with and helps avoid duplicate transactions.

Flowchart

Encrypted PIN Transfer Flowchart

Encrypted PIN Transfer Flowchart

📘

View the TypeScript code example for Encrypted PIN Transfer.

1. Generate random AES 256-bit key

First, generate a random AES 256-bit key.

2. Generate a random iv vector (12 bytes)

Next, generate a random iv vector which should be 12 bytes.

3. Encrypt the AES 256-bit key

Next, encrypt the randomly generated AES 256-bit key with the AWS RSA-2048 public key that we expose in our publicly available endpoint:

Endpoint:

GET https://api-wallet.venly.io/api/security

Response Body:

{
    "success": true,
    "result": {
            "encryptionKeys": [
                {
                    "id": "837943da-82aa-49c5-bab7-503010985ae9",
                    "keyspec": "RSA_2048",
                    "publicKey": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnDcmfHx3yZxwgZW7r+iUlUdI4a6Ot5WP8P4gzc/emfafLgOGQCoZue6n99BD6iRynnwqHUKd3fS50UX5vmZmXOJGHXlXCRsv5Z1/P32s/q5bLnTGpmzZOQXeiaOMbXgcOWcS0XhVGfioB3VsfURFBOU7okmMY0iAPPA7cdBK5fLTb0CYulMdIKfgWzeBBbqT0J6mRdUfbvXqA2gOLmaZRXKerdJUBbnNc3oOxgsk2noMlyOUId6SZsJYxQZRyjErBSjM+qNitEYLKO8tlxiPtLFWOGAa782nSMNJaLcdGWdz5TeADyvlJbbsvItA1lDWTbnJQyeN0bMDzL5XYcPTkQIDAQAB",
                    "encryptionAlgorithm": "RSAES_OAEP_SHA_256",
                }
            ]
        
    }
}

4. Prepare the Request Body

Generate a SHA256 hash based on the request body.

Example Request Body:

{
  "transactionRequest": {
    "type" : "TRANSFER",
    "secretType" : "MATIC",
    "walletId": "56137f2d-b4e9-42de-943b-cee828eb222a",
    "to" : "0x8D6331D6C5ba3306961F0b4Ea13ff13aa43560b9",
    "value": 1
  }
}

Example SHA256 Hash:

9ad9900886a75c33c327168ed6b0f8892eeffab26369e74082bc120887653762

5. Encrypt Signing-Method body with the Raw AES key

Next, encrypt the Signing-Method body with your raw AES key. The structure of the Signing-Method body is as follows:

ParamDescriptionRequired?
idThe unique ID of the signing method.
valueThe value of the signing method.
physicalDeviceIdThe physicalDeviceId which is the unique ID of the user's device. (Applicable forBIOMETRIC only)
idempotencyKeyA unique UUID for every request.
signature.typeThis will be sha256
signature.valueSHA256 encoded hash of the JSON request body. - from Step#4
{
  id: string,
  value: string,
  idempotencyKey: string,
  signature: {
    type: 'sha256',
    value: string
  }
}

Example Signing-Method body with PIN:

{
   "id":"2bed9491-cdf2-454f-bdfa-716c94cbd922",
   "value":"123456",
   "idempotencyKey": "9ee45da6-193b-4b07-bdd5-92f5602bcf62",
   "signature":{
      "type":"sha256",
      "value":"9ad9900886a75c33c327168ed6b0f8892eeffab26369e74082bc120887653762"
   }
}

Example Signing-Method body with Emergency Code:

{
   "id":"a2ffea97-c199-4685-928d-b34b6f6b2133",
   "value":"UvHSS5SCYek3U2W9h4gEUa9VZ",
   "idempotencyKey": "d06e7754-4c27-4418-8f85-0eb4337a717b",
   "signature":{
      "type":"sha256",
      "value":"54f6e79117c0dc43e1bba7e57303c81da3588703e068f723bb1a7a410c6e0230"
   }
}

Example Signing-Method body with Biometric:

{
   "id":"64c10e41-114a-4ce7-9d02-7df48ba360e6",
   "value":"2f08ae38-b3b9-4857-b46e-3b20e1a936f8",
   "physicalDeviceId": "312b26a0-e6de-4d33-a011-c17cd5fb5e7f",
   "idempotencyKey": "659386e2-4c5a-4c1f-80c7-9e30ff65265e",
   "signature":{
      "type":"sha256",
      "value":"cd0d7f18ca78e1466dd593d88a21deabb80ee42069159e28ddfaa2ac0eb9de5c"
   }
}

6. Prepare Encrypted-Signing-Method Header

6.1 Preparing JSON Body for Encrypted-Signing-Method

Next, prepare the following JSON body.

ParamDescription
encryption.typeThis will be AES/GCM/NoPadding.
encryption.key.encryptedValueThe AES key that is encrypted with RSA-2048 public key (GET /api/security) - fromStep#3
encryption.key.encryptionKeyIdThe value corresponds to the result.encryptionKeys.id param in the response body of the GET /api/security endpoint - fromStep#3
encryption.ivYour randomly generated iv vector (12 bytes). - fromStep#2
valueThe body of the Signing-Method that is encrypted with the raw AES key. - fromStep#5
{
    "encryption":{
        "type":"AES/GCM/NoPadding",
        "key":{
           "encryptedValue": "base64string", 
           "encryptionKeyId": "<uuid>"
        	},
        "iv":"base64string" 
    },
    "value":"base64string"
}

6.2 BASE64 Encode the Body

Next, you have to base64 encode the JSON body which is to be passed in the Encrypted-Signing-Method header.

7. Send the correct header

There are two ways to supply the signing method in the header:

7.1 For Encrypted Transfer (Encrypted-Signing-Method)

You need to send the JSON base64 encoded body in the Encrypted-Signing-Method header from the previous step. The Encrypted-Signing-Method indicates that the signing method and the request body are encrypted.

7.2 For non-encrypted Transfer (Signing-Method)

The Signing-Method header can be passed as usual for non-encrypted transfers.

Parameter

Param Type

Value

Description

Example Value

Signing-Method

Header

id:value

id: This is the ID of the signing method
value: This is the value of the signing method

756ae7a7-3713-43ee-9936-0dff50306488:123456

Encrypted Request Example:

Executing a Transaction

Headers
Encrypted-Signing-Method: <<BASE64 ENCODED JSON BODY from Step#6>>

Body
{
  "transactionRequest": {
    "type" : "TRANSFER",
    "secretType" : "MATIC",
    "walletId": "56137f2d-b4e9-42de-943b-cee828eb222a",
    "to" : "0x8D6331D6C5ba3306961F0b4Ea13ff13aa43560b9",
    "value": 1
  }
}

Encrypted Request Body

This flow applies when you are creating a user, creating a signing method, or updating a signing method via the Wallet API, and you want the signing method value to be transmitted encrypted so that it is never visible on your system.

📘 Note: This uses the same AES/GCM encryption mechanism but the encrypted payload is passed in the request body instead of a header. The SHA256 signature check is not required for this flow.

The steps for generating the AES key, iv vector, and encrypting with the RSA-2048 public key are the same as Steps 1–3 in this guide.


Encrypted Payload Structure

For all three endpoints, the encrypted signing method follows this structure:

ParamDescription
encryption.typeThis will be AES/GCM/NoPadding.
encryption.key.encryptedValueThe AES key encrypted with the RSA-2048 public key from GET /api/security.
encryption.key.encryptionKeyIdThe result.encryptionKeys.id from the GET /api/security response.
encryption.ivYour randomly generated iv vector (12 bytes).
valueThe AES-encrypted, base64-encoded request payload (see per-endpoint details below).
{
    "encryption": {
        "type": "AES/GCM/NoPadding",
        "key": {
            "encryptedValue": "base64string",
            "encryptionKeyId": "<uuid>"
        },
        "iv": "base64string"
    },
    "value": "base64(aesEncrypt(<requestPayload>))"
}

Endpoint: Create User

Reference: POST /api/users

When creating a user with a signing method, pass the encrypted request body.

Plain (non-encrypted) request body:

{
    "reference": "Test User Venly",
    "signingMethod": {
        "type": "PIN",
        "value": "123456"
    }
}

Encrypted request body:

The value field contains the AES-encrypted, base64-encoded creation payload:

// Creation payload (before encryption):
{
    "reference": "Test User Venly",
    "signingMethod": {
        "type": "PIN",
        "value": "123456"
    }
}
// Full request body with encrypted request body:
{
    "encryption": {
        "type": "AES/GCM/NoPadding",
        "key": {
            "encryptedValue": "base64string",
            "encryptionKeyId": "<uuid>"
        },
        "iv": "base64string"
    },
    "value": "base64(aesEncrypt(creationPayload))"
}

Endpoint: Create Signing Method

Reference: POST /api/users/{userId}/signing-methods

  • \{userId\} : The id of the user whose signing method is to be created.

When creating an additional signing method for an existing user, pass the encrypted payload as the request body.

Plain (non-encrypted) request body:

{
  "type": "PIN",
  "value": "123456"
}

Encrypted request body:

The value field contains the AES-encrypted, base64-encoded creation payload:

// Creation payload (before encryption):
{
  "type": "PIN",
  "value": "123456"
}
// Full encrypted request body:
{
  "encryption": {
      "type": "AES/GCM/NoPadding",
      "key": {
          "encryptedValue": "base64string",
          "encryptionKeyId": "<uuid>"
      },
      "iv": "base64string"
  },
  "value": "base64(aesEncrypt(creationPayload))"
}

Endpoint: Update Signing Method

Reference: PUT /api/users/{userId}/signing-methods/{signingMethodId}

  • \{userId\} : The id of the user whose signing method is to be updated.

When updating an existing signing method, pass the encrypted payload directly as the request body.

Plain (non-encrypted) request body:

{
  "value": "654321"
}

Encrypted request body:

The value field contains the AES-encrypted, base64-encoded update payload:

// Update payload (before encryption):
{
  "value": "654321"
}
// Full encrypted request body:
{
  "encryption": {
      "type": "AES/GCM/NoPadding",
      "key": {
          "encryptedValue": "base64string",
          "encryptionKeyId": "<uuid>"
      },
      "iv": "base64string"
  },
  "value": "base64(aesEncrypt(updatePayload))"
}

Supported Signing Method Types

The type field in the creation payload supports all signing method types:

TypeDescription
PINA numeric PIN set by the user.
EMERGENCY_CODEAn alphanumeric emergency recovery code.
BIOMETRICA biometric identifier tied to a physical device.