Encoding and Decoding Document Matrix in Attestations

When setting attestations, the document matrix encrypted parameter can be encoded to contain information about crypto addresses. These crypto addresses are the user withdrawal wallet addresses registered in a VASP platform for withdrawal.

In the case when a user requests a withdrawal to their wallet address, VASPs can set an attestation to the newtwork about this pending withdrawal.

Prior to setting the attestation the document matrix can be encoded using the following methods.

Encoding the Document

Step 1: install the following libraries.

const BN = require('bn.js');
const ethers = require("ethers")
const pako = require('pako');
const web3_eth_abi = require("web3-eth-abi");

Step 2: Create the Travel Rule Template

Let's assume the attestation will contain the following ETH address: 0x2F7A096C62Ba9a7a43a3aA32c46f5b059b99A30E

Note: any cryptocurrency address will work for encoding the document matrix component.

let address = "0x2F7A096C62Ba9a7a43a3aA32c46f5b059b99A30E";
let addressBuffer = new Buffer(address);
let travelRuleTemplate = createTravelRuleTemplate(addressBuffer);

Here is the createTravelRuleTemplate function:

function createTravelRuleTemplate(_documentMatrixDataBytes) {
let documentsMatrixBitString = "";
let documentsMatrixBits = new Array(256);
documentsMatrixBitString += "1";
let i = 0;
for (i = 1 ; i < 255; i++) {
documentsMatrixBitString += "0";
}
documentsMatrixBitString += "0";
let documentMatrixBignum = new BN(documentsMatrixBitString, 2);
let versionCode = 2;
return {"bitsMatrix" : documentMatrixBignum, "versionCode" : versionCode, "documentMatrixDataBytes" : _documentMatrixDataBytes};
}

Note: the result of createTravelRuleTemplate

{
bitsMatrix: BN {
negative: 0,
words: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2097152 ],
length: 10,
red: null
},
versionCode: 2,
documentMatrixDataBytes: <Buffer 30 78 32 46 37 41 30 39 36 43 36 32 42 61 39 61 37 61 34 33 61 33 61 41 33 32 63 34 36 66 35 62 30 35 39 62 39 39 41 33 30 45>
}

Step 3: Encode the Document Matrix in Place

Use the template above to encode in place.

let encodedDocumentMatrix = encodeDocumentMatrixInPlace(travelRuleTemplate);

Here is the encodeDocumentMatrixInPlace function:

function encodeDocumentMatrixInPlace(_dataTemplate) {
if (_dataTemplate.versionCode === 2 && _dataTemplate.documentMatrixDataBytes != null) {
let encodedDocumentMatrixDataPacked = JSON.stringify(_dataTemplate.documentMatrixDataBytes);
let bytesDocumentMatrixEncodedPacked = ethers.utils.toUtf8Bytes(encodedDocumentMatrixDataPacked);
_dataTemplate["encryptedData"] = bytesDocumentMatrixEncodedPacked;
return _dataTemplate;
} else {
return null;
}
}

Note: the result of encodeDocumentMatrixInPlace

{
bitsMatrix: BN {
negative: 0,
words: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2097152 ],
length: 10,
red: null
},
versionCode: 2,
documentMatrixDataBytes: <Buffer 30 78 32 46 37 41 30 39 36 43 36 32 42 61 39 61 37 61 34 33 61 33 61 41 33 32 63 34 36 66 35 62 30 35 39 62 39 39 41 33 30 45>,
encryptedData: Uint8Array [
123, 34, 116, 121, 112, 101, 34, 58, 34, 66, 117, 102,
102, 101, 114, 34, 44, 34, 100, 97, 116, 97, 34, 58,
91, 52, 56, 44, 49, 50, 48, 44, 53, 48, 44, 55,
48, 44, 53, 53, 44, 54, 53, 44, 52, 56, 44, 53,
55, 44, 53, 52, 44, 54, 55, 44, 53, 52, 44, 53,
48, 44, 54, 54, 44, 57, 55, 44, 53, 55, 44, 57,
55, 44, 53, 53, 44, 57, 55, 44, 53, 50, 44, 53,
49, 44, 57, 55, 44, 53, 49, 44, 57, 55, 44, 54,
53, 44, 53, 49,
... 54 more items
]
}

Step 4: Encode the document.

Use the result above to encode the document for setting the attestation.

let encodedDocument = encodeDocument(encodedDocumentMatrix.bitsMatrix, encodedDocumentMatrix.versionCode, encodedDocumentMatrix.encryptedData);

Here is the encodeDocument function:

function encodeDocument(_bitsMatrix, _versionCode, _encryptedData) {
if (_versionCode === 1 || _versionCode == 2) {
let encodedDocumentsMatrix = web3_eth_abi.encodeParameter(
{
"DocumentsMatrixStruct": {
"bitsMatrix": 'uint256',
"versionCode": 'uint16',
"encryptedData": 'bytes'
}
},
{
"bitsMatrix": _bitsMatrix.toString(),
"versionCode": _versionCode.toString(),
"encryptedData": _encryptedData
}
);
const hex = Uint8Array.from(Buffer.from(encodedDocumentsMatrix.substr(2), 'hex'));
let zippedEncodedDocumentsMatrix = pako.deflate(hex).buffer;
const zippedHex = Buffer.from(zippedEncodedDocumentsMatrix).toString('hex');
return zippedHex;
} else {
return null;
}
}

Note: the result of encodeDocument

789c858e410a83400c45a5272959ffc58c4e32c6a5d7904285eaba14bb28a5e0397adac648b74e166fc27f4998aa3aacf37aec4b752af86bc17fdfb4bcee1375d43fe7797a10e8362e2375436a11eb000ec84686302ce20c4e10a7291168dec28decacc1d11ba76d596393aaae1262b0a781b67eaef1e5fdee7fda72d1cb67ffe00f02d82355

This encoded document can now be used for the documents matrix encrypted parameter in Set Attestation; i.e.

tasSetAttestation("User address", jurisdiction, effective time, expiry time, public data, documents matrix encrypted, availability address encrypted, is managed, TA address)'

Decoding the Document

Upon fetching attestation components, the documentsMatrixEncrypted component will be the field that contains the encode address used above.

See documentsMatrixEncrypted:

Result {
trustAnchorAddress: '0xF3339c2CAE33dCb4Fd7a9640AFcFA5944D62976C',
jurisdiction: '1',
effectiveTime: '1551989822',
expiryTime: '1615234638',
publicData: '0x4b5943',
documentsMatrixEncrypted: '0x63676e2f744353777257556d38684e75566a362b4d6752554357716f7a746f4e79515657746d6b4b6a52522f77556d424d36743348567646365974706c6763726a725a354a39704b775a46794230734a30334c4878753137354336734a3651654f6247343932724c47366a4136336675303446765345696c3355793739667551365053673969565a5148514b45534854514161586550686e37714e652b44536b5530646837352b5657364456594d707477783342734a61304175664f4772446c2f673d3d',
availabilityAddressEncrypted: '0x4a6f686e20536d69746800000000000000000000000000000000000000000000',
isManaged: true
}

Below are the steps to decoding the field.

Step 1: Decode the Document

let documentMatrix = "789c858e410a83400c45a5272959ffc58c4e32c6a5d7904285eaba14bb28a5e0397adac648b74e166fc27f4998aa3aacf37aec4b752af86bc17fdfb4bcee1375d43fe7797a10e8362e2375436a11eb000ec84686302ce20c4e10a7291168dec28decacc1d11ba76d596393aaae1262b0a781b67eaef1e5fdee7fda72d1cb67ffe00f02d82355";
let decodedDocument = decodeDocument(documentMatrix);

Here is the decodeDocument function:

function decodeDocument(_documentEncoded) {
const hex = Uint8Array.from(Buffer.from(_documentEncoded, 'hex'));
try {
let inflated = pako.inflate(hex);
let inflatedToHex = Buffer.from(inflated).toString('hex');
let unzippedEncodedDocumentsMatrix = "0x" + inflatedToHex;
let decodedDocumentsMatrix = web3_eth_abi.decodeParameter(
{
"DocumentsMatrixStruct": {
"bitsMatrix": 'uint256',
"versionCode": 'uint16',
"encryptedData": 'bytes'
}
},
unzippedEncodedDocumentsMatrix
);
return decodedDocumentsMatrix;
} catch (err) {
console.log(err);
return null;
}
}

Note: the result of decodeDocument

[
'57896044618658097711785492504343953926634992332820282019728792003956564819968',
'2',
'0x7b2274797065223a22427566666572222c2264617461223a5b34382c3132302c35302c37302c35352c36352c34382c35372c35342c36372c35342c35302c36362c39372c35372c39372c35352c39372c35322c35312c39372c35312c39372c36352c35312c35302c39392c35322c35342c3130322c35332c39382c34382c35332c35372c39382c35372c35372c36352c35312c34382c36395d7d',
bitsMatrix: '57896044618658097711785492504343953926634992332820282019728792003956564819968',
versionCode: '2',
encryptedData: '0x7b2274797065223a22427566666572222c2264617461223a5b34382c3132302c35302c37302c35352c36352c34382c35372c35342c36372c35342c35302c36362c39372c35372c39372c35352c39372c35322c35312c39372c35312c39372c36352c35312c35302c39392c35322c35342c3130322c35332c39382c34382c35332c35372c39382c35372c35372c36352c35312c34382c36395d7d'
]

Step 2: Decode the Document Matrix in Place

let decodeDocumentMatrix = decodeDocumentMatrixInPlace(decodedDocument);

Here is the decodeDocumentMatrixInPlace function:

function decodeDocumentMatrixInPlace(_dataTemplate) {
if (_dataTemplate.versionCode == 2) {
let bytesDocumentMatrixEncodedPacked = ethers.utils.toUtf8String(_dataTemplate.encryptedData);
let encodedDocumentMatrixDataPacked = JSON.parse(bytesDocumentMatrixEncodedPacked);
_dataTemplate["decryptedData"] = encodedDocumentMatrixDataPacked;
return _dataTemplate;
} else {
return null;
}
}

Note: the result of decodeDocumentMatrixInPlace

[
'57896044618658097711785492504343953926634992332820282019728792003956564819968',
'2',
'0x7b2274797065223a22427566666572222c2264617461223a5b34382c3132302c35302c37302c35352c36352c34382c35372c35342c36372c35342c35302c36362c39372c35372c39372c35352c39372c35322c35312c39372c35312c39372c36352c35312c35302c39392c35322c35342c3130322c35332c39382c34382c35332c35372c39382c35372c35372c36352c35312c34382c36395d7d',
bitsMatrix: '57896044618658097711785492504343953926634992332820282019728792003956564819968',
versionCode: '2',
encryptedData: '0x7b2274797065223a22427566666572222c2264617461223a5b34382c3132302c35302c37302c35352c36352c34382c35372c35342c36372c35342c35302c36362c39372c35372c39372c35352c39372c35322c35312c39372c35312c39372c36352c35312c35302c39392c35322c35342c3130322c35332c39382c34382c35332c35372c39382c35372c35372c36352c35312c34382c36395d7d',
decryptedData: {
type: 'Buffer',
data: [
48, 120, 50, 70, 55, 65, 48, 57, 54, 67,
54, 50, 66, 97, 57, 97, 55, 97, 52, 51,
97, 51, 97, 65, 51, 50, 99, 52, 54, 102,
53, 98, 48, 53, 57, 98, 57, 57, 65, 51,
48, 69
]
}
]

Step 3: Unpack the Crypto Address

let unpackedCryptoAddress = unpackCryptoAddress(decodeDocumentMatrix);

Here is the unpackCryptoAddress function:

function unpackCryptoAddress(decodeDocumentMatrix) {
return ethers.utils.toUtf8String(decodeDocumentMatrix.decryptedData.data);
}

Note: the result of unpackCryptoAddress

0x2F7A096C62Ba9a7a43a3aA32c46f5b059b99A30E

This result matches the original ETH address used above when encoding the document matrix encrypted field of Set Attestation.

See below for full source code example

const BN = require('bn.js');
const ethers = require("ethers")
const pako = require('pako');
const web3_eth_abi = require("web3-eth-abi");
function createTravelRuleTemplate(_documentMatrixDataBytes) {
let documentsMatrixBitString = "";
let documentsMatrixBits = new Array(256);
documentsMatrixBitString += "1";
let i = 0;
for (i = 1 ; i < 255; i++) {
documentsMatrixBitString += "0";
}
documentsMatrixBitString += "0";
let documentMatrixBignum = new BN(documentsMatrixBitString, 2);
let versionCode = 2;
return {"bitsMatrix" : documentMatrixBignum, "versionCode" : versionCode, "documentMatrixDataBytes" : _documentMatrixDataBytes};
}
function encodeDocumentMatrixInPlace(_dataTemplate) {
if (_dataTemplate.versionCode === 2 && _dataTemplate.documentMatrixDataBytes != null) {
let encodedDocumentMatrixDataPacked = JSON.stringify(_dataTemplate.documentMatrixDataBytes);
let bytesDocumentMatrixEncodedPacked = ethers.utils.toUtf8Bytes(encodedDocumentMatrixDataPacked);
_dataTemplate["encryptedData"] = bytesDocumentMatrixEncodedPacked;
return _dataTemplate;
} else {
return null;
}
}
function encodeDocument(_bitsMatrix, _versionCode, _encryptedData) {
if (_versionCode === 1 || _versionCode == 2) {
let encodedDocumentsMatrix = web3_eth_abi.encodeParameter(
{
"DocumentsMatrixStruct": {
"bitsMatrix": 'uint256',
"versionCode": 'uint16',
"encryptedData": 'bytes'
}
},
{
"bitsMatrix": _bitsMatrix.toString(),
"versionCode": _versionCode.toString(),
"encryptedData": _encryptedData
}
);
const hex = Uint8Array.from(Buffer.from(encodedDocumentsMatrix.substr(2), 'hex'));
let zippedEncodedDocumentsMatrix = pako.deflate(hex).buffer;
const zippedHex = Buffer.from(zippedEncodedDocumentsMatrix).toString('hex');
return zippedHex;
} else {
return null;
}
}
function decodeDocument(_documentEncoded) {
const hex = Uint8Array.from(Buffer.from(_documentEncoded, 'hex'));
try {
let inflated = pako.inflate(hex);
let inflatedToHex = Buffer.from(inflated).toString('hex');
let unzippedEncodedDocumentsMatrix = "0x" + inflatedToHex;
let decodedDocumentsMatrix = web3_eth_abi.decodeParameter(
{
"DocumentsMatrixStruct": {
"bitsMatrix": 'uint256',
"versionCode": 'uint16',
"encryptedData": 'bytes'
}
},
unzippedEncodedDocumentsMatrix
);
return decodedDocumentsMatrix;
} catch (err) {
console.log(err);
return null;
}
}
function decodeDocumentMatrixInPlace(_dataTemplate) {
if (_dataTemplate.versionCode == 2) {
let bytesDocumentMatrixEncodedPacked = ethers.utils.toUtf8String(_dataTemplate.encryptedData);
let encodedDocumentMatrixDataPacked = JSON.parse(bytesDocumentMatrixEncodedPacked);
_dataTemplate["decryptedData"] = encodedDocumentMatrixDataPacked;
return _dataTemplate;
} else {
return null;
}
}
function unpackCryptoAddress(decodeDocumentMatrix) {
return ethers.utils.toUtf8String(decodeDocumentMatrix.decryptedData.data);
}
// node -e 'require("./encode_decode.js").packDocumentsMatrixEncrypted("0x2F7A096C62Ba9a7a43a3aA32c46f5b059b99A30E")'
module.exports.packDocumentsMatrixEncrypted = function (address) {
packDocumentsMatrixEncrypted(address);
};
function packDocumentsMatrixEncrypted(address) {
let addressBuffer = new Buffer(address);
let travelRuleTemplate = createTravelRuleTemplate(addressBuffer);
let encodedDocumentMatrix = encodeDocumentMatrixInPlace(travelRuleTemplate);
let encodedDocument = encodeDocument(encodedDocumentMatrix.bitsMatrix, encodedDocumentMatrix.versionCode, encodedDocumentMatrix.encryptedData);
console.log(' packDocumentsMatrixEncrypted');
console.log(encodedDocument);
}
// node -e 'require("./encode_decode.js").unpackDocumentsMatrixEncrypted("789c858ec10ac2400c448b5f2239cf61b36c369b1efd0d2958b03d8bd48388e017f8cda6ebb1d01d0293e10d215db7abe3679fb77468f04b837f5fb43c6f13f5747accf37427d0755c46eacfc950024a06b3410559913d84086137862647e19f12ccdc725d99c55da04eb8ac05af96045514814424ab07d65b1c02ac0e471ededb077fcdb0208a")'
module.exports.unpackDocumentsMatrixEncrypted = function (documentMatrix) {
unpackDocumentsMatrixEncrypted(documentMatrix);
};
function unpackDocumentsMatrixEncrypted(documentMatrix) {
let decodedDocument = decodeDocument(documentMatrix);
let decodeDocumentMatrix = decodeDocumentMatrixInPlace(decodedDocument);
let unpackedCryptoAddress = unpackCryptoAddress(decodeDocumentMatrix);
console.log(' unpackDocumentsMatrixEncrypted');
console.log(cryptoAddress);
}