Skip to main content



This CIP is an addendum to the original CIP-0020, which is active since mid 2021 and widely used across many entities. It describes the JSON schema to add encrypted messages/comments/memos as transaction metadata. It is fully backwards compatible and requires no changes in existing tools, explorers, wallets. Tools/Wallets that do not have an implementation to decrypt this format will just show the encrypted base64 as the message, but it will not break any existing processes.

Motivation: why is this CIP necessary?

Current state of transaction messages

Transaction messages/comments/memos via CIP-0020 are widely used across the Cardano Blockchain. For example DEXs are using it to comment there payouts to the customers. Individual users are using it to send funds across the network to other users with attached information about it. Users are buying goods and pay directly in ADA, attaching payment informations via an added message.

Theses and many other usecases are actively happening on the blockchain right now and are a valuable addition to the core functions.

What is the issue with the current implementation?

Metadata is attached as a CBOR bytearray in the auxiliary dataset of a transaction. So the encoding is just done from UTF8-Text to Hex-Code/Bytes and after that it is sent in plaintext over the network/blockchain. To seek further adoption of blockchain usage, privacy features are a must in the future. Having cleartext information in a TCP packet might not be an issue for many things, but it is an issue if you wanna convince users to use the blockchain and their transaction feature like users using it now with bank transfers.

It is easy for 3rd-party entities like Internet Service Providers, Datacenters or basically any Man-In-The-Middle to collect data that is sent in cleartext. Data such as bank-account-numbers, email-addresses, telephone numbers, etc. which are parts of transaction messages.

What benefits/features would this CIP have on transaction messages?

As pointed out above, everyone that is having access to the datastream and of course the publicly distributed ledger can extract the cleartext data of the transaction messages. Because there must not even be a specific approach to get such transaction message data out of a TCP stream, just a simple filter for email addresses (example) is enough. Even with a simple encryption of such messages - and publicly known passphrase - it is much more complicated for the Man-In-The-Middle listener to collect data on the fly.

Targeted benefits:

  • By using a default passphrase, Man-In-The-Middle "sniffer" cannot extract/parse data like email-addresses, invoice-numbers on the fly that easily. They would need to search for a cardano-node transmission and decrypt each message. Public explorers like, Cardanoscan, etc. can still show the decrypted message content via there https connection to the user. So no cleartext transmission at all.
  • Different users can transfer funds with encrypted messages attached between each other, using a preshared passphrase. Only theses users need to know the content. Example: A user buys goods from an online-store, the store provides a preshared-passphrase to the user on their website or via email, the user sends the payment with payment-information encrypted with this passphrase to the store.
  • Keeping the usecase of a transaction private does not only belong to different entities, but to a single user too. Example: If a user sends funds to a Dex or wants to lend some fund to a friend, he just can add information like 'Sent xxx ADA to bob for xxx' to the outgoing transaction as a documentation using an own choosen private passphrase. This information is stored on the chain and so in the wallet, only the user itself can review the use case of these transactions.
  • Backwards compatible with CIP-0020
  • Easy implementation by using well known tools like OpenSSL

What this CIP is not trying to do

This addition to the original CIP-0020 should not be seen as the end-all-be-all security solution for privacy on the blockchain. There's better options and upcoming Midnight for that. The transaction messages are also not intended to act like chat messages on the chain.

Specification - Encrypted message

The specification update for encrypted messages takes advantage of the simple original design, which is leaving room for additional json-keys not affecting the parsing of the content at all. The only outcome if a receiver does not process the encrypted content is, that the encrypted message is shown instead of an maybe autodecrypted one. But even the encrypted base64 strings fit into the max. 64char long string restriction. So it does not break any tools. More on the autodecryption later.


"enc": "",
"base64-string 1", "base64-string 2", "base64-string 3" ...

The format is identical to the original one, with a simple addition of the enc (encryptionmode) entry.

The value given in the enc field references the type of encryption is used. Starting with a simple implementation named basic. There is room to add additional encryption method in the future like using ChaCha20/Poly1305 or using public/private key encryption. Also there is the possibility to not encode the metadata in the standard JSON format, but using CBOR encoding instead.

Encryption methods:

plain - no encryption at all

descriptionplaintext, no encryption

This is not really an encryption mode, but included as a backwards compatible entry to signal this message as an unencrypted one. The entry is not needed and fully optional for unencrypted messages.

basic - aes-256-cbc salted symmetric encryption via passpharse (+default passphrase)

descriptionsymmetrical encryption via openssl and a passphrase
default passphrasecardano
cipheraes-256-cbc (salted)
iterations10000 (default)
key + iv32 bytes key + 16 bytes iv
salt8 bytes

OpenSSL was choosen, because its fast and widely available also for all kind of different platforms, web frontends, etc. Encryption algo is AES-256-CBC (salted) using pdkdf2 to derive the key from the given passphrase. 10000 Iterations is the default value for this encryption method. The format of the encoded output is base64 format.

The encryption is based on a given passphrase, which can be choosen by the user. However, a default-passphrase "cardano" should be used to encrypt/decrypt if no other passphrase is provided or known.

OpenSSL uses PKCS#7 as padding. The adopted cipher accepts only multiple of 16-byte blocks. Not fitting messages to be encrypted are filled with the number of padding bytes that are needed to form multiple of 16-bytes. So if 1 byte of padding is required, the padding "01" is added. If 2 bytes of padding are needed, "02 02" is added. If no padding is required, an extra block of 0x10 bytes is added, meaning sixteen "10" bytes. In order to be interoperable with OpenSSL this kind of padding is a requirement.

Why a default passphrase?

As pointed out above, its way harder for man-in-the-middle listeners, to decrypt every single message on the fly. So by using a default passphrase, tools can encrypt messages and explorers/wallets can autodecrypt such messages trying to use the default passphrase. In that way, the displayed message is automatically readable to the user. If a more protected communication is needed, the sender can choose a custom passphrase and communicate that to the receiver as a preshared passphrase.

What part is uses for the encryption?

The whole content of the unencrypted normal transaction metadata msg: key is used, thats the array with the message string(s). (Example below)

Is there sample code?

Yes, example implementations for node.js, PHP, bash, etc. can be found in the codesamples folder. They are showing how to encrypt/decrypt text with the right parameters set for this basic mode.


Message decryption should be done on the user frontend if possible, not via server callbacks.**

Encryption/Decryption example on the console - basic mode

First, generate a normal metadata transaction message.


"674": {
"msg": ["Invoice-No: 123456789","Order-No: 7654321","Email:"]

The encryption is done on the whole content of the msg: key, so this is

["Invoice-No: 123456789","Order-No: 7654321","Email:"]

in our example.

Encrypt this content via openssl, the default passprase cardano, iteration set to 10000 and key-derivation via pbkdf2:

echo -n '["Invoice-No: 123456789","Order-No: 7654321","Email:"]' | openssl enc -e -aes-256-cbc -pbkdf2 -iter 10000 -a -k "cardano"

The encrypted result are the base64 encoded strings:


Compose the JSON by using the base64 encoded encrypted strings now for the msg: part.

Also add the value basic for the enc: key, to mark this transaction message as encrypted with basic mode.


"enc": "basic",

Console one-liner:

jq "."674".msg = [ $(jq -cjrM ."674".msg normal-message-metadata.json | openssl enc -e -aes-256-cbc -pbkdf2 -iter 10000 -a -k "cardano" | awk {'print """$1"","'} | sed '$ s/.$//') ]" "` parameter. This automatically generates the needed metadata.json structure with the encrypted message in it and attaches it to the transaction itself.


****: With the implementation of the **encrypted message decoding** using a pure **frontend solution**.

### Implementation Plan

The following Projects have committed to also implement it:

* CNTools (
* JorManager (
* (

The plan is to reach out to other projects - which already supporting the normal transaction messages - too. And of course also to new ones.

There are various **code samples available** in the [**codesamples**](codesamples/) folder to make it as easy as possible for integrators to implement it.

## Copyright

This CIP is licensed under [CC-BY-4.0](

## CIP Information
This [null](CIP-0001#cip-format-and-structure) ./CIP-0083 created on **2022-12-08** has the status: [Active](CIP-0001#cip-workflow).
This page was generated automatically from: [cardano-foundation/CIPs](