Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mahdiyari/hive-tx/llms.txt

Use this file to discover all available pages before exploring further.

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)
memo
string
required
Message to encrypt. Must start with # for encryption. Plain text (without #) is returned unchanged.
testNonce
any
Optional nonce for testing purposes (advanced usage)
returns
string
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)
memo
string
required
Encrypted memo string. Must start with # for decryption. Plain text (without #) is returned unchanged.
returns
string
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:
  1. Use memo keys only: Never use active or owner keys for memo encryption
  2. Memo key compromise: If your memo key is compromised, past encrypted memos can be decrypted
  3. Public visibility: Encrypted memos are visible on the blockchain, only the content is encrypted
  4. Key management: Store memo private keys securely, separate from other keys
  5. 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:
  1. Shared Secret: ECDH key exchange between sender’s private memo key and recipient’s public memo key creates a shared secret
  2. AES Encryption: The message is encrypted with AES using the shared secret as the key
  3. Nonce: A random nonce ensures different ciphertexts for identical messages
  4. Checksum: A checksum is included to detect corruption
  5. Base58 Encoding: The encrypted data is encoded in Base58 for transmission
  6. 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

Build docs developers (and LLMs) love