Encrypting & Decrypting Messages
Transaction assets for Messages and KVS records are encrypted before being packed into transactions.
- Messages (type 8) use NaCl box (public-key encryption), which relies on Curve25519 for key exchange and Salsa20 for encryption.
- KVS records (type 9) use NaCl secretbox (symmetric encryption), which relies on Salsa20 for encryption.
Both use Poly1305 for authentication.
After encryption, they are included in transactions, which are then signed and broadcasted to the ADAMANT network.
Encrypting messages
An encrypted message uses the following flow:

Generate a random nonce (24 bytes)
Convert the sender’s Ed25519 private key to a Curve25519 secret key
Retrieve the recipient’s public key from the ADAMANT network
Use
GET /api/accounts
endpointINFO
If the public key can’t be retrieved, it likely means the recipient’s account hasn’t been initialized yet.
Learn about Account Initialization.
Convert the recipient’s Ed25519 public key to a Curve25519 public key
Encrypt the message using NaCl box algorithm, with:
- The plaintext message
- The nonce
- The recipient’s Curve25519 public key
- The sender’s Curve25519 secret key
Return the encrypted message and nonce
- The nonce must be stored alongside the encrypted message for decryption
Example using TypeScript
import nacl from 'tweetnacl/nacl-fast';
import ed2curve from 'ed2curve';
import sodium from 'sodium-browserify-tweetnacl';
const bytesToHex = (bytes: Uint8Array) => {
let hex = '';
for (const byte of bytes) {
hex += (byte >>> 4).toString(16);
hex += (byte & 0xf).toString(16);
}
return hex;
};
const encodeMessage = (
msg: string,
keypair: KeyPair,
recipientPublicKey: Uint8Array | string
) => {
const nonce = Buffer.allocUnsafe(24);
sodium.randombytes(nonce);
const plainText = Buffer.from(msg.toString());
const DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey);
let publicKey = recipientPublicKey;
if (typeof publicKey === 'string') {
publicKey = hexToBytes(publicKey);
}
const DHPublicKey = ed2curve.convertPublicKey(publicKey);
if (!DHPublicKey) {
throw new Error('encodeMessage: invalid key');
}
const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey);
return {
message: bytesToHex(encrypted),
own_message: bytesToHex(nonce),
};
};
Decrypting messages
To decrypt a message follow the algorithm:
- Convert your Ed25519 private key to a Curve25519 secret key.
- Convert other party's Ed25519 public key to a Curve25519 public key.
- Decrypt the message using the NaCl box algorithm with:
- The encrypted message
- The nonce
- The other party's Curve25519 public key
- Your Curve25519 secret key
Example using TypeScript
import nacl from 'tweetnacl';
import ed2curve from 'ed2curve';
import { decode } from '@stablelib/utf8';
const hexToBytes = (hex: string) => {
const bytes: number[] = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.slice(c, c + 2), 16));
}
return Uint8Array.from(bytes);
};
const decodeMessage = (
message: string,
senderPublicKey: string,
keypair: KeyPair,
nonce: string
) => {
const publicKey = hexToBytes(senderPublicKey);
const DHPublicKey = ed2curve.convertPublicKey(publicKey);
if (!DHPublicKey) {
throw new Error('decodeMessage: invalid key');
}
const DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey);
const decryptedMessage = nacl.box.open(
hexToBytes(message),
hexToBytes(nonce),
DHPublicKey,
DHSecretKey
);
return decryptedMessage ? decode(decryptedMessage) : '';
};
Encrypting KVS records
If value
needs to be encrypted, the following steps are applied:
- Wrap the content to be encrypted into a JSON object:
{ "payload": <your value> }
- Convert the JSON to a string and prefix/suffix it with a random string
- The random string should be alphanumeric (ASCII), excluding
{
and}
- The random string should be alphanumeric (ASCII), excluding
- Compute the secret key as the SHA-256 hash of your ADAMANT private key.
- Encrypt the resulting string using NaCl.secretbox with:
- The plaintext message
- A randomly generated nonce (24 bytes)
- The secret key
- Store the encrypted message and nonce in JSON format:
{ "message": <encrypted message>, "nonce": <nonce> }
, which is then saved into the KVS.
Example using JavaScript
/**
* Encodes a secret value (available for the owner only)
* @param {string} value value to encode
* @param {Uint8Array} privateKey private key
* @returns {{message: string, nonce: string}} encoded value and nonce (both as HEX-strings)
*/
const encodeValue = function (value, privateKey) {
const randomString = () =>
Math.random()
.toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, Math.ceil(Math.random() * 10));
const nonce = Buffer.allocUnsafe(24);
sodium.randombytes(nonce);
const padded =
randomString() + JSON['stringify']({ payload: value }) + randomString();
const plainText = Buffer.from(padded);
const secretKey = ed2curve.convertSecretKey(
sodium.crypto_hash_sha256(privateKey)
);
const encrypted = nacl.secretbox(plainText, nonce, secretKey);
return {
message: bytesToHex(encrypted),
nonce: bytesToHex(nonce),
};
};
Decrypting KVS records
To decrypt a value
stored in the KVS:
- Retrieve the encrypted message and nonce from the transaction data
- Compute the secret key as the SHA-256 hash of your ADAMANT private key
- Decrypt the message using NaCl.secretbox with:
- The encrypted message
- The nonce
- The secret key
Example using JavaScript
/**
* Decodes a secret value
* @param {string|Uint8Array} source source to decrypt
* @param {Uint8Array} privateKey private key
* @param {string|Uint8Array} nonce nonce
* @returns {string} decoded value
*/
adamant.decodeValue = function (source, privateKey, nonce) {
if (typeof source === 'string') {
source = hexToBytes(source);
}
if (typeof nonce === 'string') {
nonce = hexToBytes(nonce);
}
const secretKey = ed2curve.convertSecretKey(
sodium.crypto_hash_sha256(privateKey)
);
const decrypted = nacl.secretbox.open(source, nonce, secretKey);
const strValue = decrypted ? decode(decrypted) : '';
if (!strValue) return null;
const from = strValue.indexOf('{');
const to = strValue.lastIndexOf('}');
if (from < 0 || to < 0) {
throw new Error('Could not determine JSON boundaries in the encoded value');
}
const json = JSON.parse(strValue.substr(from, to - from + 1));
return json.payload;
};