Skip to main content

Challenge Verify Solution

Business Scenario

This API is designed for verifying a transfer transaction's person profile that requires travel rule verification. The originator should call this API before withdrawal. The GTR central service invites the beneficiary (deposit target) to provide the permutate combination of hash with salted person profile to verify the existence between the originator and beneficiary. Finally GTR will respond with the result to the originator within the same request.

The following figure shows two different scenarios but they are essentially the same usage. Figure 5-2. shows the withdrawal case from Originator (withdrawal from Originator, and deposit to Beneficiary) - it checks if the transfer person exists in Beneficiary service and follows the compliance policy. Figure 5-3. shows the deposit case from Beneficiary (withdraw from Originator, and deposit to Beneficiary) - It checks if the deposit person exists in Originator service and follows the compliance policy.

The difference is you verify on withdrawal and before on-chain transaction, which means you are the originator, or you verify on receiving the deposit after on-chain transaction, which means you are the beneficiary. Implies that txId is determined or not. Both are able to use network, ticker, and address to identify the person profile. The verify keypoint is who wants to confirm the profile existence and whether it follows the policy. In the case of the originator as a submitter, it means the originator wants to know it (Figure 5-2), and vice versa (Figure 5-3).

Figure 5-2. Withdraw from originator, and originator wants to initiate the verification.

Figure 5-2. Withdraw from originator, and originator wants to initiate the verification.

Figure 5-3. Deposit to beneficiary, and beneficiary wants to initiate the verification.

Figure 5-3. Deposit to beneficiary, and beneficiary wants to initiate the verification.

Step-by-step solution flow

The following description follows Figure 5-3. to show the use case. Let's assume you are VASP A (Originator), and you want to interact with VASP B (Beneficiary) by following Challenge Verify Spec. You provides the person profile to GTR, and GTR will callback to VASP B and get the person profile that is queried by address, network, and asset(ticker), and provide the permute combination of hash with salted person profile (salt is provide by VASP A, i.e: You) for GTR to cross-compare the profile matches between VASP A and VASP B, and respond with the result within the same submit request get back to VASP A (You).

Definition:

  • Originator: VASP A - You, i.e: withdraw the asset (sender)
  • Beneficiary: VASP B, i.e: deposit the asset (receiver)

Now, The example below will follow this action:

VASP A (You) sends assets to VASP B, and needs to verify the transfer person's info.

VASP B provides the permute combination of hash with salted person profile to GTR.

GTR starts matching the existence and gets back the result within the same submit request to VASP A.

Processes you need to implement, depending on your role:

  • Originator - VASP A
    • API 1: list
    • API 2: submit challenge verify
  • Beneficiary - VASP B
    • API 3: callback provide the permute combination of hash with salted person profile

You must implement both Originator and Beneficiary to complete the integration.

Common API 1: list

To know who is available on GTR service, save the list (vasp_code, vasp_name, company_name) to your database. This ensures that once transactions occur from your service, you only need to map the other party's VASP code to your system.

Invoke this API to get all VASPs exist in GTR by calling the following API:

GET https://platform.globaltravelrule.com/api/vasp/list

For further details of VASP list API, see:

/api/vasp/list

curl --silent --location --request GET "https://platform.globaltravelrule.com/api/vasp/list" \
--cert-type P12 --cert ./certificate.p12:'[MY_PASSWORD_OF_CERT]' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer [YOUR LOGIN TOKEN]" \
--header "Connection: keep-alive"

After you request for VASP list, you may get the following response:

{
"data": [
{
"companyName": "VASP A - Your Company",
"publicKey": "curve25519_pub_vasp_a",
"vaspCode": "vasp_a",
"vaspName": "VASP A - Your Company"
},
{
"companyName": "VASP B - Other People's Company",
"publicKey": "curve25519_pub_vasp_b",
"vaspCode": "vasp_b",
"vaspName": "VASP B - Other People's Company"
}
],
"msg": "success",
"status": "0",
"success": true
}
  • Keep in mind that the public key for VASP list is not permanent, people can change anytime, so do not save it when you invoke this api.

This API should let your service know the all available VASP on the GTR server.

Originator/Beneficiary API 2: submit challenge verify

Originator wants to initial the verification by submitting to the following API Endpoint.

POST https://platform.globaltravelrule.com/api/verify/submit

For further details of submit verify API, please see: /api/verify/submit

{
"txId": "",
"challengeVasp": "target vasp you want to interact",
"challengeHashPiiRequest": {
"hashedFullNames": ["keccak256(lowercase(strim('firstname,lastname,hash-salt)))"],
"hashedDateOfBirths": ["keccak256(lowercase(strim('YYYY-MM-DD,hash-salt)"]
},
"hashSalt": "hash-salt",
"requestId": "generate unique id for your service",
"ticker": "BTC/ETH....",
"address": "address", // beneficiaryAddress
"network": "specific-chain",
"tag": "",
"verifyType": 3, // Challenge Verify (fixed value here)
"transactionType": 0, // 0: YOU DEPOSIT TO TARGET, 1: TARGET DEPOSIT TO YOU ,
"legalEntityType": 0 // 0: natural person (people), 1: legal person (company)
}
  • strim(str) means remove space in the string payload.
  • keccak256(str), lowercase(str), strim(str) are pseudo code, please do not directly copy them.
  • txId depends on the scenario to send. In the case you have not yet determined the txId, leave it blank.
  • legalEntityType generally, people are using 0, companies are using 1. And note that the government department is not a legal person.

Important: For challengeHashPiiRequest.hashedFullNames, you have to add the real comma like: FIRST-LAST-SALT and challengeHashPiiRequest.hashedDateOfBirths is YYYY-MM-DD,SALT

(English name) In general, you can provide lowercase(strim(first name + "," + lastname + "," + salt)).

(Non-English name) You can provide more name possibilities, such as using non-English names, by spelling them together and removing the whitespace and comma. lowercase(strim(firstname + lastname + " ," + salt)).

(Company name verification) Please note that if it is a Legal Person (company verification), please refer to using lowercase(strim(companyname + "," + salt)). And also, if you are verifying with the company, the birthday would be 0000-00-00 (without encryption).

After sending the request, you will receive a response like:

{
"data": {
"apiUsageInfo": {
"dailyLimit": 1000,
"inputDailyCount": 0,
"inputDailyFailedCount": 0,
"inputMonthlyCount": 137,
"inputMonthlyFailedCount": 1,
"inputTrialCount": null,
"inputTrialFailedCount": null,
"monthlyLimit": 1000,
"outputDailyCount": 1,
"outputDailyFailedCount": 0,
"outputMonthlyCount": 1,
"outputMonthlyFailedCount": 1,
"outputTrialCount": null,
"outputTrialFailedCount": null,
"trialLimit": null,
"versionType": 2
},
"requestId": "requestid_100342094213",
"mismatch": ["FullName", "DateOfBirth"]
},
"success": false,
"verifyStatus": 100001,
"verifyMessage": "Verification Failed: Verification was unsuccessful."
}

The above example is for the failed case when the FullName and DateOfBirth are not matched. The below case is for all matched cases.

{
"data": {
"apiUsageInfo": {
"dailyLimit": 1000,
"inputDailyCount": 0,
"inputDailyFailedCount": 0,
"inputMonthlyCount": 137,
"inputMonthlyFailedCount": 1,
"inputTrialCount": null,
"inputTrialFailedCount": null,
"monthlyLimit": 1000,
"outputDailyCount": 1,
"outputDailyFailedCount": 0,
"outputMonthlyCount": 1,
"outputMonthlyFailedCount": 1,
"outputTrialCount": null,
"outputTrialFailedCount": null,
"trialLimit": null,
"versionType": 2
},
"requestId": "requestid_100342094213",
"mismatch": []
},
"success": true,
"verifyMessage": "Success",
"verifyStatus": 100000
}

Beneficiary API 3: callback to provide the permute combination of hash with salted person profile

Once VASP A initiates a verify request, you will receive the request for challenge verification. Based on the fields found in the request (network, ticker (assets), and address), you can query the person profile in your service, hash it using the salt and respond within the same request.

Now, we will show an example program written in Java Spring. You can choose your preferred language for implementation. The program must be a server that receives HTTP requests. The server domain and URL are updated when you log in to VASP server (see the login request field: callbackUrl), and you can update it anytime by re-logging in.

  • callbackUrl is specific to the endpoint, not just a domain.
  • callbackUrl is a POST method.

Let's assume that the callbackUrl is https://example.com/my_callback. See the following example to understand the process:

@PostMapping("/my_callback")
public Object requests(@RequestBody CommonCallbackRequestOuter < ? > vaspCallback) {
// convert to wild map
Map callbackData = (Map) vaspCallback.getCallbackData();

// wild type converter
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

Integer callbackType = vaspCallback.getCallbackType()
switch (callbackType) {
case 8: // Callback Type: 8 (Challenge Verify Callback)
return handleChallengeVerifyCallback(
objectMapper.convertValue(
callbackData,
ChallengeVerifyCallbackRequest.class
)
)
break;
}

The challenge verification callback type is 8 (Challenge Verify Callback). For more details, please see: ChallengeVerifyCallbackRequest

{
"requestId": "reque3sti41433544133211",
"invokeVaspCode": "-j_3WhMm26WSB_bkmOwRR",
"originatorVasp": "rveJ2DyP9mOCu9V9ntYj4",
"beneficiaryVasp": "-j_3WhMm26WSB_bkmOwRR",
"callbackType": 8,
"callbackData": {
"requestId": "",
"originatorVasp": "",
"originatorVaspName": "",
"hashSalt": "",
"address": "",
"ticker": "",
"tag": "",
"network": "",
"txId": "",
"transactionType": // 0: withdraw, 1: deposit
}
}

The example handler might look like:

public Object handleChallengeVerifyCallback(
ChallengeVerifyCallbackRequest req) {

// query person profile by ticker, network, address

// hash the name and date of birth

String fullName = keccak256(lowercase(strim('firstName,lastName,' + req.getHashSalt())));
String dateOfBirth = keccak256(lowercase(strim('YYYY-MM-DD,' + req.getHashSalt())));

// just return the HTTP 200 means complete the travelrule (TR)
return {
"requestId": "Your unique reqeust id",
"challengeHashPiiResult": {
"hashedFullNameCombination": ["...1", "...2"],
"hashedDateOfBirths": []
},
"verifyMessage": "success",
"verifyStatus": 100000
}
}
  • strim(str) means removing space in the string payload.
  • keccak256(str),lowercase(str),strim(str) are pseudo code, please do not directly copy them.

For details about the response, please see: ChallengeVerifyCallbackResponse

Copyright (C) 2024 Global Travel Rule. All Rights Reserved
General
Developer