Skip to content

Add Binance Travel Rule support with PII Data encryption#328

Merged
monsieurleberre merged 2 commits intodevfrom
chore/binance-travel-rule-integration-test
Feb 17, 2026
Merged

Add Binance Travel Rule support with PII Data encryption#328
monsieurleberre merged 2 commits intodevfrom
chore/binance-travel-rule-integration-test

Conversation

@monsieurleberre
Copy link
Copy Markdown
Contributor

This PR is here just to keep a trace of the useless work that was done following
https://developers.fireblocks.com/docs/a-developers-guide-to-constructing-encrypted-pii-messages-for-binance-via-fireblocks

Only to be then told that we shouldn't encrypt the fields of the PII Data...

image

We can easilly revert using this trace if/when they decide to change the implementation

Constructing Encrypted PII Messages for Exchanges via Fireblocks
🚧
Early Access feature
This feature is currently in Early Access. For more information about participating in Early Access, contact your Customer Success Manager.

Introduction
For institutions using Fireblocks to transact with major exchanges like Binance and Bitstamp, correctly constructing and transmitting Personally Identifiable Information (PII) is critical for ensuring regulatory compliance and the successful processing of transactions. Sending PII requires a robust security model that goes beyond standard transport-layer encryption.

This guide provides a practical, compliance-aware walkthrough for developers and compliance officers on how to structure the PII payload, handle the necessary encryption, and correctly format the transaction.extraParameters object in the Fireblocks API. It breaks down each field and explains the specific data requirements based on key global jurisdictions.

For step-by-step instructions on completing Travel Rule compliance through the Fireblocks Console UI, see [Travel Rule compliance for exchange transactions](https://support.fireblocks.io/hc/en-us/articles/20851702645404-Travel-Rule-compliance-for-exchange-transactions).

📘
Note for Compliance Officers
This document details the technical data structure for compliant PII transmission. The core obligation is to ensure your institution has a robust AML/CFT program that includes counterparty due diligence, risk-based transaction monitoring, and adherence to the specific data requirements of all relevant jurisdictions.

Core concept: End-to-end encryption of PII
It is critical to understand that you must never send raw, unencrypted PII in an API call. The piiData object detailed in this guide represents the plaintext data structure that must be encrypted before it is submitted to the Fireblocks API.

Fireblocks documentation indicates that PII encryption is a manual implementation step that developers must handle. This ensures a secure, end-to-end model where only the intended recipient (in this case, the specific exchange entity) can decrypt and access the sensitive user data.

Exchange PII data encryption guide (RSA-only model)
Overview
We only support RSA-only encryption for PII data transmission. Exchanges do not accept hybrid encryption (RSA + AES). Using hybrid encryption will result in failed Travel Rule submissions or empty compliance data.

RSA-only encryption model
This method encrypts the entire PII data payload directly with the recipient's (exchange's) RSA public key, without generating a separate AES key:

Process:

Direct RSA Encryption: Serialize the PII data to JSON and encrypt the whole payload using RSA-OAEP with SHA-256. Each value should be encrypted (see examples below).
Single Encrypted Blob: The result is a single base64-encoded string containing the encrypted PII data.
Send in API Call: Pass this encrypted blob in the piiData field of the Fireblocks transaction request.
Production usage: Encryption & Transaction creation
In a production environment, you must fetch the public key from Fireblocks to encrypt the PII data.

Example: RSA-only encryption utility
TypeScript

import * as forge from 'node-forge';

/**
* EXAMPLE: Encrypts each PII field individually using RSA-only encryption
*
* This is a simplified example showing how you could implement individual field encryption.
* Each developer should customize this approach based on their specific needs.
*
* @param piiData - PII data object to encrypt field by field
* @param publicKey - RSA public key in PEM format
* @returns Promise<any> - Data with each field encrypted individually
*/
export async function encryptPiiFieldsIndividually(piiData: any, publicKey: string): Promise<any> {
 /**
  * Implementation approach:
  * 1. Takes the original piiData object
  * 2. Recursively walks through each field
  * 3. Encrypts each string value separately using RSA encryptWithRSAOnly()
  * 4. Returns the same object structure but with encrypted values
  *
  * Note: This is an EXAMPLE implementation. Developers should customize
  * this logic based on their specific requirements and data structures.
  */
  // Simple recursive function to encrypt string values
 async function encryptPrimitives(obj: any): Promise<any> {
   // Encrypt all primitive values except null/undefined
   if (obj === null || obj === undefined) {
     return obj;
   }
  
   if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean') {
     return await encryptWithRSAOnly(obj, publicKey);
   }
  
   if (Array.isArray(obj)) {
     return Promise.all(obj.map(item => encryptPrimitives(item)));
   }
  
   if (typeof obj === 'object') {
     const result: any = {};
     for (const [key, value] of Object.entries(obj)) {
       result[key] = await encryptPrimitives(value);
     }
     return result;
   }
  
   return obj;
 }

 const result = JSON.parse(JSON.stringify(piiData)); // Deep clone
  if (result.extraParameters?.piiData?.data) {
   result.extraParameters.piiData.data = await encryptStrings(result.extraParameters.piiData.data);
 } else {
   return await encryptStrings(result);
 }
  return result;
}
Example: Creating an exchange Fireblocks transaction with RSA-only encryption
TypeScript

import { FireblocksSDK } from "fireblocks-sdk";
import { encryptWithRSAOnly } from './encryptWithRSAOnly';
import { piiData } from './piiPayload';

const fireblocksSDK = new FireblocksSDK(privateKey, apiKey, baseUrl);

async function getExchangePublicKey() {
  try {
    const credentialsResponse = await fireblocksSDK.getExchangeAccountsCredentialsPublicKey({
      // Specify exchange exchange account ID
    });
    return credentialsResponse.publicKey;
  } catch (error) {
    console.error('Failed to get exchange public key:', error);
    throw error;
  }
}

async function createExchangeTransaction() {
  // 1. Get exchange's public key from Fireblocks
  const publicKey = await getExchangePublicKey();

  // 2. RSA-only encrypt each PII field individually
  const encryptedPiiData = await encryptPiiFieldsIndividually(piiData, publicKey);

  // 3. Build the Fireblocks transaction request
  const transactionRequest = {
    operation: "TRANSFER",
    source: { type: "VAULT_ACCOUNT", id: "1" },
    destination: { type: "EXCHANGE_ACCOUNT", id: "exchange_account_id" },
    amount: "100",
    assetId: "BTC",
    extraParameters: {
      piiData: encryptedPiiData
    }
  };

  // 4. Send the transaction
  const result = await fireblocksSDK.createTransaction(transactionRequest);
  console.log('Exchange transaction created:', result.id);
  return result;
}

createExchangeTransaction()
  .then(() => console.log("PII transaction sent successfully to exchange."))
  .catch(console.error);
Real-world PII data example 1: Binance - Compact French withdrawal scenario
This example demonstrates a common real-world scenario where minimal required information is provided for a withdrawal.

Transaction Type: Withdrawal
Jurisdiction: France (FR)
Relationship: ThirdParty
Entity: Individual
Scenario: A user is making a withdrawal from their exchange account to another beneficiary, where the user is an individual located in France.

Example: Before encryption (Compact PII data)
JSON

{
  "extraParameters": {
    "piiData": {
      "type": "exchange-service-travel-rule",
      "typeVersion": "1.0.0",
      "data": {
        "beneficiary": {
          "participantRelationshipType": "ThirdParty",
          "entityType": "Individual",
          "names": [
            {
              "primaryName": "John",
              "nameType": "Latin",
              "secondaryName": "Doe"
            }
          ],
          "postalAddress": {
            "country": "BE"
          }
        },
        "beneficiaryVASP": {
          "vaspCode": "BINANCE"
        },
        "transactionData": {
          "withdraw": {
            "isAddressVerified": true
          }
        },
        "originatingVASP": {
          "vaspCountry": "FR"
        }
      }
    }
  }
}
Example: After encryption (Compact encrypted data)
JSON

{
  "extraParameters": {
    "piiData": {
      "type": "exchange-service-travel-rule",
      "typeVersion": "1.0.0",
      "data": {
        "beneficiary": {
          "participantRelationshipType": "DkGFzfPCLi3SNl6BrrS1C4X/DmuqfVvU83iczY+pDgamI3FUTmzghoGvtjERSH6uqKo3h9gwAWQN8mYV7PGdgkV7cnSS6/H4CNJWV7BlN63RU7DmsmVdHoGbA0F7GnriQvKlnTFPU/CCXJDcsaXG7cHa5VtST8rw+acw73fnUpwUnGMLnw0QOUoYrvu6eGo+BX1aZu+Ve0hrhJTcYXXfnvekkXWCLxDu8AYltJSy4YgzsFNvHb3JyOOuPvJg3qka2NfVrzRXv+7pQy6c4eyErfBJZ6T8i/WXsu9gVZXuOC24wtZi99pP2XGz9ogW2hv9edmEUTitZXneWm26sbtwN9jBPVh4ZRLfGg9wxV1fN/g6MPVbil7Ilu6olO9gRlPj2GUyFIfNPFAziqGEw7CEFvPlQuLv5n2/Pbh98jS/sGANwjKZhXBKyy79WLs/HDVZw6E99+Vxf786HdmIc4MEyj+AELhqcdp8uF0D1/xA+hDFiDpcZjV3JASX9b8EPOaxix9P7TdPepoFoZo4fJFQtaXvAunnsYiG2bE/NareEjLZvqcHadeaCdy29sXCmxeaj1v5hL5GZJFFkBEUa9QLcle0YXQQa3ukkc1W0tChIeGwSEY/qNW/FZ5jg3Pqm8gpdwGzd0gZk/JO8YDIthtTRdgm7zjydIxVZGChTO/h4WM=",
          "entityType": "CvnHs/CzrfdTpGukJXmJH+88n2cW9C0Ezc6HtJtDzN6+e7dk+GoGrK/ia9Kptncrh7Q+6AVHR3pmrJFH10Y8kwxLoNOmJkPtNDJLoVPDSxAn4So7Jw0mA+OGXu8J1e/b1v9u4g3/2L5373MURnsrMydU0vt77YaBZgmZo0dtukLE4kGTnW2lUKexdeRs6NR99r+VQNYG38XGi2/OAjwvBPIAxfmaXBoNMCVda0JZkjlweezwIGL8theV+RMPtQBm8mUsBpbcIiBLAFA4/pQgCKVHZuUMkfaSXh6zPBga6hQu4N0fhLxTj1VwW1Mocd373ifUAn5mziMB4tunObI6jy91s0zunmIajZeHqxIvxci1sMEI6RXnuzKfbkadUzM9cyUMjnzkrQk5q5VuurxfZRqFHuw9Ikq8dNUavAetwgJipUi2aTVh0+uVgZ84QKusJ08LqD3N4k6eghZwIDUkCFXNZnK1reK+oIqGcbs8y0r+Vmr1zh5GukA55+yqRJXu/YxAlS3HIwp+HnhSvM8Wz1O5/PgCHm2iRcP3+pCYWJltkPoKPzynurAN6I1tGSak7+AiPwiJsd5sqdNylR5eFR4N28U61vYhk4nebsvvI5J+7M9+f25hs4hDloV+bmj0wzN3QnCRS3RyaPg91fdCo2iTR5PTuLvjVJ0GX/h1674=",
          "names": [
            {
              "primaryName": "SpDXvmGFXH3qe0jAVZeinBniH18VBPsAqnLz+SxidNd9Hh6XKbs10cHMPoda4Yga3dDLXSZTRMb9hJPbsYlanFgWpcIW6rq4KGeK/Q9LiN1gVSGwZrXMD2Jx/3+hPVcSUqYqGiirZpoeQqJUTzmJvglRCWDDNbE88jFcQ+XEvj0v9tC1rrHrxrowe1QaxFeOt8HMl2PjGVzUGCD9QdRsG0DbBa4To49EsvXl6qCMr11pioCWzAVgYxRJU5HlfikbsU00dxXTVVaHAFGfGgeTSBBVpNLTvUuk7Q4CW4TIa9PiDYiZsT9KW3X0tjXLs0xflrUXMXUWDC3bVfEMcFtM75YyOFrMY4IhGfyND6o+4MyeqC7LmYc09o9wmheiRS/l3J77SNc8w7tlqoSgvVVb45TDpBtf611SQ8GnCQ9rTg9rKROwPXA58micxFKrgVVYfY+E0uo90kzs/Hfw0OohrC/3vS6d3ocXAEp8FEtmCvyRxK+xgTv3obIzL6eVqI2TBDkkyjT3gGBrbS0a8GPZSKUw9W0cNnYjxZ/rxF1xBUG1gg+cE7St2yNwQ9m3gcneoB8U3AA5y/f5Zw7iz5ekAmMdrUFWf5ENNodNNv66KVNQLwpSIWeHiVkr5mqEIooAJDaFDaYKw6A2GRJzU+ZQ7ca3rOumleUq6kXfQcCmpr4=",
              "nameType": "UFvT9EbXqfGJGH3UPsXtRbwyi9VDb3JgEyGXDM6x4Pkk/R8nlfEVnKFSmt0LSU1Vnzl/k8pjMsLcap33RDENMWTRAfjasnqDK7uZCNO8m37SBZiTLmTYrX3k5V4hF0p6SQqvltPV4cye4AwALxZKtc95jzsfJUxgylaXbmuR8+W8RoHZrhid4YCFmarpC8xCz0fcrWrbRtfV/wuv4wHotkX7SQJj0pf31xsRjfDYIV6Fo0tlnhI1JTdVh+ztE9e76OcRdlpM2Fk7UM42+CXUSvvO3iP4E1ot5K7O+u+3a8pcVWNbAIycLlz79fz2LFXiQ7jc63Yg/sG/P2yQqk/ivDT/ZWjSoCNlB8oi7VDpL2ir0Xwh1+D6I9n6f1G4jc0Fkqr0DP00P93jpmBQzqPi2V7TBrnIwxPnQeEzj1SXE0ENMbj2P/PGeinGW34h6/0WFKyyOubMqLnvG5lPUusFPA7NFY7NdS/1OAU/y4J52R9YFWkW9o2jdn9G3iLbjNKx8g0yRbnT0sTXql9E+VCv2HPYS1Wuoqo/S1LhG6j0y9ugUCS1+advbxHm0/4ZD5zJ4+5FHIlH8s5+CJ7/FGFsRRN/R2jAW9GSqdzczzYm4X24l8cn4xHRWR2XO0jqlYmoH8sBMrZVgfvJnDy4KotlthZT5ooIOQJY+58x6gXnUL0=",
              "secondaryName": "KjXjKySekOb13IvuP4ainsMzGCmxK9CUeVI6VMLp3MYxz1zFbZBq95v8Ly5TBoH+RL176yioR5o4l0NHEgL8+vjjSeau/o9cHRZK16VjrGmF2MmpuFeD4NPN9IAsD8M+xeq2X06EK5ns9ebuGCADAiEGIEbBfnjZcJpUhnlllTptbuzBoeaK/k1545XMA30f6SuR+6hGxDBX6rX+vYJOvEBhSfK9km1mJ2y89it38QY2eRcDD8Li4nuLPaKrqzWGcN+Lp8KrII604D9ewUj/a9LfefrZBB5H+gL0XI2h7rVfiYNBJghXakfunS595nBNTjbxz5spuDxE7k0EqhW6t2N70FzyNVUjmxo0nFCSRViONXuiyhddZfRcIeTXJf1hXp0/cujqvOkbdrl2yPXl2pZ0/XZaCHfx76unLaLHUcFKCVpib5FXi5i7C6Gd+8QuBliUiUAdWUJGpcqQLkz7/zALdZLqISZ5WLyKENIt/Rdo/GzQ35CZVCe9TtIIIEHq0wmMArCB73KFzMrrJStefPA4tNtbWM4iwPmahoSWFYl96pIclGNdRdFNTF1jM4VgfboB27YG/6YFJMcWYJvXR5gkbeYUgRRe/q/B7iFvGKdNIrCe34iCGEZ8/hNs+ljBFmcxEbOiFjoAg3sOe3WKnpLaI5jaFyel4Jar7XkKAxg="
            }
          ],
          "postalAddress": {
            "country": "o9MZlFzz76bKfw3giyekwbhpZo3Nn1+o/NTzFQnSWaozIKa9xoAiOE8IvmU9tkT3yZLYOeaX6F6Am2hyzpers9lMHr2Ha1pOFnreQUKE9x2fEFZyAXv625Hz8Ja+ZeywlkbAok1atpc7+JC8GD7quErQwC/qgZ8yzW926JyQ38sZPHY+W0IM2sc7S0KoL77Yu5+gixQPbiUdMfYjimuRRwW+4NzsSOWUcZdQ+NB/khz/7CDc9q6tqYFuHg17sFo6QO2rQSpcgfzDrqmdCX0ZnSRBf5FZG2I0tHA2t24UuRJJGNIU66jr9b4h2pJFWpE+UwSmx8skyI+0DCk4P8gxhS6ZhQ93TGpSgpI5rWW7pJLLzlmEeVF5bm7zYQg2eu0HTUi3DEyUHfW0g/LpGEfLCudwSXRjGUUXI7tSV6BxC4EY7T5pt6RkNrnTdjSo8B4azdv06BqVjYzeJFwXL7Zo4khoaQETQf3PiTlwMQw/WQox/65IfpKfM1KhHtPFCiG1v358q2FO/S0yV6hcKNVRNfrZXG5v32DDZ8S/K8Cs0+XBEBl8OxHo581uahVKONzb2VwBGTxcXouWHVLEq928wDKnhAPvUmFMkHtkpmdSxOWr0qoMUD9WQFvkYqm9RXeczoN/RTsAqMSr1InoqfmqU9TZBCdjiMDnhe4ggAvFKkU="
          }
        },
        "beneficiaryVASP": {
          "vaspCode": "VTiePIZHqIn6q17/SWZ8fdpWga72UvaVjvwBs2PQ/R5DyN6GwDOC6msAFr3+1h/14PwFfawmWXpWu6xmKm7iyUHpMmmnnPlUKSjlNeb+k2QVTHPXYT6eboT3V8gav47mIBfv+XMm2AtXfLRGpWF5or6Q929QqxXgOOl89xwdYxRFnboSB/T9coZ2lonf15F/n+8XEMmE/g77iGwlY5pIi7VmAhi0MIJthRgxIXbuB5yzL1MtaPQr5a4tTY9J0WR6MlMjkeGODV0VyVX90aajDgTv4ocB0TLwGkC4GRTLBsdyGD3jkpp0Lion5ip+BT0MhYg2xvshef/GJ4cob2bZy+10GAdWUbid2n66HQUJjIyPuPtAZLzf/hZiL5dsgykRz/FpT291eoTeQ0V6RIYLBGSqSPkc+n56Pqum0c47Oan5078R9/k5YLpTX9DD464m5pHSPPh/sg5TeBydd31bHJy0EfwGMJPCK4yjaTlK2At6vLzhJ+BZ6au8oWoDvbCbS2bh1jJh5x/V/4HAkv/h7woGb3OY+z/GazfFEhCBAC4WdvGExHqbQhbhcOlbZr3Wt6b3Vx1U06u+acwLUwt3P9yuufAvQkjxYp60nSKKJxjj0Vnk/Qjf1vCXab8y7siEXthVO1MqTsozGtgCqzHw8aNtg7rEAykSd8AHyyjRqz8="
        },
        "transactionData": {
          "withdraw": {
            "isAddressVerified": "g6y8yN5t+5GrJfFkhqrOz5dfJjTRiIO2qYH+OXsRNaTrtoJjPwda7OOnAmuSp2yBdRFTPKxgcWzBo5fofQvOyl64RdrWmtKFkWBDAtJUzoIE5s6yk5qp+wi5+gNdo4bHmrKaJXaOsaKrO+o+kfg0ySt8K1qrsZK7AFa+yqwabUmiDwRdElromyDpACv2n2ol/1W2Oh87nAHIW8fUWsEiuI8nM83X5Do6ZlsUGjTXRhIVVuu4Yd9Jkeld1OSe2iSETFb2XEy0ml5viU5H31AJ1RQaNCvBVpzu/1fe6YgB05MQjTiL01/kn2cT3PlZ5WaKZxXQuuoNAR0y5eeJPiwfdBCCrL7Z8KJpsCRspRBXxXoUFkN56XUfBHaRwoeA4flSLfpmfKYSNcATUr13sd/fH7n6wk6mU8Zl0CJJkoWQaUCk0cM54wxWGeRtgSqPAteBsAw2olBpRrK5fIvi5YWd7ez88CtlIZwfsAI7WaMtZMTTlC8E9BdRhJVjX4neadpVfNYoiVPLiEzfo9gK5jqyeuFgYG1fAhWYvKVk+rw2Csn5iqO/AXhkEQht1riw4BnrfymFGJY8+9hrz+KTUZndG1Xw5GbFlEuitqg9yRGCUdhJVALoSeJ71YCMFIWPoq/3IRogFTqEBtzkP4N9ohUBlZVxPXZhZFc+Axe6SsaH+Wo="
          }
        },
        "originatingVASP": {
          "vaspCountry": "ppyFQ37QrH2GUityQet2+PMZdkqbgxv3tM7hiokv0TLUjZikj6TT/8jaDXumx2itIDKYr+N7zhvPtM9cm9mMWTjo2D7m3LOPNXu0uRPOCNWOQAACOlx21rC5Sc+yUazLq7e8kyi2wlF3ngH9DIjlVeQ6aGZk5q6HEWm4pvuqOaLA6h4qLsLB5VLg4ygEOw0Lqmc04Y5pKRkrB8f4MrAQtzUjwaQabUXheeAncakIKlRGYfOWYnyqKjJ+X7WD2dK95nNHahORltLeZkPVpHz7ERNqlDtRWlplbBT08PZ0pv3IVWxkFDPc1UB5NVGl0PhkdLSmCLvQe8DwmeBMZzC8o0Ks+vLlmJZbcbjYFmosaH5ZlbJh01Azs1xLaYqhNF/NwFSbYqL5vm7GsKS6wXgKCDJkikyIXG1UavKhL2HdrOIXOv07E/ox0pH7Sn8rs3p6aVM5s0fQKV5nRaM9s5ixbC3VAX5uVKG0mBSu9G//Q/RrjUXCdcaKD4lKw9hzmX1linKJCnCjEjwaDpRtPXledumNFt7CWn+dcvjG7xhOro/EeTpTcT1IqcPw+D0NnZLddNyx/xrJC5YMU3yZ7tKHLsvVZyotC6UTTjBQJUCLt0rpEvd7stZtpp/Nn5i1KeSwYbXIaEMthNZQaO63gMi8od/R0LCcYeEHInZ5qYhD1n0="
        }
      }
    }
  }
}

Copy link
Copy Markdown
Contributor Author

@monsieurleberre monsieurleberre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sub production level, OK, and breaking the build, but this is just a trace, and next commit will remove all the 💩

@monsieurleberre monsieurleberre merged commit 9ca4bf3 into dev Feb 17, 2026
4 of 6 checks passed
@monsieurleberre monsieurleberre deleted the chore/binance-travel-rule-integration-test branch February 17, 2026 12:10
monsieurleberre added a commit that referenced this pull request Feb 18, 2026
* Add Binance Travel Rule support with PII encryption (#328)

This is sub production level, OK, and breaking the build, but this is just a trace, and next commit will remove all the 💩

* refactor: remove RSA encryption from Binance travel rule (#329)

* refactor: remove RSA encryption from Binance travel rule

* Unbreak the build 🤞

* OK this time we fix the build 🙈

* revert ExchangeAccountsClientTests to 5cddfcf

* Chore/just put the encryption back the fun never ends (#331)

* add Binance Travel Rule support with PII encryption

* refactor: remove beneficiaryVASP and consolidate travel rule tests

* Sorry I missed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant