Encrypt Decrypt KYC Data

Now that we have TA Onboarding, Attestations and Discovery set up we can look at how VASPs on the network can safely share KYC information relating to crypto transactions between their respective platforms.

Below is an encryption/decryption scheme that will allow VASPs to encrypt KYC information and send to the associated VASP whereby the VASPs can decrypt the KYC information and ultimately complete the FAFT Travel Rule requirement and reporting obligations.

Let's assume the following:

  1. The VASP that set the attestation is called VASP-Sender.
  2. The VASP that decoded the attestation and recognized the attestation was to a crypto deposit address on their platform is called VASP-Beneficiary.
  3. Both VASPs have been onboarded and verified on the network.
  4. Both VASPs have completed their respective Discovery Layer setup (domain name and IPv4 addresses)

Below is a JSON template that will be ping/ponged back and forth between these two VASPs.

var kycTemplate = {
AttestationHash: '',
BeneficiaryTAAddress: '',
BeneficiaryTAPublicKey: '',
BeneficiaryUserAddress: '',
BeneficiaryUserPublicKey: '',
BeneficiaryTASignatureHash: '',
BeneficiaryTASignature: {},
CryptoAddressType: '',
CryptoAddress: '',
CryptoPublicKey: '',
CryptoSignatureHash: '',
CryptoSignature: {},
SenderTAAddress: '',
SenderTAPublicKey: '',
SenderUserAddress: '',
SenderUserPublicKey: '',
SenderTASignatureHash: '',
SenderTASignature: {},
BeneficiaryKYC: '',
SenderKYC: ''
};

The goal of this ping/pong communication between these two VASPs is to complete the template.

NOTE: The ping/pong communication is over HTTPs to unique public apis available on each VASP platform; i.e. listed in the discovery layer contract.

Recall when a VASP sets an attestation to the network, the attestation contains the TA Account, User Account and meta data in the attestation; ie the crypto type and address.

Other VASPs on the network, by subcribing to Attestation Events, can decode the attestation to discover the TA Account, User Account and the crypto address.

Let's assume VASP-Sender is the TA that sets the attestation and VASP-Beneficiary decodes the attestation.

When the VASP-Beneficiary decodes the attestation and determines there is an association between one of their users and the crypto deposit address, they can begin completing the kycTemplate above.

The first thing to do is dynamically look up the VASP-Sender discovery information to determine the domain name for the HTTPS ping/pong communication.

NOTE: the code examples below utilize the following private and public accounts.

// VASP-Sender
var TASenderPrivateKey = '0x8f248586a00b7161644d928415a32b4464c36be766de982ba8f8023b1acdda14';
var TASenderAccount = '0x6C6C1a25b0Aeec93EB458023fd58fbEfC96CdDFa';
var TASenderUserPrivateKey = '0xfba556045b439f709efc0abecb8bcf7bdd661b2186fbbdfd4db157aa451936f5';
var TASenderUserAccount = '0xdA67F7EB772f709b626469ec9055d081f451C118';
// VASP-Beneficiary
var TABeneficiaryPrivateKey = '0x455bd988c72fbd91a42043f02dd07d720e59044127510a47eac694c7933a5af7';
var TABeneficiaryAccount = '0xbC11D726e5A6081d940eF452D5e50Be0623BB24c';
var TABeneficiaryUserPrivateKey = '0x8f6f6e45f967bb839d5d010dcd75959ddfff90c88ae802625473a47bf63df570';
var TABeneficiaryUserAccount = '0x96591Ba6F9C06493Cd3A65fF1FA0a63534bF1052';
var CryptoAddress = '0x540Dc0B550e91D4CE34E37d5b555B8E2Eb2Ec62a';
var CryptoPrivateKey = '0x462780f9f8b3ebc228d8a4c60950fc756a53483653a4690027ab30ee58fce15d';
var AttestationHash = '0x242c7ff825cb8df155fbbc83362600188c3027b5f93af9d7a92dd6ed1b3cc8d1';

NOTE: the code examples below utilize these helper functions

const EthCrypto = require('eth-crypto');
const ecies = require("eth-ecies");
const EthUtil = require('ethereumjs-util');
function bufferToHex(buffer) {
var result = EthUtil.bufferToHex(buffer);
return result;
}
function getEthPublicKey(_privateKey) {
let privateKey = _privateKey;
if (Buffer.isBuffer(_privateKey)) {
privateKey = "0x" + _privateKey.toString('hex');
}
const publicKey = EthCrypto.publicKeyByPrivateKey(
privateKey
);
return publicKey;
}
function getEthAddressFromPublicKey(_publicKey) {
const publicAddress = EthCrypto.publicKey.toAddress(_publicKey);
return publicAddress;
}
function encryptData(publicKey, data) {
let userPublicKey = new Buffer(publicKey, 'hex');
let bufferData = new Buffer(data);
let encryptedData = ecies.encrypt(userPublicKey, bufferData);
return encryptedData.toString('base64')
}
function decryptData(privateKey, data) {
let userPrivateKey = new Buffer(privateKey, 'hex');
let bufferEncryptedData = new Buffer(data, 'base64');
let decryptedData = ecies.decrypt(userPrivateKey, bufferEncryptedData);
return decryptedData.toString('utf8');
}

Step 1 - VASP-Beneficiary Begins Completing the KYC Template

From the attestation and discovery layer, VASP-Beneficiary can add a number of fields in the template as shown here:

kycTemplate['AttestationHash'] = AttestationHash;
kycTemplate['BeneficiaryTAAddress'] = TABeneficiaryAccount;
kycTemplate['BeneficiaryTAPublicKey'] = getEthPublicKey(TABeneficiaryPrivateKey);
kycTemplate['BeneficiaryUserAddress'] = TABeneficiaryUserAccount;
kycTemplate['BeneficiaryUserPublicKey'] = getEthPublicKey(TABeneficiaryUserPrivateKey);
kycTemplate['CryptoAddressType'] = 'ETH';
kycTemplate['CryptoAddress'] = CryptoAddress;
kycTemplate['CryptoPublicKey'] = getEthPublicKey(CryptoPrivateKey);
kycTemplate['SenderTAAddress'] = TASenderAccount;
kycTemplate['SenderUserAddress'] = TASenderUserAccount;

NOTE: see here portions of the template completed from Step 1

{
AttestationHash: '0x242c7ff825cb8df155fbbc83362600188c3027b5f93af9d7a92dd6ed1b3cc8d1',
BeneficiaryTAAddress: '0xbC11D726e5A6081d940eF452D5e50Be0623BB24c',
BeneficiaryTAPublicKey: '0730e063e946848bea3ae6334fee192085fc3859b8116719e818a1e4a2f0341c22461ddd66e98fe5bd405d1cff35be335a3bdd366ed1523c42d4e73f86250f30',
BeneficiaryUserAddress: '0x96591Ba6F9C06493Cd3A65fF1FA0a63534bF1052',
BeneficiaryUserPublicKey: '3e2717083338cbd7a04fa9c8a0c778bddd7bedbd6807a53faf965d9eccbcb81e52138fea7bee013e4c40f3da2f609a85e262a8af90d1fad95654b45d14b2ff6d',
BeneficiaryTASignatureHash: '',
BeneficiaryTASignature: {},
CryptoAddressType: 'ETH',
CryptoAddress: '0x540Dc0B550e91D4CE34E37d5b555B8E2Eb2Ec62a',
CryptoPublicKey: '0d186b18b3a5867fe3b023c8977a870124ec9521213a66c2b5eedfafcaa39c9e68ede69bd3a8f31f2b2a9c680bcc8a50d53e22f5820cb35f725a086e2816e817',
CryptoSignatureHash: '',
CryptoSignature: {},
SenderTAAddress: '0x6C6C1a25b0Aeec93EB458023fd58fbEfC96CdDFa',
SenderTAPublicKey: '',
SenderUserAddress: '0xdA67F7EB772f709b626469ec9055d081f451C118',
SenderUserPublicKey: '',
SenderTASignatureHash: '',
SenderTASignature: {},
BeneficiaryKYC: '',
SenderKYC: ''
}

Step 2 - VASP-Beneficiary Creates a Beneficiary Signature of the Message with their TA Private Key and includes them in the KYC Template

function TASign(messageJSON, privateKey) {
var messageBuffer = new Buffer(JSON.stringify(messageJSON));
var hash = EthUtil.hashPersonalMessage(messageBuffer);
var ecprivkey = Buffer.from(privateKey.substr(2),'hex');
var result = EthUtil.ecsign(hash, ecprivkey, 1);
kycTemplate['BeneficiarySignatureHash'] = bufferToHex(hash);
kycTemplate['BeneficiarySignature'] = {r: bufferToHex(result['r']),s: bufferToHex(result['s']),v: bufferToHex(result['v'])};
}
TASign(kycTemplate, TABeneficiaryPrivateKey);

Step 2.1 - VASP-Beneficiary Creates a Crypto Signature of the Message With the Crypto Private Key and includes them in the KYC Template

function TASign(messageJSON, privateKey) {
var messageBuffer = new Buffer(JSON.stringify(messageJSON));
var hash = EthUtil.hashPersonalMessage(messageBuffer);
var ecprivkey = Buffer.from(privateKey.substr(2),'hex');
var result = EthUtil.ecsign(hash, ecprivkey, 1);
kycTemplate['CryptoSignatureHash'] = bufferToHex(hash);
kycTemplate['CryptoSignature'] = {r: bufferToHex(result['r']),s: bufferToHex(result['s']),v: bufferToHex(result['v'])};
}
TASign(kycTemplate, CryptoPrivateKey);

NOTE: see here portions of the template completed from Step 2

BeneficiaryTASignatureHash: '0x144fccf1ea97612bca15c182c727361d8a8531c965eace83a7769a637b6bb0c8',
BeneficiaryTASignature: {
r: '0xfaa0e1a81b79d2bac8cfffb24d72e40c5661babbaad8471a632107e8eb9e3d4e',
s: '0x7c3de725071204e5810ed612456e09d9fdb620e59276a093367bf8a396dfd8fd',
v: '0x25'
},
CryptoSignatureHash: '0x1fe8a71478c508d45604e6b7462d816c7dac4461ab0998652eacc2cac5ea3e45',
CryptoSignature: {
r: '0xe487a2675f3d0c17a722f1e92ad80aa8d782f0d09f4edadcb712ca7dc2e1c623',
s: '0x26d660e2737db3400a239c25b6ef856e911de6433f856c00a683dffc2937284c',
v: '0x25'
},

Step 3 - VASP-Beneficiary POSTs the KYC Template to the VASP-Sender

Once the VASP-Sender receives the incomplete KYC Template, they can confirm the contents.

  1. The Beneficiary Public Key
  2. The Beneficiary Account from the Public Key
  3. The Crypto Public Key
  4. The Crypto Address from the Public Key
  5. Confirm the TA account has be verified on the network
  6. Confirm the IPv4 address in the discovery layer matches the incoming IPv4 addresss from the POST HTTPs connection

Step 4 - VASP-Sender Confirms the Public Key from 1. and 2. above

function TARecover(template, TYPE) {
var message;
var signature;
if(TYPE === 'Beneficiary') {
message = template['BeneficiaryTASignatureHash'];
signature = template['BeneficiaryTASignature'];
}
else if(TYPE === 'Crypto') {
message = template['CryptoSignatureHash'];
signature = template['CryptoSignature'];
}
var messageHash = Buffer.from(message.substr(2),'hex');
result = EthUtil.ecrecover(messageHash, signature['v'], signature['r'], signature['s'], 1);
var pubkeyString = bufferToHex(result).substr(2);
// compare public key from signature with public key in message
if (pubkeyString === template['BeneficiaryTAPublicKey']) {
console.log('beneficiary publicKey: found match');
}
else if (pubkeyString === template['CryptoPublicKey']) {
console.log('crypto publicKey: found match');
}
else {
console.log('publicKey: no match');
exit();
}
var address = getEthAddressFromPublicKey(pubkeyString);
if (address === template['BeneficiaryTAAddress']) {
console.log('beneficiary address: found match');
}
else if (address === template['CryptoAddress']) {
console.log('crypto address: found match');
}
else {
console.log('address: no match');
exit();
}
}
TARecover(kycTemplate, 'Beneficiary');
TARecover(kycTemplate, 'Crypto');
> beneficiary publicKey: found match
> beneficiary address: found match
> crypto publicKey: found match
> crypto address: found match

Now that the Beneficiary/Crypto Public Key and Address have been validated, along with confirming the TA has been verfied and the IPv4 address confirmed on the discovery layer, the VASP-Sender can continue to complete the KYC template.

Step 5 - VASP-Sender adding Signature Message to the KYC Template

kycTemplate['SenderTAPublicKey'] = getEthPublicKey(TASenderPrivateKey);
kycTemplate['SenderUserPublicKey'] = getEthPublicKey(TASenderUserPrivateKey);
function TASign(messageJSON, privateKey) {
var messageBuffer = new Buffer(JSON.stringify(messageJSON));
var hash = EthUtil.hashPersonalMessage(messageBuffer);
var ecprivkey = Buffer.from(privateKey.substr(2),'hex');
var result = EthUtil.ecsign(hash, ecprivkey, 1);
kycTemplate['SenderTASignatureHash'] = bufferToHex(hash);
kycTemplate['SenderTASignature'] = {r: bufferToHex(result['r']),s: bufferToHex(result['s']),v: bufferToHex(result['v'])};
}
TASign(kycTemplate, TASenderPrivateKey);

NOTE: Here is the KYC Template completed by VASP-Sender in Step 5

{
AttestationHash: '0x242c7ff825cb8df155fbbc83362600188c3027b5f93af9d7a92dd6ed1b3cc8d1',
BeneficiaryTAAddress: '0xbC11D726e5A6081d940eF452D5e50Be0623BB24c',
BeneficiaryTAPublicKey: '0730e063e946848bea3ae6334fee192085fc3859b8116719e818a1e4a2f0341c22461ddd66e98fe5bd405d1cff35be335a3bdd366ed1523c42d4e73f86250f30',
BeneficiaryUserAddress: '0x96591Ba6F9C06493Cd3A65fF1FA0a63534bF1052',
BeneficiaryUserPublicKey: '3e2717083338cbd7a04fa9c8a0c778bddd7bedbd6807a53faf965d9eccbcb81e52138fea7bee013e4c40f3da2f609a85e262a8af90d1fad95654b45d14b2ff6d',
BeneficiaryTASignatureHash: '0x144fccf1ea97612bca15c182c727361d8a8531c965eace83a7769a637b6bb0c8',
BeneficiaryTASignature: {
r: '0xfaa0e1a81b79d2bac8cfffb24d72e40c5661babbaad8471a632107e8eb9e3d4e',
s: '0x7c3de725071204e5810ed612456e09d9fdb620e59276a093367bf8a396dfd8fd',
v: '0x25'
},
CryptoAddressType: 'ETH',
CryptoAddress: '0x540Dc0B550e91D4CE34E37d5b555B8E2Eb2Ec62a',
CryptoPublicKey: '0d186b18b3a5867fe3b023c8977a870124ec9521213a66c2b5eedfafcaa39c9e68ede69bd3a8f31f2b2a9c680bcc8a50d53e22f5820cb35f725a086e2816e817',
CryptoSignatureHash: '0x1fe8a71478c508d45604e6b7462d816c7dac4461ab0998652eacc2cac5ea3e45',
CryptoSignature: {
r: '0xe487a2675f3d0c17a722f1e92ad80aa8d782f0d09f4edadcb712ca7dc2e1c623',
s: '0x26d660e2737db3400a239c25b6ef856e911de6433f856c00a683dffc2937284c',
v: '0x25'
},
SenderTAAddress: '0x6C6C1a25b0Aeec93EB458023fd58fbEfC96CdDFa',
SenderTAPublicKey: '518bbe71ef9bd9b6feb10a32efe0c9beb34d1edb5ad9b0f5a24495f63b134c5587dd72118c4ab43f08fde52328bb041b9871a3500eb0f7a975351211b5b1c76f',
SenderUserAddress: '0xdA67F7EB772f709b626469ec9055d081f451C118',
SenderUserPublicKey: '8870c2e326b9b7ad0fad7c58475e5d29867debaf0f8a0de7d6a24266c258a3e9d70977250325bf15bcbddaa3c5d8f320a7b93ad99801cd9b64c6f841f1be87f1',
SenderTASignatureHash: '0x55dc803c0c3c476575a4f8f19a9dc98e571f175a8def344e86176d014ea05792',
SenderTASignature: {
r: '0x81d3c1eb66b8748f4c559902b081dbd4fd3849ef4d7f6a09835032e5d393a086',
s: '0x7ec16befa623611663ed155416e1452925b97c8eb3eb7b6b1b416dfb13514543',
v: '0x26'
},
BeneficiaryKYC: '',
SenderKYC: ''
}

Step 6 - VASP-Sender sends a POST request over HTTPs to the VASP-Beneficiary API, posting the KYC Template

Once the VASP-Beneficiary receives the KYC template they can confirm the following (similar to verifying the VASP-Beneficiary Data).

  1. The VASP-Sender Public Key
  2. The VASP-Sender Account from the Public Key
  3. Confirm the TA account has be verified on the network
  4. Confirm the IPv4 address in the discovery layer matches the incoming IPv4 addresss from the POST HTTPs connection
function TARecover(template) {
var message = template['SenderSignatureHash'];
var signature = template['SenderSignature'];
var messageHash = Buffer.from(message.substr(2),'hex');
result = EthUtil.ecrecover(messageHash, signature['v'], signature['r'], signature['s'], 1);
var pubkeyString = bufferToHex(result).substr(2);
// compare public key from signature with public key in message
if (pubkeyString === template['SenderTAPublicKey']) {
console.log('sender publicKey: found match');
}
else {
console.log('public key: no match');
}
var address = getEthAddressFromPublicKey(pubkeyString);
if (address === template['SenderTAAddress']) {
console.log('sender address: found match');
}
else {
console.log('address: no match');
}
}
TARecover(kycTemplate);
> sender publicKey: found match
> sender address: found match

Step 7 - VASP-Beneficiary Encrypts Beneficiary KYC Data

Now that the VASP-Beneficiary has confirmed VASP-Sender data, they can encrypt the Beneficiary KYC data and include it in the KYC Template.

Note: The BeneficiaryKYC data is encrypted with the Sender User Public Key found in the kycTemplate.

var kycJSON = {fullname: 'John Smith', dob: new Date('October 15, 1996 05:35:32').toString(), jurisdiction: 'United States'};
var kycData = JSON.stringify(kycJSON);
var kycEncrypt = encryptData(kycTemplate['SenderUserPublicKey'], kycData);
kycTemplate['BeneficiaryKYC'] = kycEncrypt;

Note: Here is the BeneficiaryKYC data encrypted

BeneficiaryKYC: 'djBxUSMLKEj7ZzcnE42gYwS7DcMRm4u4Ts1E+GE8VS4aKxQa128IyyQOVBpqJh9fYQIq7AMQM4WhWRQ2NUC3VKTlQCc5atXB79+CDxHeqJpqi2AFFsE8PgeR4kj0VLTfv1U2nRq+r1kkQSCLIkHQiR/QOZuJh3Cds6+RSv61RDSc5WLv75Gh5jZzjTtsC6krXi8D3VWdMqmUYLJBG7fk++jUbqyIrK6syc/olo8BvkvgoOsTzsbKqY51DKoZgfY9r5C7KmySrB0V9oOr8HlJxbvvySdqi11yFa/vuxCHXRERcU9NXmpAkoJIEQOvVZyFqw==',

Step 8 - VASP-Beneficiary POSTs the KYC Template to the VASP-Sender API url

VASP-Sender can now decrypt the BeneficiaryKYC data using the SenderUserPrivateKey.

var kycDecrypt = decryptData(TASenderUserPrivateKey.substr(2), kycTemplate['BeneficiaryKYC']);
console.log(kycDecrypt);
> {"fullname":"John Smith","dob":"Tue Oct 15 1996 05:35:32 GMT-0400 (Eastern Daylight Time)","jurisdiction":"United States"}

Step 9 - VASP-Sender Encrypts Sender KYC Data

Note: The SenderKYC data is encrypted with the Beneficiary User Public Key found in the kycTemplate.

var kycJSON = {fullname: 'Jane Doe', dob: new Date('September 15, 1990 05:35:32').toString(), jurisdiction: 'United States'};
var kycData = JSON.stringify(kycJSON);
var kycEncrypt = encryptData(kycTemplate['BeneficiaryUserPublicKey'], kycData);
kycTemplate['SenderKYC'] = kycEncrypt;

Note: Here is the SenderKYC data encrypted

SenderKYC: 'oLyDzDCQDXDzxxMyxTZIpgT429vnBdGEmVdJuV9kZIORoj6J3PoEzHayGClRt/ZxyKEO+QRu+nVRbGKsrgPubxwk6AV6AQemS59+iNsHwKZPC2OryFcKWSFGA9XMaid+EY2XzvxXbOyS9sIUsBG6SpbQNfTfp/2BSiWmCrpmxsS3upf86OA33gBqlNO45wmvipPJKgDX7Lcxs6Z0VYA4z9+3EjPs0BsHRqqRXNwhdFRhKoCYFLhAGriGToCf170pYU0DJuDdEoNVZnE5DaAv0z+eKolAY420htgyboKUaIYOv1Ewu80BE0zpGX1YjjAdrg=='

Step 10 - VASP-Sender POSTs the KYC Template to the VASP-Beneficiary API url

VASP-Beneficiary can now decrypt the SenderKYC data using the BeneficiaryUserPrivateKey.

var kycDecrypt = decryptData(TABeneficiaryUserPrivateKey.substr(2), kycTemplate['SenderKYC']);
> {"fullname":"Jane Doe","dob":"Sat Sep 15 1990 05:35:32 GMT-0400 (Eastern Daylight Time)","jurisdiction":"United States"}

Conclusion FATF Travel Rule Complete

Now both VASPs contain the sender and beneficiary kyc data in order to comply with the FATF Travel Rule requirement.

To view the full source of this guide, please visit the next section.