npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@majikah/majik-file

v0.1.4

Published

Majik File is the core cryptographic engine for secure file handling in the Majikah ecosystem. It provides a post-quantum secure "MJKB" format designed for file encryption, multi-recipient key encapsulation, and transparent compression using NIST-standard

Readme

Majik File

Developed by Zelijah GitHub Sponsors

Static Badge

Post-quantum file encryption for the Majik Message platform. Produces self-contained .mjkb binary files — sealed with ML-KEM-768 + AES-256-GCM, optionally Zstd-compressed, readable without any network access.

This library is designed to work within the Majik Message ecosystem. It expects callers to supply ML-KEM-768 key material from an identity store; it does not generate or persist keys itself.

npm npm downloads npm bundle size License TypeScript


Contents

How it works

raw bytes
  │
  ├─ SHA-256 hash (pre-compression — used for dedup)
  │
  ├─ [chat_image / chat_attachment only]
  │   WebP conversion via Canvas API (PNG/JPEG/GIF/BMP → WebP at quality 0.88)
  │   Skipped for SVG, HEIC, HEIF, JXL — browser cannot encode these
  │
  ├─ Zstd compress at level 22
  │   Skipped for pre-compressed formats (JPEG, WebP, AVIF, video, audio, archives, Office XML)
  │
  ├─ [single recipient]
  │   ML-KEM-768 encapsulate(ownerPublicKey)
  │   → sharedSecret (32 bytes) used directly as AES-256-GCM key
  │
  ├─ [group — 2+ recipients]
  │   Random 32-byte AES key encrypts the file once
  │   Per recipient: ML-KEM-768 encapsulate(recipientPublicKey)
  │   → encryptedAesKey = aesKey XOR sharedSecret
  │
  └─ AES-256-GCM encrypt (12-byte random IV, 16-byte auth tag)
       → .mjkb binary

The encrypted binary is self-contained: the IV, all key material, original filename, and MIME type are embedded inside the file. No sidecar files or database records are required to decrypt.


The .mjkb binary format

Version: 0x01

┌──────────────────────────────────────────────────────┐
│  4 bytes  │  Magic: ASCII "MJKB"  (0x4D 0x4A 0x4B 0x42)  │
│  1 byte   │  Version (currently 0x01)                      │
│ 12 bytes  │  AES-GCM IV (random per file)                  │
│  4 bytes  │  Payload JSON length (big-endian uint32)        │
│  N bytes  │  Payload JSON (UTF-8)                           │
│  M bytes  │  AES-GCM ciphertext (compressed plaintext + 16-byte auth tag) │
└──────────────────────────────────────────────────────┘

Fixed header: 21 bytes (before variable payload JSON)

Single-recipient payload JSON:

{
  "mlKemCipherText": "<base64, 1088 bytes>",
  "n": "photo.png",
  "m": "image/png"
}

Group payload JSON:

{
  "keys": [
    {
      "fingerprint": "<base64 SHA-256 of public key>",
      "mlKemCipherText": "<base64, 1088 bytes>",
      "encryptedAesKey": "<base64, 32 bytes>"
    }
  ],
  "n": "photo.png",
  "m": "image/png"
}

n and m use short keys to minimise binary overhead (~30–50 extra bytes per file). Both fields are string | null — null when encryption was called without providing originalName / mimeType.

Per-recipient overhead in group mode: ~1.5 KB (1088-byte ML-KEM ciphertext + 32-byte encrypted AES key, base64-encoded).


Encryption modes

Single-recipient

Used when no recipients are passed, or when recipients is empty after deduplication. The ML-KEM shared secret is used directly as the AES-256-GCM key. The owner is the only entity who can decrypt.

Group

Used when one or more recipients are supplied. The file is encrypted once with a random 32-byte AES key. Each recipient — including the owner, who is always prepended automatically — gets their own ML-KEM encapsulation entry:

encryptedAesKey = groupAesKey XOR mlKemSharedSecret

This is safe because ML-KEM shared secrets are 32 uniformly random bytes, making the XOR a one-time pad for the group key. Each recipient can independently recover groupAesKey using only their own secret key.

Recipient deduplication: If the owner's own fingerprint appears in recipients, it is silently removed (not an error). Duplicate fingerprints in recipients are also silently deduplicated — first occurrence wins.

Limit: Maximum 100 recipients (excluding the owner) per file. Exceeding this throws MajikFileError("INVALID_INPUT").


Compression behaviour

Zstd compression at level 22 is applied selectively. Files whose MIME types indicate they are already compressed at the codec level are passed through uncompressed:

| Skipped (already compressed) | Compressed with Zstd lv.22 | |---|---| | JPEG, WebP, AVIF, HEIC, HEIF, JXL | PNG, BMP, TIFF, SVG, GIF | | All video (mp4, webm, mkv, mov, …) | WAV, FLAC, AIFF | | Lossy audio (mp3, aac, ogg, opus, …) | PDF, JSON, XML, CSV | | ZIP, gzip, 7z, rar, bzip2, xz, zstd | Plain text, source code | | .docx, .xlsx, .pptx, .epub | SQLite databases |

If mimeType is null or unknown, compression is applied (safer default).


Installation

npm install @majikah/majik-file
# or
pnpm add @majikah/majik-file

This package requires a browser or browser-like environment (Electron, React Native with JSI) for:

  • crypto.randomUUID() and crypto.subtle (Web Crypto)
  • Blob / URL.createObjectURL for WebP conversion
  • HTMLCanvasElement for image re-encoding (only needed for chat_image / chat_attachment contexts)

Node.js is not a supported target.


Quick start

Encrypt a file (self, single recipient)

import { MajikFile } from '@majikah/majik-file'

// identity comes from your key store — MajikFile does not generate keys
const identity = {
  userId: 'user-uuid',
  fingerprint: 'base64-sha256-of-public-key',
  mlKemPublicKey: new Uint8Array(1184),  // ML-KEM-768 public key
  mlKemSecretKey: new Uint8Array(2400),  // ML-KEM-768 secret key
}

const fileBytes = await file.arrayBuffer()

const majikFile = await MajikFile.create({
  data: fileBytes,
  identity,
  context: 'user_upload',
  originalName: file.name,
  mimeType: file.type,
})

// Export the encrypted binary
const blob = majikFile.toMJKB()             // Blob — upload to R2
const metadata = majikFile.toJSON()         // MajikFileJSON — insert into Supabase

Decrypt a file

const { bytes, originalName, mimeType } = await MajikFile.decryptWithMetadata(
  mjkbBlob,
  { fingerprint: identity.fingerprint, mlKemSecretKey: identity.mlKemSecretKey }
)

const recovered = new Blob([bytes], { type: mimeType ?? 'application/octet-stream' })

Encrypt for multiple recipients

const majikFile = await MajikFile.create({
  data: fileBytes,
  identity: senderIdentity,
  recipients: [
    { fingerprint: 'recipient-a-fp', mlKemPublicKey: recipientAKey },
    { fingerprint: 'recipient-b-fp', mlKemPublicKey: recipientBKey },
  ],
  context: 'chat_attachment',
  originalName: 'report.pdf',
  mimeType: 'application/pdf',
})

Any of the three principals (sender, recipient A, recipient B) can decrypt using only their own mlKemSecretKey.

Temporary files

const majikFile = await MajikFile.create({
  data: fileBytes,
  identity,
  context: 'user_upload',
  isTemporary: true,
  expiresAt: MajikFile.buildExpiryDate(7), // 7 days from now
})

Chat Images

const majikFile = await MajikFile.create({
  data: imageBytes,
  identity,
  context: 'chat_image',         // triggers automatic WebP conversion
  conversationId: 'conv-uuid',   // required for chat_image
  mimeType: 'image/png',
})

Chat Attachment

const majikFile = await MajikFile.create({
  data: imageBytes,
  identity,
  context: 'chat_attachment', 
  conversationId: 'conv-uuid',   // required for chat_attachment
})

Chat Voice

const majikFile = await MajikFile.create({
  data: imageBytes,
  identity,
  context: 'chat_voice', 
  conversationId: 'conv-uuid',   // required for chat_voice
})

API reference

MajikFile.create(options)

static async create(options: CreateOptions): Promise<MajikFile>

Encrypts raw bytes and returns a MajikFile instance with both _binary (the .mjkb) and metadata populated. Throws MajikFileError on validation or crypto failure.

CreateOptions

| Field | Type | Required | Description | |---|---|---|---| | data | Uint8Array \| ArrayBuffer | ✓ | Raw file bytes to encrypt | | identity | MajikFileIdentity | ✓ | Owner's full identity (both keys) | | context | FileContext | ✓ | user_upload | chat_attachment | chat_voice | chat_image | thread_attachment | | recipients | MajikFileRecipient[] | — | Additional recipients. Empty → single-recipient mode | | originalName | string | — | Original filename (e.g. "photo.png"). Embedded in .mjkb payload | | mimeType | string | — | MIME type. Inferred from originalName extension if omitted | | isTemporary | boolean | — | Default false. Routes to files/public/ R2 prefix | | isShared | boolean | — | Default false. Enables toggleSharing() | | id | string | — | Pre-computed UUID. Auto-generated if omitted | | bypassSizeLimit | boolean | — | Default false. Bypasses the 100 MB file size cap | | expiresAt | string | — | ISO-8601. Required when isTemporary = true | | chatMessageId | string | — | FK → majik_message_chat.id. Mutually exclusive with threadMessageId | | threadMessageId | string | — | FK → majik_message_thread.id. Mutually exclusive with chatMessageId | | conversationId | string | — | Required when context = "chat_image". Scopes the R2 key |

Context behaviour:

| Context | WebP conversion | R2 prefix | |---|---|---| | user_upload | No | files/user/<userId>/<hash>.mjkb | | chat_attachment | Yes (images only) | files/public/1/<userId>_<hash>.mjkb | | chat_voice | No | files/public/1/<userId>_<hash>.mjkb | | chat_image | Yes (always) | images/chats/<conversationId>/<userId>_<hash>.mjkb | | thread_attachment | No | files/user/<userId>/<hash>.mjkb |


MajikFile.decrypt(source, identity)

static async decrypt(
  source: Blob | Uint8Array | ArrayBuffer,
  identity: Pick<MajikFileIdentity, 'fingerprint' | 'mlKemSecretKey'>
): Promise<Uint8Array>

Decrypts a .mjkb binary and returns the raw plaintext bytes. Does not return filename or MIME type — use decryptWithMetadata() if you need those.

Note on wrong keys: ML-KEM decapsulation never throws on a wrong key — it silently returns a garbage shared secret. AES-GCM authentication detects this and causes a MajikFileError("DECRYPTION_FAILED").


MajikFile.decryptWithMetadata(source, identity)

static async decryptWithMetadata(
  source: Blob | Uint8Array | ArrayBuffer,
  identity: Pick<MajikFileIdentity, 'fingerprint' | 'mlKemSecretKey'>
): Promise<{
  bytes: Uint8Array
  originalName: string | null
  mimeType: string | null
}>

Preferred method for UI use. Returns decrypted bytes alongside originalName and mimeType read directly from the .mjkb payload JSON — no second parse required.

originalName and mimeType will be null for files encrypted without those fields (i.e. encrypted before the n/m payload fields were introduced, or when originalName/mimeType were not provided at encryption time). Callers should implement fallbacks.


MajikFile.fromJSON(json, binary?)

static fromJSON(
  json: MajikFileJSON,
  binary?: Uint8Array | ArrayBuffer | null
): MajikFile

Restores a MajikFile instance from a Supabase row. The binary is optional — if omitted, the instance is metadata-only (calling toMJKB() or decryptBinary() will throw MISSING_BINARY). R2 prefix validation is intentionally skipped here to tolerate rows from older schema versions.

static async fromJSONWithBlob(json: MajikFileJSON, binary: Blob): Promise<MajikFile>

Async variant that accepts a Blob (e.g. fetched from R2).


Instance methods

| Method | Returns | Description | |---|---|---| | toJSON() | MajikFileJSON | Serialise metadata for Supabase. Binary is excluded | | toMJKB() | Blob | Export encrypted binary as application/octet-stream Blob for R2 upload | | toBinaryBytes() | Uint8Array | Export encrypted binary as raw bytes | | decryptBinary(identity) | Promise<Uint8Array> | Decrypt the in-memory binary. Throws if binary not loaded | | validate() | void | Validate all metadata invariants. Throws MajikFileError on failure | | attachBinary(binary) | void | Load or replace the encrypted binary in memory | | clearBinary() | void | Free the in-memory binary after upload | | toggleSharing(token?) | string \| null | Toggle share token on/off. Returns active token or null | | userIsOwner(userId) | boolean | Check if userId matches this file's owner | | exceedsSize(limitMB) | boolean | True if original size exceeds the given MB limit | | isDuplicateOf(other) | boolean | Compare by SHA-256 file_hash | | getStats() | MajikFileStats | Human-readable stats snapshot | | toString() | string | Debug string: id, hash prefix, size, mode, storage type |

Instance getters:

| Getter | Type | Description | |---|---|---| | id | string | UUID primary key | | userId | string | Owner's auth UUID | | r2Key | string | Full R2 object key | | originalName | string \| null | Original filename from CreateOptions | | mimeType | string \| null | Resolved MIME type | | sizeOriginal | number | Plaintext byte length | | sizeStored | number | .mjkb byte length | | sizeKB / sizeMB / sizeGB / sizeTB | number | Original size in various units (3 dp) | | fileHash | string | SHA-256 hex of original bytes (pre-compression) | | encryptionIv | string | Hex-encoded IV (audit record — authoritative IV is in the binary) | | storageType | StorageType | "permanent" or "temporary" | | isShared | boolean | Whether sharing is enabled | | shareToken | string \| null | Active share token | | hasShareToken | boolean | Shorthand for shareToken !== null | | context | FileContext \| null | File context | | chatMessageId | string \| null | FK to chat message | | threadMessageId | string \| null | FK to thread message | | conversationId | string \| null | Conversation scope (chat_image only) | | expiresAt | string \| null | ISO-8601 expiry | | timestamp | string \| null | ISO-8601 creation time | | lastUpdate | string \| null | ISO-8601 last mutation time | | hasBinary | boolean | Whether encrypted binary is loaded in memory | | isGroup | boolean | Whether the file has multiple recipient key entries | | isSingle | boolean | !isGroup | | isExpired | boolean | Whether expiresAt is in the past | | isTemporary | boolean | storageType === "temporary" | | isInlineViewable | boolean | Whether MIME type can render inline in a browser | | safeFilename | string | <fileHash><ext> — safe download name |


Static helpers

| Method | Returns | Description | |---|---|---| | MajikFile.buildExpiryDate(days?) | string | ISO-8601 expiry, default 15 days from now | | MajikFile.formatBytes(bytes) | string | Human-readable size (e.g. "4.2 MB") | | MajikFile.inferMimeType(filename) | string \| null | MIME from file extension | | MajikFile.isMjkbCandidate(data) | boolean | Magic byte check — does not fully parse | | MajikFile.hasPublicKeyAccess(pk, fp) | boolean | SHA-256 fingerprint match — not a decryption proof | | MajikFile.wouldBeDuplicate(bytes, hash) | boolean | Pre-flight dedup check by SHA-256 |


Type reference

MajikFileIdentity

interface MajikFileIdentity {
  userId: string          // auth.users UUID
  fingerprint: string     // base64 SHA-256 of mlKemPublicKey
  mlKemPublicKey: Uint8Array  // 1184 bytes — used during encryption
  mlKemSecretKey: Uint8Array  // 2400 bytes — used during decryption
}

MajikFileRecipient

interface MajikFileRecipient {
  fingerprint: string         // base64 SHA-256 of mlKemPublicKey
  mlKemPublicKey: Uint8Array  // 1184 bytes
  // No secret key — it never leaves the recipient's device
}

MajikFileGroupKey (embedded in group .mjkb)

interface MajikFileGroupKey {
  fingerprint: string      // identifies which recipient this entry belongs to
  mlKemCipherText: string  // base64, 1088 bytes
  encryptedAesKey: string  // base64, 32 bytes = groupAesKey XOR mlKemSharedSecret
}

MjkbSinglePayload

interface MjkbSinglePayload {
  mlKemCipherText: string  // base64, 1088 bytes
  n: string | null         // original filename
  m: string | null         // MIME type
}

MjkbGroupPayload

interface MjkbGroupPayload {
  keys: MajikFileGroupKey[]
  n: string | null         // original filename
  m: string | null         // MIME type
}

MajikFileJSON

Mirrors the majikah.majik_files Supabase table exactly. The encrypted binary is intentionally absent — it lives in R2.

interface MajikFileJSON {
  id: string
  user_id: string
  r2_key: string
  original_name: string | null
  mime_type: string | null
  size_original: number       // plaintext bytes
  size_stored: number         // .mjkb bytes (after compression + encryption overhead)
  file_hash: string           // SHA-256 hex of original bytes
  encryption_iv: string       // hex, 12 bytes — audit record; binary header is authoritative
  storage_type: 'permanent' | 'temporary'
  is_shared: boolean
  share_token: string | null
  context: FileContext | null
  chat_message_id: string | null
  thread_message_id: string | null
  conversation_id: string | null
  expires_at: string | null
  timestamp: string | null
  last_update: string | null
}

FileContext

type FileContext =
  | 'user_upload'        // general file vault — no WebP conversion, no size limit with bypassSizeLimit
  | 'chat_attachment'    // message attachment — images converted to WebP
  | 'chat_image'         // inline chat image — always converted to WebP, requires conversationId
  | 'thread_attachment'  // thread attachment — no WebP conversion

MajikFileStats

interface MajikFileStats {
  id: string
  originalName: string | null
  mimeType: string | null
  sizeOriginalHuman: string    // e.g. "4.2 MB"
  sizeStoredHuman: string      // e.g. "1.1 MB"
  compressionRatioPct: number  // percentage reduction, clamped to 0 minimum
  fileHash: string
  storageType: StorageType
  isGroup: boolean
  context: FileContext | null
  isShared: boolean
  isExpired: boolean
  expiresAt: string | null
  timestamp: string | null
  r2Key: string
}

Error handling

All errors thrown by this library are instances of MajikFileError.

import { MajikFileError } from '@majikah/majik-file'

try {
  const file = await MajikFile.create({ ... })
} catch (err) {
  if (err instanceof MajikFileError) {
    console.error(err.code)     // MajikFileErrorCode
    console.error(err.message)
    console.error(err.cause)    // original cause if available
  }
}

Error codes

| Code | When thrown | |---|---| | INVALID_INPUT | Missing required fields, wrong key sizes, incompatible option combinations (e.g. both chatMessageId and threadMessageId), recipient limit exceeded | | VALIDATION_FAILED | validate() found inconsistent state (all violations reported at once) | | ENCRYPTION_FAILED | Unexpected error during the crypto or compression pipeline | | DECRYPTION_FAILED | Wrong key, corrupted ciphertext (AES-GCM auth tag mismatch), missing fingerprint in group key list | | FORMAT_ERROR | Magic byte mismatch, truncated binary, malformed payload JSON | | SIZE_EXCEEDED | data.byteLength > 100 MB and bypassSizeLimit is false | | MISSING_BINARY | toMJKB(), toBinaryBytes(), or decryptBinary() called when _binary is null | | UNSUPPORTED_VERSION | .mjkb version byte is not 0x01 |

Important: A wrong decryption key does not throw INVALID_INPUT — it reaches DECRYPTION_FAILED via AES-GCM authentication failure. This is by design: ML-KEM decapsulation is deterministic and never throws on bad input.


Storage model

This library produces two distinct artefacts that must be stored separately:

| Artefact | What it is | Where it goes | |---|---|---| | toMJKB() → Blob | Encrypted binary | Cloudflare R2 at r2_key | | toJSON() → object | Metadata record | Supabase majikah.majik_files table |

The library does not perform R2 uploads or Supabase inserts itself — it only produces the data. Upload and persistence are the caller's responsibility (typically handled by MajikMessage.encryptFile()).

R2 key structure:

Permanent:  files/user/<userId>/<fileHash>.mjkb
Temporary:  files/public/<userId>_<fileHash>.mjkb
Chat image: images/chats/<conversationId>/<userId>_<fileHash>.mjkb

Temporary files (files/public/) are expected to be auto-deleted by an R2 lifecycle policy targeting that prefix after ~15 days. The library enforces expiresAt at the metadata level, but bucket-level deletion is an infrastructure concern.

File immutability: .mjkb files are write-once. There is no update() or patch() on encrypted fields. To replace a file, delete the R2 object and Supabase row, then call MajikFile.create() again.

Deduplication: file_hash is a SHA-256 hex digest of the original plaintext bytes, computed pre-compression and pre-WebP-conversion. This means the same source file always produces the same hash regardless of context. Use MajikFile.wouldBeDuplicate(rawBytes, existingHash) to short-circuit re-encryption.


Limitations and honest caveats

Key management is your problem. This library assumes you hand it a valid ML-KEM-768 key pair. It does not generate, store, rotate, or protect keys. Identity unlocking, passphrase-based key derivation, and secure key storage are handled elsewhere in the Majik Message stack.

No key revocation. Once a .mjkb file is encrypted for a recipient, that recipient retains access as long as they have their secret key. There is no mechanism to revoke access to an already-distributed .mjkb binary short of deleting it from R2.

Metadata is partially in-band, partially out-of-band. The .mjkb binary embeds originalName (n) and mimeType (m) in the payload JSON. All other metadata (user_id, r2_key, context, etc.) lives only in Supabase. If you have a .mjkb file but no database row, you can decrypt the content but not recover those fields.

Old binaries lack n/m fields. The payload n/m fields were added in a later revision. Files encrypted before this change will return null for both fields from decryptWithMetadata(). Callers must handle this gracefully.

WebP conversion is best-effort. The Canvas API conversion path can fail silently (unsupported format, canvas unavailable in the environment). In all failure cases the original bytes are used unchanged — the conversion is never a hard requirement.

No streaming. The entire file is read into memory before encryption and held in memory after decryption. This is a deliberate simplicity trade-off; it is not suitable for multi-GB files even with bypassSizeLimit: true.

MajikFile.hasPublicKeyAccess() is not a security primitive. It hashes a public key and compares to a stored fingerprint. It does not prove the caller controls the corresponding secret key. Use decrypt() for cryptographic proof of access.

Zstd WASM. Compression depends on @bokuweb/zstd-wasm. In Vite projects, the WASM file requires the dev server's fs.allow list to include the package's node_modules directory, and the package should be excluded from optimizeDeps. See the Vite config section of your project setup.

Format version. The current .mjkb format is version 0x01. Attempting to decode a binary with a different version byte throws MajikFileError("UNSUPPORTED_VERSION"). There is currently no migration path for old binaries.


Cryptographic Parameters

| Primitive | Parameters | Role | |---|---|---| | ML-KEM-768 (FIPS 203) | PK: 1184 B, SK: 2400 B, CT: 1088 B | Key encapsulation — post-quantum | | AES-256-GCM | 32-byte key, 12-byte IV, 16-byte auth tag | Symmetric authenticated encryption | | Zstd | Level 22 (maximum) | Pre-encryption compression | | SHA-256 | — | File deduplication hash, public key fingerprints | | CSPRNG | crypto.getRandomValues | IV generation, group AES key generation |

ML-KEM-768 provides NIST security category 3 (roughly equivalent to AES-192). The hybrid construction (ML-KEM for key encapsulation + AES-256-GCM for bulk encryption) means the security of the scheme is bounded by both primitives — currently AES-256-GCM is the stronger of the two against classical adversaries, while ML-KEM-768 provides the post-quantum security.

Related Projects

Majik Message

Secure messaging platform using Majik Keys

npm npm downloads npm bundle size License TypeScript

Read more about Majik Message here

Majik Message Thumbnail

Click the image to try Majik Message live.

Read Docs

Also available on Microsoft Store for free.

Official Repository SDK Library


Majik Key

Majik Key is a seed phrase account library for creating, managing, and parsing mnemonic-based cryptographic accounts (Majik Keys). Generate deterministic key pairs from BIP39 seed phrases with simple, developer-friendly APIs. Now supports ML-KEM-768 post-quantum key derivation alongside X25519.

npm npm downloads npm bundle size License TypeScript

Read Docs Official Repository SDK Library


Majik Envelope

Majik Envelope is the core cryptographic engine of the Majik Message platform. It provides a post-quantum secure "envelope" format that handles message encryption, multi-recipient key encapsulation, and transparent compression using NIST-standardized algorithms.

npm npm downloads npm bundle size License TypeScript

Read Docs Official Repository SDK Library


Contributing

If you want to contribute or help extend support to more platforms, reach out via email. All contributions are welcome!


License

Apache-2.0 — free for personal and commercial use.


Author

Made with 💙 by @thezelijah

About the Developer


Contact