Overview
The Memo utility provides AES encryption and decryption for private messages between Hive users. It uses ECDH (Elliptic Curve Diffie-Hellman) key exchange to derive shared secrets and AES encryption for secure communication.
Messages must start with # to be encrypted/decrypted. Plain text messages (without #) are returned unchanged.
Methods
encode
Encrypts a memo using AES encryption for secure private messaging on Hive.
const encryptedMemo = Memo.encode(privateKey, publicKey, memo, testNonce?)
privateKey
string | PrivateKey
required
Sender’s private memo key (WIF string or PrivateKey instance)
publicKey
string | PublicKey
required
Recipient’s public memo key (string or PublicKey instance)
Message to encrypt. Must start with # for encryption. Plain text (without #) is returned unchanged.
Optional nonce for testing purposes (advanced usage)
Encrypted memo string prefixed with #, or original string if not encrypted
Example
import { Memo, PrivateKey, PublicKey } from 'hive-tx'
// Alice's memo key
const alicePrivate = PrivateKey.fromLogin('alice', 'password', 'memo')
// Bob's memo key (public)
const bobPrivate = PrivateKey.fromLogin('bob', 'password', 'memo')
const bobPublic = bobPrivate.createPublic()
// Encrypt message from Alice to Bob
const message = '#Hello Bob, this is a secret message!'
const encrypted = Memo.encode(alicePrivate, bobPublic, message)
console.log('Encrypted:', encrypted)
// Output: #base58_encoded_encrypted_data...
// Plain text message (no encryption)
const plainText = Memo.encode(alicePrivate, bobPublic, 'Public message')
console.log('Plain text:', plainText)
// Output: Public message
decode
Decrypts an encrypted memo using AES decryption.
const decryptedMemo = Memo.decode(privateKey, memo)
privateKey
string | PrivateKey
required
Recipient’s private memo key (WIF string or PrivateKey instance)
Encrypted memo string. Must start with # for decryption. Plain text (without #) is returned unchanged.
Decrypted memo content with # prefix, or original string if not encrypted
Example
import { Memo, PrivateKey } from 'hive-tx'
// Bob receives an encrypted memo
const bobPrivate = PrivateKey.fromLogin('bob', 'password', 'memo')
// Encrypted memo received in a transfer
const encryptedMemo = '#4LmkPLQKBkfCHhfJ...' // From transaction
// Decrypt the memo
const decrypted = Memo.decode(bobPrivate, encryptedMemo)
console.log('Decrypted:', decrypted)
// Output: #Hello Bob, this is a secret message!
// Plain text memo (no decryption needed)
const plainText = Memo.decode(bobPrivate, 'Public message')
console.log('Plain text:', plainText)
// Output: Public message
Complete Example: Encrypted Transfer
import { Transaction, PrivateKey, Memo } from 'hive-tx'
async function sendEncryptedTransfer() {
// Alice's keys
const aliceActiveKey = PrivateKey.fromLogin('alice', 'password', 'active')
const aliceMemoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
// Bob's public memo key (get from blockchain or derive)
const bobMemoKey = PrivateKey.fromLogin('bob', 'bobpassword', 'memo')
const bobPublicMemo = bobMemoKey.createPublic()
// Encrypt the memo
const message = '#Payment for web development services - Invoice #12345'
const encryptedMemo = Memo.encode(aliceMemoKey, bobPublicMemo, message)
// Create transfer with encrypted memo
const tx = new Transaction()
await tx.addOperation('transfer', {
from: 'alice',
to: 'bob',
amount: '50.000 HIVE',
memo: encryptedMemo
})
// Sign with active key
tx.sign(aliceActiveKey)
// Broadcast
const result = await tx.broadcast()
console.log('Transfer sent:', result.tx_id)
console.log('Encrypted memo:', encryptedMemo)
}
sendEncryptedTransfer()
Decrypting Received Memos
import { Memo, PrivateKey } from 'hive-tx'
import { Client } from '@hiveio/dhive'
const client = new Client(['https://api.hive.blog'])
async function readEncryptedMemos(username: string, memoKey: PrivateKey) {
// Get recent account history
const history = await client.database.getAccountHistory(username, -1, 100)
// Filter for transfers to this account
const transfers = history.filter(([, op]) => {
return op[0] === 'transfer' && op[1].to === username
})
// Decrypt memos
for (const [, op] of transfers) {
const transfer = op[1]
if (transfer.memo.startsWith('#')) {
try {
const decrypted = Memo.decode(memoKey, transfer.memo)
console.log('From:', transfer.from)
console.log('Amount:', transfer.amount)
console.log('Memo:', decrypted)
} catch (error) {
console.error('Failed to decrypt memo:', error.message)
}
} else {
console.log('Plain text memo:', transfer.memo)
}
}
}
// Usage
const bobMemoKey = PrivateKey.fromLogin('bob', 'password', 'memo')
readEncryptedMemos('bob', bobMemoKey)
Bidirectional Encryption
Memos can be decrypted by both sender and recipient:
import { Memo, PrivateKey } from 'hive-tx'
// Alice's keys
const aliceMemoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const alicePublicMemo = aliceMemoKey.createPublic()
// Bob's keys
const bobMemoKey = PrivateKey.fromLogin('bob', 'password', 'memo')
const bobPublicMemo = bobMemoKey.createPublic()
// Alice encrypts message to Bob
const message = '#Secret meeting at noon'
const encrypted = Memo.encode(aliceMemoKey, bobPublicMemo, message)
console.log('Encrypted:', encrypted)
// Bob decrypts (as recipient)
const bobDecrypted = Memo.decode(bobMemoKey, encrypted)
console.log('Bob reads:', bobDecrypted)
// Output: #Secret meeting at noon
// Alice can also decrypt her own sent message
const aliceDecrypted = Memo.decode(aliceMemoKey, encrypted)
console.log('Alice reads:', aliceDecrypted)
// Output: #Secret meeting at noon
Unicode Support
Memos fully support Unicode characters:
import { Memo, PrivateKey } from 'hive-tx'
const aliceMemoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const bobMemoKey = PrivateKey.fromLogin('bob', 'password', 'memo')
const bobPublicMemo = bobMemoKey.createPublic()
// Messages with Unicode
const messages = [
'#Hello 世界 🌍',
'#Café ☕',
'#Привет мир',
'#مرحبا بالعالم',
'#🚀 To the moon! 🌙'
]
for (const message of messages) {
const encrypted = Memo.encode(aliceMemoKey, bobPublicMemo, message)
const decrypted = Memo.decode(bobMemoKey, encrypted)
console.log('Original:', message)
console.log('Encrypted:', encrypted)
console.log('Decrypted:', decrypted)
console.log('Match:', message === decrypted)
console.log('---')
}
Fetching Public Memo Keys
To encrypt a memo, you need the recipient’s public memo key:
import { Memo, PrivateKey, PublicKey } from 'hive-tx'
import { Client } from '@hiveio/dhive'
const client = new Client(['https://api.hive.blog'])
async function encryptMemoForUser(
senderMemoKey: PrivateKey,
recipientUsername: string,
message: string
): Promise<string> {
// Fetch recipient's account data
const [account] = await client.database.getAccounts([recipientUsername])
if (!account) {
throw new Error(`Account ${recipientUsername} not found`)
}
// Get memo key from account
const recipientPublicMemo = PublicKey.fromString(account.memo_key)
// Encrypt message
const encrypted = Memo.encode(senderMemoKey, recipientPublicMemo, message)
return encrypted
}
// Usage
const aliceMemoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const encrypted = await encryptMemoForUser(
aliceMemoKey,
'bob',
'#Private message for Bob'
)
console.log('Encrypted memo:', encrypted)
Error Handling
import { Memo, PrivateKey } from 'hive-tx'
const memoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const bobPublicKey = PrivateKey.fromLogin('bob', 'password', 'memo').createPublic()
try {
// Encryption error (environment check)
const encrypted = Memo.encode(memoKey, bobPublicKey, '#Test')
console.log('Encrypted successfully')
} catch (error) {
console.error('Encryption failed:', error.message)
// Might output: This environment does not support encryption.
}
try {
// Decryption error (wrong key or corrupted data)
const corrupted = '#InvalidBase58Data'
const decrypted = Memo.decode(memoKey, corrupted)
} catch (error) {
console.error('Decryption failed:', error.message)
}
Plain Text vs Encrypted
Memos without the # prefix are treated as plain text:
import { Memo, PrivateKey } from 'hive-tx'
const memoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const bobPublicKey = PrivateKey.fromLogin('bob', 'password', 'memo').createPublic()
// Encrypted (starts with #)
const encrypted = Memo.encode(memoKey, bobPublicKey, '#Private message')
console.log('Encrypted:', encrypted.startsWith('#')) // true
console.log('Length:', encrypted.length) // Long base58 string
// Plain text (no #)
const plainText = Memo.encode(memoKey, bobPublicKey, 'Public message')
console.log('Plain text:', plainText) // 'Public message'
console.log('Unchanged:', plainText === 'Public message') // true
// Decoding plain text returns as-is
const decoded = Memo.decode(memoKey, 'Public message')
console.log('Decoded:', decoded) // 'Public message'
Security Considerations
Memo Security Best Practices:
- Use memo keys only: Never use active or owner keys for memo encryption
- Memo key compromise: If your memo key is compromised, past encrypted memos can be decrypted
- Public visibility: Encrypted memos are visible on the blockchain, only the content is encrypted
- Key management: Store memo private keys securely, separate from other keys
- Sensitive data: Avoid putting highly sensitive data in memos; they’re not as secure as off-chain encryption
Secure Memo Example
import { Memo, PrivateKey } from 'hive-tx'
// ✅ GOOD: Use memo key for encryption
const memoKey = PrivateKey.fromLogin('alice', 'password', 'memo')
const bobMemoPublic = PrivateKey.fromLogin('bob', 'password', 'memo').createPublic()
const encrypted = Memo.encode(memoKey, bobMemoPublic, '#Secret message')
// ❌ BAD: Using active key for memos
const activeKey = PrivateKey.fromLogin('alice', 'password', 'active')
// Don't do this - use dedicated memo keys
How It Works
Memo encryption uses the following process:
- Shared Secret: ECDH key exchange between sender’s private memo key and recipient’s public memo key creates a shared secret
- AES Encryption: The message is encrypted with AES using the shared secret as the key
- Nonce: A random nonce ensures different ciphertexts for identical messages
- Checksum: A checksum is included to detect corruption
- Base58 Encoding: The encrypted data is encoded in Base58 for transmission
- Format:
#[base58_encoded(from_key + to_key + nonce + checksum + encrypted_message)]
Type Definition
type Memo = {
encode(
privateKey: string | PrivateKey,
publicKey: string | PublicKey,
memo: string,
testNonce?: any
): string
decode(privateKey: string | PrivateKey, memo: string): string
}
See Also