To solve the first two requirements, it was natural to look at encryption algorithms. Any of the commonly used encryption algorithms out there are able to encode text so that only those who know the secret key used to encrypt it are able to decrypt it. The last three requirements (randomization, no external storage, and memo size) are the interesting bits of the solution.

First I went looking for an encryption algorithm that doesn’t bloat the final memo size and that could support randomization. I didn’t have to look very far, because one of the most common algorithms, AES-256 in CTR mode, is a perfect fit. The final length of the encrypted data scales well with the size of the input, it is incredibly resistant to attack, and it can be used with a random initialization vector so that the encrypted data changes every time.

So with the encryption algorithm chosen, I set out to implement it, trying to fit as much space as possible in the memo. My first attempt was just to use “text” type Stellar memos, which normally can fit 28 characters of data, and encrypt the data into that. However, the encryption scheme would use up 8 characters of that space in the memo to encode the randomized initialization vector that makes it so the data changes every time it run, leaving you with only 20 characters left.

While 20 characters seems reasonable, I knew I could do better. Stellar has a different memo type, “hash” memos, which contain exactly 32 bytes of hexadecimal characters. If we encoded the memo into hex, I could potentially add up to 4 more characters of maximum storage (we would still need to use 8 of them for the randomization aspect). Here was my first attempt:

> Error, “hash” type memo must be 32 bytes of hexadecimal characters

So while we could indeed fit more data into a hash memo, it would only work if the data size was exactly 24 characters, enough to make the final hash have exactly 32 bytes.

To solve that problem, I could pad the data with extra random bytes until it fit the required size. However doing that causes another problem: we won’t know which bits of the memo were additional randomized padding and which bits of the memo was the original data. To solve that we just need to store the size of the padding inside the memo itself so when we decrypt it we can pull the padding off the end and get back the original data.

We’ve done it! At the end of the day were were able to squeeze out an additional 3 characters of space by using hash type memos (lost 1 potential byte so that we could store the padding size).

All that was left was to package it up into a reusable library: I’ve published version 1 of @stellarguard/secret-memo which is available as a JavaScript/TypeScript library on NPM and the code is available on Github.

Here’s how code that uses it might look.

I look forward to the days when exchanges can leverage this code or ideas to keep their customers data more private and secure. When combined with Stellar’s federation servers, exchanges should be able to automatically generate these secret memos without having to have their users copying and pasting anything.

If you’ve got ideas for how to fit even more characters into the memo or other improvements, let me know here or file an issue.