Hiero Message Box is a simple way for users to set up a message box and receive private messages, for example getting alerts about security communications about their assets or wallet, etc.
This implementation follows the specifications defined in the HIP-1334.
View the interactive presentation to visualize the message box flow.
The repo contains the code both for the sender and the receiver.
The goal is to enable users to send encrypted messages to an account's message box just like this:
npm run send-message -- 0.0.1441 "This is a secret message for you"Users can create the message box with this command:
npm run setup-message-boxUsers can listen for new messages in real-time using this command:
npm run listen-for-new-messagesUsers can also check for historical messages using this command:
npm run check-messages -- [start-sequence] [end-sequence]On first setup, the program generates/derives encryption keys, creates a Hedera topic as your message box, and updates your account memo with the topic ID in HIP-1334 format.
- Two-Key System: Separates transaction payer from message box owner
- PAYER_PRIVATE_KEY: Pays for all Hedera transactions
- MESSAGE_BOX_OWNER_PRIVATE_KEY: Signs messages to prove ownership
- Enables third-party services to pay for users while maintaining user control
- Ownership Verification: Cryptographic signatures prove message box ownership
- First message signed with owner's Hedera private key
- Senders verify signature against Mirror Node before sending
- Prevents sending to compromised or fraudulent message boxes
- Dual Encryption Support: Choose between RSA-2048 or ECIES (Elliptic Curve Integrated Encryption Scheme)
- RSA Mode: Traditional RSA-2048 keys stored in
data/folder (works with all key types) - ECIES Mode: Uses your Hedera operator's SECP256K1 key (no separate key files needed)
- RSA Mode: Traditional RSA-2048 keys stored in
- Automatic Key Management: RSA keys are auto-generated, ECIES keys are derived from your operator credentials
- Hedera Topics: Creates and manages Hedera topics for message distribution
- Key Verification: Automatically verifies local keys match the topic's public key
- Mirror Node API: Uses Hedera Mirror Node for all read operations (account validation, memo retrieval, message polling, topic verification)
- Real-time Listening: Continuously polls for new encrypted messages every 3 seconds
- Message Formats: Supports both JSON and CBOR encoding formats for flexibility
- Chunked Messages: Automatically handles messages larger than 1KB split across multiple chunks by HCS
- Modular Architecture: Common functions extracted for reusability and maintainability
- Minimal External Dependencies: Uses only Hashgraph SDK v2.76.0 and native Node.js crypto module
- Node.js (v14 or higher recommended, v18+ for best compatibility)
- A Hedera testnet or mainnet account
- Get a free testnet account at: https://portal.hedera.com/register
-
Clone or download this repository
-
Install dependencies:
npm install
-
Create a
.envfile with your Hedera credentials:cp .env.example .env
-
Edit
.envand add your Hedera account details:
# Two-Key System Configuration
# PAYER_PRIVATE_KEY: Account that pays for all Hedera transactions
PAYER_ACCOUNT_ID=0.0.xxxxx
PAYER_PRIVATE_KEY=302e020100300506032b657004220420...
# MESSAGE_BOX_OWNER_PRIVATE_KEY: Account that owns and signs the message box
# - If not set, defaults to PAYER_PRIVATE_KEY (operator owns the message box)
# - Allows third-party services to pay for transactions on behalf of users
MESSAGE_BOX_OWNER_ACCOUNT_ID=0.0.xxxxx
MESSAGE_BOX_OWNER_PRIVATE_KEY=302e020100300506032b657004220420...
# Encryption Configuration (optional - defaults to RSA)
# Options: RSA, ECIES
# RSA: Uses RSA-2048 keys (generated and stored in data/ folder)
# ECIES: Uses operator's SECP256K1 key for encryption (derived from MESSAGE_BOX_OWNER_PRIVATE_KEY)
# Note: ECIES requires SECP256K1 - ED25519 keys are not supported
ENCRYPTION_TYPE=RSA
# Data directory for RSA keys (optional - defaults to ./data)
RSA_DATA_DIR=./data
# Network Configuration (optional - defaults to testnet)
HEDERA_NETWORK=testnet
MIRROR_NODE_URL=https://testnet.mirrornode.hedera.com
For mainnet, change to:
HEDERA_NETWORK=mainnet
MIRROR_NODE_URL=https://mainnet.mirrornode.hedera.com
The Hiero Message Box supports two encryption methods:
| Feature | RSA (Default) | ECIES |
|---|---|---|
| Key Management | Generate & store PEM files | Uses your operator key |
| Key Type Support | All (ED25519, SECP256K1) | SECP256K1 only |
| Public Key Size | 294 bytes | 33-65 bytes |
| Setup Time | ~50ms (key generation) | <1ms (key derivation) |
| Security | RSA-2048 + AES-256-CBC | ECDH + AES-256-GCM |
| Files to Backup | data/rsa_*.pem |
None (uses .env) |
Use RSA if:
- You already have a message box and want to keep it
- Your Hedera account uses ED25519 keys
- You prefer separate encryption keys from your operator key
Use ECIES if:
- Your Hedera account uses SECP256K1 keys
- You want to use your Hedera key for everything
- You want faster setup with no key file management
To enable ECIES, add to your .env:
ENCRYPTION_TYPE=ECIESNote: ED25519 keys cannot use ECIES (signature algorithm, no ECDH support). The system will prompt to switch to RSA if needed.
npm run setup-message-boxThe setup process:
- Loads/generates encryption keys (RSA:
data/*.pem, ECIES: derived fromHEDERA_PRIVATE_KEY) - Checks existing message box in account memo
- Verifies keys can decrypt messages
- Creates new topic if needed, publishes public key
- Updates account memo with topic ID:
[HIP-1334:0.0.xxxxx]
Start the listener to continuously poll for and receive encrypted messages:
npm run listen-for-new-messages
# or
npm startNote: npm start runs setup then starts listening.
Polls Mirror Node every 3 seconds, automatically detects and decrypts messages. Press Ctrl+C to stop.
Retrieve and read messages from your message box in a specific range:
npm run check-messages -- [start-sequence] [end-sequence]Examples:
# Get all messages from sequence 2 onwards (default)
npm run check-messages
# Get all messages from sequence 5 onwards
npm run check-messages -- 5
# Get messages from sequence 5 to 10 (inclusive)
npm run check-messages 5 10Retrieves and decrypts messages in the specified range with timestamps and sequence numbers.
Send an encrypted message to another account:
npm run send-message -- <account-id> <message> [--cbor]Examples:
npm run send-message -- 0.0.1441 "Hello, secret message!"
npm run send-message -- 0.0.1441 "Hello, secret message!" --cborNote: Use -- to separate npm options from script arguments.
- JSON (default): Human-readable, easy to debug (~510 bytes typical message)
- CBOR (optional): Binary format, ~3-5% smaller (~491 bytes), best for high-volume scenarios
Both formats are auto-detected when reading messages.
- Fetches recipient's account memo and public key from topic
- Auto-detects encryption type (RSA or ECIES)
- Encrypts message (RSA: AES-256+RSA-2048, ECIES: ECDH+AES-256-GCM)
- Sends encrypted payload to topic (JSON or CBOR)
Recipients automatically detect and decrypt messages when polling.
HCS automatically splits messages >1KB into chunks. This application transparently reassembles them before decryption—no size limit.
To remove your message box configuration (clears your account memo):
npm run remove-message-boxClears account memo but doesn't delete the topic or keys.
Hybrid encryption: AES-256-CBC for messages + RSA-2048-OAEP for key exchange. Supports all key types, works with any length messages.
Uses ECDH (secp256k1) + AES-256-GCM. Provides smaller public keys (33 bytes vs 294), and derives keys from your operator credentials. Requires SECP256K1 (ED25519 not supported).
The codebase is organized into three main modules:
-
lib/crypto.js: Cryptographic operations- Environment variable loading from
.envfile - RSA hybrid encryption/decryption (AES-256-CBC + RSA-2048-OAEP)
- ECIES encryption/decryption (ECDH + AES-256-GCM)
- Encryption type detection and routing
- Custom CBOR encoder/decoder implementation (RFC 8949 compliant)
- Message signing and signature verification (ED25519, ECDSA_SECP256K1)
- DER encoding helpers for public/private keys
- Environment variable loading from
-
lib/hedera.js: Hedera blockchain operations- Client initialization (testnet/mainnet)
- Account memo read (via Mirror Node) and update (via Hedera SDK)
- Account validation and public key retrieval using Mirror Node API
- Topic creation and message submission
- Mirror Node URL configuration
- Topic message queries with pagination support
- Hedera key parsing and public key derivation (SECP256K1, ED25519)
- Transaction execution and signing helpers
-
lib/message-box.js: Core message box logic- Two-key system support (payer and owner separation)
- Message box setup with ownership signature generation
- RSA key pair generation and management
- ECIES key derivation from operator credentials
- Public key publishing with cryptographic signature proof
- Signature verification before sending messages
- Message encryption and sending (JSON/CBOR formats, auto-detecting encryption type)
- Real-time message polling with sequence tracking
- Automatic format and encryption type detection and decoding
- Canonical JSON serialization for deterministic signatures
Uses Hedera Mirror Node REST API for all read operations (cost-free):
- Account validation and memo retrieval
- Topic verification and public key retrieval
- Message polling with pagination
- Historical message queries
All messages submitted to the topic use either JSON or CBOR encoding with a type field:
Public Key Message (first message in topic, always JSON):
RSA format with ownership proof:
{
"payload": {
"encryptionType": "RSA",
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhki...",
"type": "HIP-1334_PUBLIC_KEY"
},
"proof": {
"accountId": "0.0.12345",
"signerPublicKey": "a1b2c3d4...",
"signerKeyType": "ED25519",
"signature": "d5e6f7g8..."
}
}ECIES format with ownership proof:
{
"payload": {
"encryptionType": "ECIES",
"publicKey": {
"curve": "secp256k1",
"key": "03a1b2c3d4e5f6...",
"type": "ECIES"
},
"type": "HIP-1334_PUBLIC_KEY"
},
"proof": {
"accountId": "0.0.12345",
"signerPublicKey": "02a1b2c3d4...",
"signerKeyType": "ECDSA_SECP256K1",
"signature": "d5e6f7g8..."
}
}The proof section contains a cryptographic signature of the payload using the message box owner's Hedera private key. Senders verify this signature against the account's public key from Mirror Node before sending messages, preventing fraudulent message boxes.
Encrypted Message (JSON format):
RSA:
{
"type": "HIP-1334_ENCRYPTED_MESSAGE",
"format": "json",
"data": {
"type": "RSA",
"encryptedKey": "base64...",
"iv": "base64...",
"encryptedData": "base64..."
}
}ECIES:
{
"type": "HIP-1334_ENCRYPTED_MESSAGE",
"format": "json",
"data": {
"type": "ECIES",
"ephemeralPublicKey": "hex...",
"iv": "base64...",
"encryptedData": "base64...",
"authTag": "base64...",
"curve": "secp256k1"
}
}Encrypted Message (CBOR format): Same structure as JSON, more compact.
Messages are auto-detected (format: JSON/CBOR/plain, encryption: RSA/ECIES) and decrypted accordingly.
./
├── src/
│ ├── setup-message-box.js # Setup message box for account
│ ├── check-messages.js # Check existing messages inside the message box
│ ├── listen-for-new-messages.js # Listener/Receiver application
│ ├── send-message.js # Sender application
│ ├── remove-message-box.js # Remove message box configuration
│ └── lib/
│ ├── common.js # Common utilities (encryption, env loading, CBOR)
│ ├── hedera.js # Hedera SDK wrappers, client init, key parsing
│ └── message-box.js # Core message box logic (setup, send, poll)
├── data/
│ ├── rsa_private.pem # RSA private key (auto-generated, RSA mode only)
│ └── rsa_public.pem # RSA public key (auto-generated, RSA mode only)
├── docs/ # Documentation and presentations
├── package.json # Dependencies and scripts
├── .env # Hedera credentials and config (not committed)
├── .env.example # Example environment file
└── .gitignore # Git ignore rules
npm start # Setup message box and start listening for new messages
npm run setup-message-box # Setup/verify message box configuration
npm run listen-for-new-messages # Start polling for new messages
npm run check-messages -- [start] [end] # Read message history (defaults to all messages)
npm run send-message -- <account id> <msg> [--cbor] # Send encrypted message to account
npm run remove-message-box # Remove message box (clear account memo)
npm run format # Format code with Prettier
npm test # Run integration testsNote: Use -- to separate npm options from script arguments when passing parameters.
Run the integration test suite to verify all functionality:
npm testThe test suite covers:
- Message box setup (new and existing)
- Sending messages (JSON and CBOR formats)
- Retrieving and decrypting messages
- Message box reuse (idempotency)
- Signature verification
- Message box removal
See test/README.md for detailed test documentation.
Required variables:
# Transaction payer
PAYER_ACCOUNT_ID=0.0.xxxxx
PAYER_PRIVATE_KEY=302e020100300506032b657004220420...
# Message box owner
MESSAGE_BOX_OWNER_ACCOUNT_ID=0.0.xxxxx
MESSAGE_BOX_OWNER_PRIVATE_KEY=302e020100300506032b657004220420...
# Data directory for RSA keys
RSA_DATA_DIR=./data
Optional variables:
# Encryption type (defaults to RSA)
ENCRYPTION_TYPE=RSA # or ECIES
# Network (defaults to testnet)
HEDERA_NETWORK=testnet
MIRROR_NODE_URL=https://testnet.mirrornode.hedera.com
RSA Mode:
data/rsa_private.pem: Your private key for decryption (keep secure!)data/rsa_public.pem: Your public key (published to the topic for others to use)
ECIES Mode:
- No separate key files needed
- Keys are derived from
HEDERA_PRIVATE_KEYin.env - Requires SECP256K1 key type
- Never commit
.envor private keys - Two-key system: Separates payment from ownership
PAYER_PRIVATE_KEY: Pays for transactionsMESSAGE_BOX_OWNER_PRIVATE_KEY: Proves ownership via signatures- Enables third-party payment while maintaining user control
- RSA mode: private key in
data/rsa_private.pemfor local decryption only - ECIES mode: operator key in
.envused for transactions and decryption - Signature verification: Message box ownership uses cryptographic signatures with canonical JSON serialization
- First message signed with owner's Hedera private key
- Senders verify signature against account's public key from Mirror Node
- Ensures deterministic signature verification regardless of JSON property ordering
- Keys are sorted alphabetically before signing to prevent signature mismatch
- Prevents sending messages to compromised or fraudulent message boxes
- Missing credentials: Ensure
.envexists with validPAYER_ACCOUNT_ID,PAYER_PRIVATE_KEY,MESSAGE_BOX_OWNER_ACCOUNT_ID, andMESSAGE_BOX_OWNER_PRIVATE_KEY - Message box not found: Recipient needs to run
npm run setup-message-box - Cannot decrypt: Keys don't match topic—restore original keys or create new message box
- Signature verification failed: Message box signature doesn't match recipient's public key—possible fraudulent message box
- Encryption mismatch:
ENCRYPTION_TYPEin.envdoesn't match message box - ECIES with ED25519: ED25519 doesn't support ECIES—use RSA or SECP256K1 account
- Mirror Node errors: Check internet and verify
MIRROR_NODE_URLmatches network
- Update
ENCRYPTION_TYPEin.env(RSA or ECIES) - Run
npm run setup-message-boxto create new message box - Old message box remains accessible with original keys
Note: ECIES requires SECP256K1 key (not ED25519).
- ECIES Specification
- Node.js Crypto Documentation
- Hedera Documentation
- Hedera Key Types
- NIST Elliptic Curve Standards
- RFC 8949 - CBOR Specification
MIT