Sending an encrypted message

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

Background

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.

symbol-cli account generate --save

Enter network type (MIJIN_TEST, MIJIN, MAIN_NET, TEST_NET): TEST_NET
Do you want to save it? [y/n]: y
Enter a Symbol Node URL. (Example: http://localhost:3000): http://api-01.us-east-1.096x.symboldev.network:3000/
Insert profile name: alice
symbol-cli account generate --save

Enter network type (MIJIN_TEST, MIJIN, MAIN_NET, TEST_NET): TEST_NET
Do you want to save it? [y/n]: y
Enter a Symbol Node URL. (Example: http://localhost:3000): http://api-01.us-east-1.096x.symboldev.network:3000/
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.
// replace with network type
const networkType = NetworkType.TEST_NET;
// 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);
// replace with network type
const networkType = symbol_sdk_1.NetworkType.TEST_NET;
// 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(),
    certificatePublicAccount.address,
    [],
    encryptedMessage,
    networkType,
    UInt64.fromUint(2000000));
const transferTransaction = symbol_sdk_1.TransferTransaction.create(symbol_sdk_1.Deadline.create(), 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 nodeUrl + '/node/info' in a new browser tab and copy the meta.networkGenerationHash value.

// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(transferTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
// replace with meta.networkGenerationHash (nodeUrl + '/node/info')
const networkGenerationHash = '1DFB2FAA9E7F054168B0C5FCB84F4DEB62CC2B4D317D861F3168D161F54EA78B';
const signedTransaction = aliceAccount.sign(transferTransaction, networkGenerationHash);
console.log(signedTransaction.hash);
  1. Once signed, announce the transaction to the network.
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();

transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));
const nodeUrl = 'http://api-01.us-east-1.096x.symboldev.network:3000';
const repositoryFactory = new symbol_sdk_1.RepositoryFactoryHttp(nodeUrl);
const transactionHttp = repositoryFactory.createTransactionRepository();
transactionHttp
    .announce(signedTransaction)
    .subscribe((x) => console.log(x), (err) => console.error(err));

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 = 'http://api-01.us-east-1.096x.symboldev.network:3000';
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 = 'http://api-01.us-east-1.096x.symboldev.network:3000';
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.

Method #02: Using the CLI

Encrypt the message

 symbol-cli transaction transfer --recipient-public-key 3A537D5A1AF51158C42F80A199BB58351DBF3253C4A6A1B7BD1014682FB595EA --message "This message is secret" --encrypted --sync