@encra/core
Pure cryptographic primitives — zero framework dependencies. Works in Node.js 18+, all modern browsers, and React Native. All operations are built on libsodium via libsodium-wrappers.
Installation
npm install @encra/coreawait sodiumReady() once before using any function — or it will be called automatically on first use.import { sodiumReady } from '@encra/core'
// In your app entry point or server startup:
await sodiumReady()Key pairs
Encra uses X25519 key pairs for all asymmetric operations — key exchange, shared secret derivation, and Double Ratchet initialisation.
import { generateKeyPair, exportKey, importKey, deriveSharedSecret } from '@encra/core'
// Generate an X25519 key pair
const keyPair = await generateKeyPair()
// keyPair.publicKey — Uint8Array (32 bytes)
// keyPair.privateKey — Uint8Array (32 bytes)
// Serialize to URL-safe base64 (for storage / transmission)
const pubB64 = exportKey(keyPair.publicKey) // string
const privB64 = exportKey(keyPair.privateKey) // string
// Deserialize back to Uint8Array
const pubKey = importKey(pubB64)
const privKey = importKey(privB64)
// Derive a shared secret (ECDH — both sides get the same 32-byte key)
const shared = await deriveSharedSecret(myPrivateKey, theirPublicKey)| Prop | Type | Default | Description |
|---|---|---|---|
| generateKeyPair() | Promise<KeyPair> | — | Generate a new random X25519 key pair. |
| exportKey(key) | string | — | Encode a Uint8Array key to URL-safe base64 (no padding). |
| importKey(b64) | Uint8Array | — | Decode a URL-safe base64 string back to a Uint8Array. |
| deriveSharedSecret(privateKey, publicKey) | Promise<Uint8Array> | — | Perform X25519 ECDH. Returns the 32-byte shared secret. |
| sodiumReady() | Promise<void> | — | Wait for libsodium WASM to finish loading. Safe to call multiple times. |
encrypt / decrypt
Symmetric encryption using XSalsa20-Poly1305. Requires a 32-byte key (typically a shared secret from deriveSharedSecret). A fresh random nonce is generated on every encrypt call.
import { encrypt, decrypt, deriveSharedSecret } from '@encra/core'
const shared = await deriveSharedSecret(alicePrivKey, bobPubKey)
// Encrypt
const { ciphertext, nonce } = await encrypt('Hello, Bob!', shared)
// ciphertext — Uint8Array
// nonce — Uint8Array (24 bytes, random, unique per call)
// Decrypt
const plaintext = await decrypt({ ciphertext, nonce }, shared)
// plaintext — stringencryptField / decryptField
Standalone field-level encryption — no server, no React, no key pair required. Provide a 32-byte symmetric key and encrypt individual string values for storage in a database. Each call generates a fresh random nonce.
Ideal for HIPAA / GDPR compliance: store encrypted columns in your database and decrypt only when needed, client-side.
import { encryptField, decryptField, generateFieldKey } from '@encra/core'
// Generate a 32-byte symmetric key (store this securely)
const key = await generateFieldKey()
// Encrypt a field value
const encrypted = await encryptField('123-45-6789', key)
// encrypted.ciphertext — URL-safe base64 string
// encrypted.nonce — URL-safe base64 string
// Store both in your database:
// INSERT INTO patients (ssn_ciphertext, ssn_nonce) VALUES (?, ?)
// Decrypt when reading back
const ssn = await decryptField(encrypted, key)
// ssn === '123-45-6789'| Prop | Type | Default | Description |
|---|---|---|---|
| generateFieldKey() | Promise<Uint8Array> | — | Generate a cryptographically random 32-byte symmetric key. |
| encryptField(value, key) | Promise<EncryptedField> | — | Encrypt a string value with a 32-byte key. Returns { ciphertext, nonce } as URL-safe base64 strings. |
| decryptField(encrypted, key) | Promise<string> | — | Decrypt an EncryptedField. Throws DecryptionFailedError if the key is wrong or the ciphertext is tampered. |
interface EncryptedField {
ciphertext: string // URL-safe base64, XSalsa20-Poly1305
nonce: string // URL-safe base64, 24 random bytes
}💡 Key management is your responsibility
generateFieldKey returns a raw key — store it in a secrets manager (AWS Secrets Manager, Vault, etc.) or derive it from a user-controlled passphrase. Never store the key in the same database as the ciphertext.generateFingerprint
Generate Signal-style safety numbers — a human-readable fingerprint of two public keys that both parties can compare out-of-band to verify they're talking to each other (not an impostor).
import { generateFingerprint } from '@encra/core'
const fingerprint = await generateFingerprint(alicePubKey, bobPubKey)
// e.g. "12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 67890"
// Display to both users — if they match, the channel is authenticTyped errors
All errors thrown by @encra/core extend a common base so you can handle them specifically.
import {
DecryptionFailedError,
InvalidKeyError,
KeyNotFoundError,
} from '@encra/core'
try {
const text = await decrypt({ ciphertext, nonce }, wrongKey)
} catch (err) {
if (err instanceof DecryptionFailedError) {
// Ciphertext tampered or wrong key
}
if (err instanceof InvalidKeyError) {
// Key is wrong length or format
}
}TypeScript types
import type {
KeyPair, // { publicKey: Uint8Array, privateKey: Uint8Array }
EncryptedField, // { ciphertext: string, nonce: string }
MessageHeader, // Double Ratchet wire header
} from '@encra/core'ℹ DoubleRatchet
DoubleRatchet class is exported from @encra/corefor advanced use cases, but most apps should use the higher-level hooks in @encra/react or @encra/client instead.