Set up the memo & send the tx
Now that we have our encrypted message, we can send it through the XRPL network. We'll create a transaction with a minimal amount (1 drop) and include our encrypted message in the transaction's memo field. The memo field is perfect for this as it can store arbitrary data, making it an ideal place for our encrypted message. Once the transaction is validated, our secret message will be securely stored on the blockchain - visible to everyone but readable only by the intended recipient who holds the matching private key.
import tweetnacl from 'tweetnacl';
import { Buffer } from "buffer";
import { Client, Wallet, deriveAddress, Payment } from "xrpl";
import {
edwardsToMontgomeryPub,
edwardsToMontgomeryPriv,
} from "@noble/curves/ed25519";
const { box, randomBytes } = tweetnacl;
/**
* Encrypts a message using X25519 (Montgomery curve) for Diffie-Hellman key exchange
*
* @param message - Plain text message to encrypt
* @param recipientPublicKey - Recipient's Ed25519 public key (hex encoded)
* @param senderSecretKey - Sender's Ed25519 private key (hex encoded)
* @returns JSON string containing base64 encoded encrypted message and nonce
*
* Steps:
* 1. Convert hex keys to byte arrays
* 2. Generate random nonce for uniqueness
* 3. Convert message to bytes
* 4. Convert Ed25519 keys to X25519 (Montgomery) format for encryption
* 5. Encrypt using NaCl box with converted keys
* 6. Return encrypted message and nonce as base64 JSON
*/
export function encryptMessage(
message: string,
recipientPublicKey: string,
senderSecretKey: string,
): string {
const pubKeyBytes = Buffer.from(recipientPublicKey.slice(2), "hex");
const secretKeyBytes = Buffer.from(senderSecretKey.slice(2), "hex");
const nonce = randomBytes(box.nonceLength);
const messageUint8 = Buffer.from(message);
const pubKeyCurve = edwardsToMontgomeryPub(pubKeyBytes);
const privKeyCurve = edwardsToMontgomeryPriv(secretKeyBytes);
const encryptedMessage = box(messageUint8, nonce, pubKeyCurve, privKeyCurve);
return JSON.stringify({
encrypted: Buffer.from(encryptedMessage).toString("base64"),
nonce: Buffer.from(nonce).toString("base64"),
});
}
/**
* Sends an XRPL transaction containing an encrypted message in its memo field
*
* @param cypherMessage - The encrypted message to send
* @param myWallet - Sender's XRPL wallet for signing the transaction
* @param receiverPubKey - Recipient's public key to derive their XRPL address
* @returns Transaction result or undefined if error occurs
*
* Flow:
* 1. Connect to XRPL testnet
* 2. Create Payment transaction:
* - Minimal amount (1 drop)
* - Include encrypted message in memo field
* - Derive recipient's address from their public key
* 3. Prepare, sign and submit transaction
* 4. Wait for validation and return result
* 5. Always disconnect client when done
*
* Note: MemoType is hex encoded "http://example.com/memo/generic"
*/
async function sendTX(
cypherMessage: string,
myWallet: Wallet,
recipientPublicKey: string,
) {
const client = new Client("wss://clio.altnet.rippletest.net:51233/");
try {
await client.connect();
const receiverAddress = deriveAddress(recipientPublicKey);
const tx: Payment = {
TransactionType: "Payment",
Account: myWallet.classicAddress,
Destination: receiverAddress,
Amount: "10000000",
Memos: [
{
Memo: {
MemoData: Buffer.from(cypherMessage).toString("hex"),
},
},
],
};
const prepared = await client.autofill(tx);
const signed = myWallet.sign(prepared);
const result = await client.submitAndWait(signed.tx_blob);
return result;
} catch (error) {
console.log(error);
} finally {
await client.disconnect();
}
}
async function main(): Promise<void> {
const recipientPublicKey = "Recipient public key goes here";
const myWallet = Wallet.fromSeed("Your seed goes here");
const cypherMessage = encryptMessage(
"Hello World",
recipientPublicKey,
myWallet.privateKey,
);
console.log("==============CYPHERED MESSAGE==============");
console.log(cypherMessage);
const tx = await sendTX(cypherMessage, myWallet, recipientPublicKey);
console.log("==============TRANSACTION==============");
console.log(tx);
console.log("all done");
}
main();
Last updated