Sending an encrypted message

Send an encrypted message that only can be read by the recipient account.

Use case

Imagine that Alice wants to timestamp a sensitive message to send to an account representing her academic certificate.

Alice knows that sending a TransferTransaction with a plain message to the public network will make the content of the message publicly available.

Thus, Alice sends an encrypted message that is only readable by herself and those with access to the academic certificate.

Prerequisites

Setting up the use case

Create two accounts: one for Alice and another for the certificate with the CLI tool.

Enter the NODE_URL to use to access the network.

symbol-cli account generate --save

Enter network type (MAIN_NET, TEST_NET): TEST_NET
Do you want to save it? [y/n]: y
Enter a Symbol Node URL. (Example: http://localhost:3000): <NODE_URL>
Insert profile name: alice
symbol-cli account generate --save

Enter network type (MAIN_NET, TEST_NET): TEST_NET
Do you want to save it? [y/n]: y
Enter a Symbol Node URL. (Example: http://localhost:3000): <NODE_URL>
Insert profile name: certificate

Method #01: Using the SDK

Encrypt the message

  1. Create an encrypted message for the certificate, signing it with Alice’s account.

  const networkType = await repositoryFactory.getNetworkType().toPromise();
  // replace with alice private key
  const alicePrivateKey =
    '1111111111111111111111111111111111111111111111111111111111111111';
  const aliceAccount = Account.createFromPrivateKey(
    alicePrivateKey,
    networkType,
  );
  // replace with certificate public key
  const certificatePublicKey =
    '3A537D5A1AF51158C42F80A199BB58351DBF3253C4A6A1B7BD1014682FB595EA';
  const certificatePublicAccount = PublicAccount.createFromPublicKey(
    certificatePublicKey,
    networkType,
  );

  const encryptedMessage = aliceAccount.encryptMessage(
    'This message is secret',
    certificatePublicAccount,
  );
  const networkType = await repositoryFactory.getNetworkType().toPromise();
  // replace with alice private key
  const alicePrivateKey =
    '1111111111111111111111111111111111111111111111111111111111111111';
  const aliceAccount = symbol_sdk_1.Account.createFromPrivateKey(
    alicePrivateKey,
    networkType,
  );
  // replace with certificate public key
  const certificatePublicKey =
    '3A537D5A1AF51158C42F80A199BB58351DBF3253C4A6A1B7BD1014682FB595EA';
  const certificatePublicAccount = symbol_sdk_1.PublicAccount.createFromPublicKey(
    certificatePublicKey,
    networkType,
  );
  const encryptedMessage = aliceAccount.encryptMessage(
    'This message is secret',
    certificatePublicAccount,
  );
  1. Attach the encrypted message to a TransferTransaction, setting the certificate address as the recipient.

  const transferTransaction = TransferTransaction.create(
    Deadline.create(epochAdjustment),
    certificatePublicAccount.address,
    [],
    encryptedMessage,
    networkType,
    UInt64.fromUint(2000000),
  );
  const transferTransaction = symbol_sdk_1.TransferTransaction.create(
    symbol_sdk_1.Deadline.create(epochAdjustment),
    certificatePublicAccount.address,
    [],
    encryptedMessage,
    networkType,
    symbol_sdk_1.UInt64.fromUint(2000000),
  );
  1. Sign the transaction with Alice’s account.

Note

To make the transaction only valid for your network, include the network generation hash. Open NODE_URL /node/info in a new browser tab and copy the meta.networkGenerationHash value.

  const networkGenerationHash = await repositoryFactory
    .getGenerationHash()
    .toPromise();
  const signedTransaction = aliceAccount.sign(
    transferTransaction,
    networkGenerationHash,
  );
  console.log(signedTransaction.hash);
  const networkGenerationHash = await repositoryFactory
    .getGenerationHash()
    .toPromise();
  const signedTransaction = aliceAccount.sign(
    transferTransaction,
    networkGenerationHash,
  );
  console.log(signedTransaction.hash);
  1. Once signed, announce the transaction to the network.

  const transactionRepository = repositoryFactory.createTransactionRepository();

  const response = await transactionRepository
    .announce(signedTransaction)
    .toPromise();
  console.log(response);
  const transactionRepository = repositoryFactory.createTransactionRepository();
  const response = await transactionRepository
    .announce(signedTransaction)
    .toPromise();
  console.log(response);

Decrypt the message

After the transaction gets confirmed, fetch it using the transaction hash output from (3). You can now decrypt the message using either the certificate account or address account.

// replace with network type
const networkType = NetworkType.TEST_NET;

// replace with certificate private key
const certificatePrivateKey =
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const certificateAccount = Account.createFromPrivateKey(
  certificatePrivateKey,
  networkType,
);
// replace with alice public key
const alicePublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const alicePublicAccount = PublicAccount.createFromPublicKey(
  alicePublicKey,
  networkType,
);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
// replace with transaction hash
const transactionHash =
  '0000000000000000000000000000000000000000000000000000000000000000';

transactionHttp
  .getTransaction(transactionHash, TransactionGroup.Confirmed)
  .pipe(map((x) => x as TransferTransaction))
  .subscribe(
    (transaction) => {
      console.log('Raw message: ', transaction.message.payload);
      console.log(
        'Message: ',
        certificateAccount.decryptMessage(
          transaction.message,
          alicePublicAccount,
        ).payload,
      );
    },
    (err) => console.log(err),
  );
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// replace with certificate private key
const certificatePrivateKey =
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';
const certificateAccount = symbol_sdk_1.Account.createFromPrivateKey(
  certificatePrivateKey,
  networkType,
);
// replace with alice public key
const alicePublicKey =
  'D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737';
const alicePublicAccount = symbol_sdk_1.PublicAccount.createFromPublicKey(
  alicePublicKey,
  networkType,
);
// replace with node endpoint
const nodeUrl = 'NODE_URL';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
// replace with transaction hash
const transactionHash =
  '0000000000000000000000000000000000000000000000000000000000000000';
transactionHttp
  .getTransaction(transactionHash, symbol_sdk_1.TransactionGroup.Confirmed)
  .pipe(operators_1.map((x) => x))
  .subscribe(
    (transaction) => {
      console.log('Raw message: ', transaction.message.payload);
      console.log(
        'Message: ',
        certificateAccount.decryptMessage(
          transaction.message,
          alicePublicAccount,
        ).payload,
      );
    },
    (err) => console.log(err),
  );

If you managed to read the message, try to decrypt it using another unrelated account to ensure that only the defined participants can read the encrypted content.