4

How can I attach an encrypted message to a payment transaction from inside a contract? In release 2.2.0e it was possible by just calling the SendMoneyCall API with .messageToEncrypt:

SendMoneyCall sendMoneyCall = SendMoneyCall.create(<chain id>)
        .recipient(<recipient account>)
        .amountNQT(<amount>)
        .messageToEncrypt("test message");

this doesn’t seem to work in version 2.2.1. Do I have to do it manually now? Like first encrypting and compressing the message and then adding it to the transaction via sendMoneyCall.message(“test message”)?

  • Please post the answer as a self-answer, instead of as part of the question. – Raghav Sood Dec 23 '18 at 16:01
  • ok, sorry. I'm new here :) – atzen Dec 24 '18 at 9:34
2

I've asked this question in ardors slack developer channel and post it here for documentation. After a small dialog in which I detailed the occurring error with a small example contract I got an explanation with a working solution:

About the messageToEncrypt parameter, the problem is that when a transaction is submitted by a contract it is first generated without a secretPhrase then signed and broadcasted. This means that message cannot be encrypted when the transaction is generated. Therefore to submit an encrypted message in your contract you need to encrypt it yourself using the technique in sample contract IgnisArdorRates line 94:

   // Encrypt the message
   EncryptedData encryptedData = context.getConfig().encryptTo(Account.getPublicKey(context.getSenderId()), Convert.toBytes(response.toJSONString(), true), true);

   // Send a message back to the user who requested the information
   SendMessageCall sendMessageCall = SendMessageCall.create(context.getChainOfTransaction().getId()).recipient(context.getSenderId()).
           encryptedMessageData(encryptedData.getData()).
           encryptedMessageNonce(encryptedData.getNonce()).
           encryptedMessageIsPrunable(true);
   return context.createTransaction(sendMessageCall);

I've adapted my example contract according to this answer and post it here to share the solution:

example contract:

package com.jelurida.ardor.contracts;

import nxt.account.Account;
import nxt.addons.AbstractContract;
import nxt.addons.JO;
import nxt.addons.TransactionContext;
import nxt.crypto.EncryptedData;
import nxt.http.callers.SendMoneyCall;
import nxt.http.responses.TransactionResponse;
import nxt.util.Convert;

public class TestContract extends AbstractContract {


    @Override
    public JO processTransaction(TransactionContext context) {
        TransactionResponse transaction = context.getTransaction();
        EncryptedData encryptedData = context.getConfig().encryptTo(Account.getPublicKey(context.getSenderId()), Convert.toBytes("test message", true), true);

        SendMoneyCall sendMoneyCall = SendMoneyCall.create(transaction.getChainId())
                .recipient(transaction.getSender())
                .amountNQT(transaction.getAmount())
                .encryptedMessageData(encryptedData.getData())
                .encryptedMessageNonce(encryptedData.getNonce())
                .encryptedMessageIsPrunable(true);

        return context.createTransaction(sendMoneyCall);
    }
}

unit test:

package com.jelurida.ardor.contracts;

import nxt.addons.JO;
import nxt.blockchain.Block;
import nxt.blockchain.ChildTransaction;
import nxt.blockchain.FxtTransaction;
import nxt.messaging.PrunableEncryptedMessageAppendix;
import nxt.util.Convert;
import org.junit.Assert;
import org.junit.Test;

import static nxt.blockchain.ChildChain.IGNIS;

public class TestContractTest extends AbstractContractTest {

    @Test
    public void encryptedMessageTest() {
        String contractName = ContractTestHelper.deployContract(TestContract.class);

        JO messageJson = new JO();
        messageJson.put("contract", contractName);
        String message = messageJson.toJSONString();
        ContractTestHelper.bobPaysContract(message, IGNIS);
        generateBlock();

        Block lastBlock = getLastBlock();
        boolean transactionFound = false;
        for (FxtTransaction transaction : lastBlock.getFxtTransactions()) {
            for (ChildTransaction childTransaction : transaction.getSortedChildTransactions()) {

                transactionFound = true;
                Assert.assertEquals("test message", extractMessage(childTransaction));

            }
        }
        Assert.assertTrue(transactionFound);
    }

    private String extractMessage(ChildTransaction transaction) {
        PrunableEncryptedMessageAppendix appendix = (PrunableEncryptedMessageAppendix) transaction.getAppendages().stream().filter(a -> a instanceof PrunableEncryptedMessageAppendix).findFirst().orElse(null);
        byte[] compressedData = appendix.getEncryptedData().decrypt(BOB.getSecretPhrase(), ALICE.getPublicKey());
        byte[] data = Convert.uncompress(compressedData);
        return Convert.toString(data, true);
    }

Hope it's helpful :).

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.