Tutorial

Confidential Operations

Findora Ledger is transparent; any curious party can fetch and authenticate the transaction log. However, privacy-conscious users can blind parts of their transactions, hiding sensitive information from the outside world. For example, asset issuers can blind the amount field of a TXO created by an IssueAsset transaction. When transferring assets, users can blind not only the amount field of output TXOs, but also the field containing the asset token code. There are situations where users are precluded from blinding certain fields, but that will be the topic of another tutorial.

In this example, Alice and Bob will demonstrate how to issue and transfer assets while using Findora Ledger's confidentiality features.

First, Alice and Bob will create their respective key stores:

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:

const network = new Network.Network(PROTOCOL, HOST, QUERY_PORT, SUBMISSION_PORT, LEDGER_PORT);
const tokenCode = Ledger.random_asset_type();
const memo = 'this is a test asset';
const assetRules = Ledger.AssetRules.new();
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);

Alice will now issue a few units of the asset to herself. Notice that, as opposed to in the Transferring an Asset example, the flag confidential is set to true. This means that the first TXO created by issueTxn will have a blinded amount field. Only Alice will have the capability to view the actual amount. We will see how she does this shortly.

await Utils.sleep(4);
blockCount = BigInt((await network.getStateCommitment())[1]);
const zeiParams = Ledger.PublicParams.new();
const seqID = BigInt(await network.getIssuanceNum(tokenCode));
const amount = BigInt(5);
let confidential = true;
let issueTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_basic_issue_asset(aliceKeyPair, tokenCode, seqID, amount, confidential, zeiParams);

For demonstration purposes, Alice will also issue some units of her asset without making the amount field confidential:

const secondSeqID = BigInt(await network.getIssuanceNum(tokenCode) + 1);
confidential = false;
issueTxn = issueTxn.add_basic_issue_asset(aliceKeyPair, tokenCode, secondSeqID, amount, confidential, zeiParams).transaction();

In summary, Alice has created a transaction that will issue two asset records, the first of which has a blinded amount field, and the second of which does not.

Alice will now submit her transaction and retrieve the TXOSIDs associated with two new asset records:

const handle = await network.submitTransaction(issueTxn);
await Utils.sleep(4);
const status = await network.getTxnStatus(handle);
const confSid = await status.Committed[1][0];
const nonConfSid = await status.Committed[1][1];

Now, Alice will now fetch her committed TXOs:

const confUtxo = await network.getUtxo(confSid);
const nonConfUtxo = await network.getUtxo(nonConfSid);

Alternatively, Alice could have retrieved her TXOs from the issuance transaction that she just created. Either usage pattern is valid.

Let's take a closer look at the TXO that Alice didn't blind. The amount field can be accessed directly:

assert(nonConfUtxo.utxo.record.amount.NonConfidential == 5);

On the other hand, the blinded TXO has a Confidential property on the amount field that does not directly expose the actual amount:

assert(confUtxo.utxo.record.amount.Confidential);

Logging the Confidential property will reveal two seemingly random arrays of numbers. We wont dive into exactly why the blinded amount is represented this way. The takeaway is that it is not possible to derive the actual amount of Alice's TXO from this blinded field.

Alice can unblind the asset record with her secret key and an owner memo. An owner memo is an object generated with every new TXO that contains the encrypted values that the blinded asset record values are generated from. For convenience, owner memos are stored on the query server. Alice will now decrypt the owner memo corresponding to her blinded asset record:

const ownerMemoJson = await network.getOwnerMemo(confSid);
const ownerMemo = Ledger.OwnerMemo.from_json(ownerMemoJson);
const assetRecord = Ledger.ClientAssetRecord.from_json(confUtxo.utxo);
const decryptedRecord = Ledger.open_client_asset_record(assetRecord, ownerMemo.clone(), aliceKeyPair);

Alice can now view the contents of decryptedRecord directly.

assert(decryptedRecord.amount == 5);

Now, Alice will send some units of her new asset to Bob, blinding not only the asset amount, but also the asset type in the output of her transfer operation:

const transferAmount = BigInt(decryptedRecord.amount);
const txoRef = Ledger.TxoRef.absolute(BigInt(confSid));
const blindedAmount = true;
const blindedType = true;
const transferOp = Ledger.TransferOperationBuilder.new()
  .add_input_no_tracking(txoRef,
    assetRecord,
    ownerMemo,
    aliceKeyPair,
    transferAmount)
  .add_output_no_tracking(transferAmount, bobKeyPair.get_pk(), tokenCode, blindedAmount, blindedType)
  .create()
  .sign(aliceKeyPair)
  .transaction();

Alice can now create and submit her transaction:

blockCount = BigInt((await network.getStateCommitment())[1]);
const transferTxn = Ledger.TransactionBuilder.new(blockCount)
  .add_transfer_operation(transferOp)
  .transaction();
await network.submitTransaction(transferTxn);

Bob can retrieve his new asset record, fetch the associated owner memo, and decrypt the amount and type:

await Utils.sleep(3);
const base64PubKey = Ledger.public_key_to_base64(bobKeyPair.get_pk());
const bobTxoSids = await network.getOwnedSids(base64PubKey);
const newSid = bobTxoSids[0];
const bobUtxo = await network.getUtxo(newSid);
const bobMemoJson = await network.getOwnerMemo(newSid);
const bobOwnerMemo = Ledger.OwnerMemo.from_json(bobMemoJson);
const bobAssetRecord = Ledger.ClientAssetRecord.from_json(bobUtxo.utxo);
const bobDecryptedRecord = Ledger.open_client_asset_record(bobAssetRecord, bobOwnerMemo, bobKeyPair);
assert(bobDecryptedRecord.amount == 5);
assert(Ledger.asset_type_from_jsvalue(bobDecryptedRecord.asset_type) == tokenCode);