Tutorial

Asset Tracer Decrypts Records

This example demonstrates how can the asset issuer append a tracing policy to an asset, and later an asset tracer can decrypt the transfer information when the transfer is confidential. First, we will show a successful transfer with asset tracing. Later, we will show that if the transfer does not contain tracing memos for the traceable assets, then the transfer is rejected.

If you've reviewed the previous examples, the first part of the code below should look very familiar.


console.log('Creating a keystore for Alice...');
const ALICE_PASSWORD = 'aliceisthebest';
const aliceKeyStore = new KeyStore.KeyStore(ALICE_PASSWORD);
const aliceMasterKey = aliceKeyStore.genMasterKey(ALICE_PASSWORD);
const aliceKeyPair = aliceKeyStore.genKeyPair(aliceMasterKey, 'Alice');

console.log('Creating a keystore for Bob...');
const BOB_PASSWORD = 'bobisthebest';
const bobKeyStore = new KeyStore.KeyStore(BOB_PASSWORD);
const bobMasterKey = bobKeyStore.genMasterKey(BOB_PASSWORD);
const bobKeyPair = bobKeyStore.genKeyPair(bobMasterKey, 'Bob');

Alice will now define an new asset with a tracing rule

const network = new Network.Network(PROTOCOL, HOST, QUERY_PORT, SUBMISSION_PORT, LEDGER_PORT);
console.log('Creating traceable Asset.');
const tokenCode = Ledger.random_asset_type();
const memo = 'this is a traceable asset';

Sample a tracing key for the asset issuer

const trackingKey = Ledger.AssetTracerKeyPair.new();
const tracingPolicy = Ledger.TracingPolicy.new_with_tracking(trackingKey);
const assetRules = Ledger.AssetRules.new().add_tracing_policy(tracingPolicy);

Submit asset definition

let blockCount = BigInt((await network.getStateCommitment())[1]);
const definitionTransaction = Ledger.TransactionBuilder
  .new(blockCount).add_operation_create_asset(aliceKeyPair, memo, tokenCode, assetRules).transaction();
await network.submitTransaction(definitionTransaction);
console.log('Asset created.');

await Utils.sleep(4);

Now that the asset has been created, Alice can issue units of it

blockCount = BigInt((await network.getStateCommitment())[1]);
const publicParams = Ledger.PublicParams.new();
const seqID = BigInt(await network.getIssuanceNum(tokenCode));
const amount = BigInt(100);
const transferAmount = BigInt(75);
const confidential_amount_flag = true;
const issueOp = Ledger.TransactionBuilder.new(blockCount)
  .add_basic_issue_asset(aliceKeyPair, tokenCode, seqID, amount, confidential_amount_flag, publicParams);
const issueTxn = issueOp.transaction();
let handle = await network.submitTransaction(issueTxn);
await Utils.sleep(4);
let status = await network.getTxnStatus(handle);
console.log('Asset has been issued');

I. The Asset holder, in this case Alice, can transfer asset units to bob by appending tracing memos

  1. Obtain the associated policies
const assetJson = await network.getAsset(tokenCode);
const tracingPolicies = Ledger.AssetType.from_json(assetJson).get_tracing_policies();

  1. Obtain the utxo's assetRecord and owner memo
let utxoSid = status.Committed[1][0];
let utxo = await network.getUtxo(utxoSid);
let assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
let ownerMemoJson = await network.getOwnerMemo(utxoSid);
let ownerMemo = Ledger.OwnerMemo.from_json(ownerMemoJson);

  1. Create transaction to Bob
let txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid));
const transferOp1Builder = Ledger.TransferOperationBuilder.new()
  .add_input_with_tracking(txoRef,
    assetRecord,
    ownerMemo,
    tracingPolicies,
    aliceKeyPair,
    amount)
  .add_output_with_tracking(transferAmount, bobKeyPair.get_pk(), tracingPolicies, tokenCode, confidential_amount_flag, false)
  .add_output_with_tracking(amount - transferAmount, aliceKeyPair.get_pk(), tracingPolicies, tokenCode, confidential_amount_flag, false)
  .create()
  .sign(aliceKeyPair);
const transferOp1Txn = transferOp1Builder.transaction();

  1. Submit the transaction
blockCount = BigInt((await network.getStateCommitment())[1]);
const xfrTxn1Builder = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp1Txn);
const xfrTxn1 = xfrTxn1Builder.transaction();
handle = await network.submitTransaction(xfrTxn1);
await Utils.sleep(4);
status = await network.getTxnStatus(handle);
console.log('Transaction submitted');

Now that the transaction has been submitted, the tracer can fetch the transfer note and look for traceable assets associated with its tracing key


a. Fetch the asset traced by the tracer

const base64PubKey = Ledger.public_key_to_base64(aliceKeyPair.get_pk());
const allTracedAssets = await network.getTracedAssets(base64PubKey);
const asset = Ledger.asset_type_from_jsvalue(allTracedAssets[0].val);

b. Fetch the related transfer txn related to the asset to trace

const txnSids = await network.getRelatedXfrs(asset);
const txn = await network.getTxn(txnSids[0]);

c. Fetch the transfer note

const xfrBody = txn.finalized_txn.txn.body.operations[0].TransferAsset.body.transfer;

d. Specify possible asset code to search. In upcoming versions, this won't be necessary.

const assetsToTrace = [asset];

e. Look for tokenCode in the transfer note.

const tracedAssets = Ledger.trace_assets(xfrBody, trackingKey, assetsToTrace);

Check correctness of tracing feature

assert(tracedAssets.length == 3); // input and both output should be traceable
assert(tracedAssets[0][0] == amount);
assert(tracedAssets[1][0] == transferAmount);
assert(tracedAssets[2][0] == amount - transferAmount);
assert(tracedAssets[0][1] == tokenCode);
assert(tracedAssets[1][1] == tokenCode);
assert(tracedAssets[2][1] == tokenCode);

console.log('Record has been decrypted and verified');

II. Now we will check failure of transaction verification if the transfer do not contain tracing memo on the input

console.log('Checking that transfer is rejected if input does not contain tracing memo');
const new_amount = amount - transferAmount;

utxoSid = status.Committed[1][1];
utxo = await network.getUtxo(utxoSid);
txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid));
assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
ownerMemoJson = await network.getOwnerMemo(utxoSid);
ownerMemo = Ledger.OwnerMemo.from_json(ownerMemoJson);
const transferOp2Builder = Ledger.TransferOperationBuilder.new()
  .add_input_no_tracking(txoRef,
    assetRecord,
    ownerMemo.clone(),
    aliceKeyPair,
    new_amount)
  .add_output_with_tracking(new_amount, bobKeyPair.get_pk(), tracingPolicies, tokenCode, confidential_amount_flag, false)
  .create()
  .sign(aliceKeyPair);
const transferOp2 = transferOp2Builder.transaction();
blockCount = BigInt((await network.getStateCommitment())[1]);
const xfrTxn2Builder = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp2);
const xfrTxn2 = xfrTxn2Builder.transaction();
assert.rejects(() => network.submitTransaction(xfrTxn2));

console.log('Transfer has been rejected as expected');

Removing tracing policy on output

console.log('Checking that transfer is rejected if output does not contain tracing memo');
txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid));
assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
const transferOp3Builder = Ledger.TransferOperationBuilder.new()
  .add_input_no_tracking(txoRef,
    assetRecord,
    ownerMemo.clone(),
    aliceKeyPair,
    new_amount)
  .add_output_no_tracking(new_amount, bobKeyPair.get_pk(), tokenCode, confidential_amount_flag, false)
  .create()
  .sign(aliceKeyPair);
const transferOp3Txn = transferOp3Builder.transaction();

blockCount = BigInt((await network.getStateCommitment())[1]);
const xfrTxn3 = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp3Txn)
  .transaction();
assert.rejects(() => network.submitTransaction(xfrTxn3));
console.log('Transfer has been rejected as expected');

Adding the tracing policy back to the transaction

console.log('Checking that transfer is accepted if input and output do contain tracing memos');

Removing tracing policy on output

txoRef = Ledger.TxoRef.absolute(BigInt(utxoSid));
assetRecord = Ledger.ClientAssetRecord.from_json(utxo.utxo);
const transferOp4Txn = Ledger.TransferOperationBuilder.new()
  .add_input_with_tracking(txoRef,
    assetRecord,
    ownerMemo.clone(),
    tracingPolicies,
    aliceKeyPair,
    new_amount)
  .add_output_with_tracking(new_amount, bobKeyPair.get_pk(), tracingPolicies, tokenCode, confidential_amount_flag, false)
  .create()
  .sign(aliceKeyPair)
  .transaction();

blockCount = BigInt((await network.getStateCommitment())[1]);
const xfrTxn4 = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp4Txn)
  .transaction();
handle = await network.submitTransaction(xfrTxn4);
await Utils.sleep(4);
status = await network.getTxnStatus(handle);
assert(status.Committed);
console.log('Transfer has been accepted');