Pre-1.0 Notice: This library is under active development. The API may change between minor versions until 1.0.
TypeScript implementation of the AlgoChat protocol for encrypted messaging on Algorand.
| Property | Status |
|---|---|
| Message content confidentiality | Protected (E2EE) |
| Message integrity | Protected (authenticated encryption) |
| Forward secrecy | Protected (ephemeral keys per message) |
| Replay attacks | Protected (blockchain uniqueness + PSK counter) |
| Quantum resistance (key exchange) | Optional (PSK mode provides defense-in-depth) |
| PSK session forward secrecy | Optional (100-message session boundaries in PSK mode) |
| Metadata privacy | Not protected (addresses, timing visible) |
| Traffic analysis | Not protected |
# npm
npm install @corvidlabs/ts-algochat
# bun
bun add @corvidlabs/ts-algochat
# pnpm
pnpm add @corvidlabs/ts-algochat
import {
AlgorandService,
createChatAccountFromMnemonic,
} from '@corvidlabs/ts-algochat';
// Initialize service
const service = new AlgorandService({
algodToken: '',
algodServer: 'https://testnet-api.algonode.cloud',
indexerToken: '',
indexerServer: 'https://testnet-idx.algonode.cloud',
});
// Create account from mnemonic
const account = createChatAccountFromMnemonic('your 25 word mnemonic...');
// Discover recipient's encryption key
const recipientKey = await service.discoverPublicKey('RECIPIENT_ADDRESS');
// Send encrypted message
const result = await service.sendMessage(
account,
'RECIPIENT_ADDRESS',
recipientKey,
'Hello from AlgoChat!'
);
console.log('Transaction ID:', result.txid);
// Fetch messages
const messages = await service.fetchMessages(account, 'RECIPIENT_ADDRESS');
// Create from mnemonic
const account = createChatAccountFromMnemonic('word1 word2 ...');
// Generate new account
const newAccount = createRandomChatAccount();
console.log('Address:', newAccount.address);
console.log('Mnemonic:', newAccount.mnemonic);
// Validate mnemonic
if (validateMnemonic('word1 word2 ...')) {
// Valid 25-word mnemonic
}
// Validate address
if (validateAddress('ALGO...')) {
// Valid Algorand address
}
// Simple message
await service.sendMessage(account, recipient, recipientKey, 'Hello!');
// Reply to a message
await service.sendReply(account, recipient, recipientKey, 'Reply text', {
txid: 'original-tx-id',
preview: 'Original message preview...',
});
// Get all messages with an address
const messages = await service.fetchMessages(account, 'ADDRESS');
// Get all conversations
const conversations = await service.fetchConversations(account);
// Discover public key
const pubKey = await service.discoverPublicKey('ADDRESS');
import {
deriveEncryptionKeys,
encryptMessage,
decryptMessage,
encodeEnvelope,
decodeEnvelope,
} from '@corvidlabs/ts-algochat';
// Derive keys from seed
const keys = deriveEncryptionKeys(seed);
// Encrypt message
const envelope = encryptMessage(
'Hello!',
senderPrivateKey,
senderPublicKey,
recipientPublicKey
);
// Encode for transmission
const bytes = encodeEnvelope(envelope);
// Decode received envelope
const decoded = decodeEnvelope(bytes);
// Decrypt message
const content = decryptMessage(decoded, myPrivateKey, myPublicKey);
interface ChatAccount {
address: string;
publicKey: Uint8Array;
privateKey: Uint8Array;
encryptionKeys: X25519KeyPair;
mnemonic?: string;
}
interface Message {
id: string;
sender: string;
recipient: string;
content: string;
timestamp: Date;
confirmedRound: number;
direction: 'sent' | 'received';
replyContext?: ReplyContext;
}
interface Conversation {
participant: string;
participantPublicKey?: Uint8Array;
messages: Message[];
lastMessage?: Message;
}
This library implements the AlgoChat Protocol v1 and the PSK v1.1 extension.
[version: 1][protocol: 1][sender_pubkey: 32][ephemeral_pubkey: 32][nonce: 12][encrypted_sender_key: 48][ciphertext: variable]
[version: 1][protocol: 2][ratchet_counter: 4][sender_pubkey: 32][ephemeral_pubkey: 32][nonce: 12][encrypted_sender_key: 48][ciphertext: variable]
| Function | Algorithm |
|---|---|
| Key Agreement | X25519 ECDH |
| Encryption | ChaCha20-Poly1305 |
| Key Derivation | HKDF-SHA256 |
The PSK (Pre-Shared Key) v1.1 protocol adds an additional layer of authentication and security on top of standard ECDH encryption by incorporating a pre-shared key into the key derivation process.
PSK mode provides defense against future quantum attacks through hybrid key derivation:
symmetricKey = HKDF(
ikm = ephemeralECDH || currentPSK,
salt = ephemeralPublicKey,
info = "algochat-psk-v1" || senderPubKey || recipientPubKey
)
The encryption key is derived from both the ephemeral ECDH shared secret and the ratcheted PSK, concatenated before HKDF. This means an attacker must break both layers:
This hybrid approach ensures that even if quantum computers eventually break X25519 ECDH, messages encrypted with PSK mode remain secure as long as the pre-shared key was exchanged securely.
PSK mode derives per-message keys using a two-level ratchet:
sessionPSK = HKDF(initialPSK, sessionIndex) where sessionIndex = counter / 100currentPSK = HKDF(sessionPSK, position) where position = counter % 100This creates 100-message session boundaries. Compromising a session PSK exposes at most 100 messages.
PSK exchange URIs are designed for QR code sharing:
algochat-psk://v1?addr=<algorand_address>&psk=<base64url>&label=<optional>
Use any QR library (e.g., qrcode) to encode the URI for easy scanning between devices.
import {
derivePSKAtCounter,
encryptPSKMessage,
decryptPSKMessage,
encodePSKEnvelope,
decodePSKEnvelope,
isPSKMessage,
createPSKState,
advanceSendCounter,
validateCounter,
recordReceive,
createPSKExchangeURI,
parsePSKExchangeURI,
} from '@corvidlabs/ts-algochat';
// Derive PSK for a specific counter
const psk = derivePSKAtCounter(sharedSecret, counter);
// Encrypt a PSK message
const envelope = encryptPSKMessage(
'Hello with PSK!',
senderPublicKey,
recipientPublicKey,
psk,
counter,
);
// Encode for transmission
const bytes = encodePSKEnvelope(envelope);
// Check if received data is a PSK message
if (isPSKMessage(bytes)) {
const decoded = decodePSKEnvelope(bytes);
const content = decryptPSKMessage(decoded, myPrivateKey, myPublicKey, psk);
}
// Counter state management
let state = createPSKState();
const { counter: sendCounter, state: newState } = advanceSendCounter(state);
state = newState;
// Exchange URI for out-of-band key sharing
const uri = createPSKExchangeURI('ALGO_ADDRESS', pskBytes, 'My Chat');
const parsed = parsePSKExchangeURI(uri);
bun test
This implementation is fully compatible with:
MIT License - See LICENSE for details.