@thezelijah/majik-message 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,5 @@ export declare const KEY_ALGO: {
4
4
  readonly namedCurve: "X25519";
5
5
  };
6
6
  export declare const MAJIK_SALT = "MajikMessageSalt";
7
+ export declare const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
7
8
  export declare const API_DEFAULT_FAIL: MAJIK_API_RESPONSE;
@@ -1,5 +1,6 @@
1
1
  export const KEY_ALGO = { name: "ECDH", namedCurve: "X25519" };
2
2
  export const MAJIK_SALT = "MajikMessageSalt";
3
+ export const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
3
4
  export const API_DEFAULT_FAIL = {
4
5
  message: "Something went wrong",
5
6
  success: false,
@@ -18,3 +18,4 @@ export declare function aesGcmDecrypt(keyBytes: Uint8Array, iv: Uint8Array, ciph
18
18
  export declare function deriveKeyFromPassphrase(passphrase: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
19
19
  export declare function deriveKeyFromMnemonic(mnemonic: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
20
20
  export declare function x25519SharedSecret(privRaw: Uint8Array, pubRaw: Uint8Array): Uint8Array;
21
+ export declare function sha256(input: string): string;
@@ -57,7 +57,6 @@ export function deriveKeyFromMnemonic(mnemonic, salt, iterations = 200000, keyLe
57
57
  return deriveKey(SHA256, m, salt, iterations, keyLen);
58
58
  }
59
59
  export function x25519SharedSecret(privRaw, pubRaw) {
60
- // Use @stablelib/x25519 for scalar multiplication / shared secret
61
60
  const priv = new Uint8Array(privRaw);
62
61
  const pub = new Uint8Array(pubRaw);
63
62
  if (x25519.scalarMult) {
@@ -68,3 +67,7 @@ export function x25519SharedSecret(privRaw, pubRaw) {
68
67
  }
69
68
  throw new Error("@stablelib/x25519: compatible API not found");
70
69
  }
70
+ export function sha256(input) {
71
+ const hashed = hash(new TextEncoder().encode(input));
72
+ return arrayToBase64(hashed);
73
+ }
@@ -1,3 +1,4 @@
1
+ import { MajikKey } from "@thezelijah/majik-key";
1
2
  export interface KeyStoreIdentity {
2
3
  id: string;
3
4
  publicKey: CryptoKey | {
@@ -39,6 +40,7 @@ export declare class KeyStore {
39
40
  static onUnlockRequested?: (id: string) => string | Promise<string>;
40
41
  static init(deviceID: string): void;
41
42
  private static getDB;
43
+ static addMajikKey(key: MajikKey): Promise<void>;
42
44
  private static putSerializedIdentity;
43
45
  private static getSerializedIdentity;
44
46
  /**
@@ -1,5 +1,5 @@
1
1
  import { arrayBufferToBase64, base64ToArrayBuffer, base64ToUtf8, utf8ToBase64, concatUint8Arrays, arrayToBase64, } from "../utils/utilities";
2
- import { KEY_ALGO, MAJIK_SALT } from "./constants";
2
+ import { KEY_ALGO, MAJIK_MNEMONIC_SALT, MAJIK_SALT } from "./constants";
3
3
  import { EncryptionEngine } from "./encryption-engine";
4
4
  import { generateMnemonic } from "@scure/bip39";
5
5
  import { wordlist } from "@scure/bip39/wordlists/english";
@@ -60,6 +60,19 @@ export class KeyStore {
60
60
  });
61
61
  return this.dbPromise;
62
62
  }
63
+ static async addMajikKey(key) {
64
+ const serializedIdentity = key.toSerializedIdentity();
65
+ const keyIdentity = key.toKeyIdentity();
66
+ this.unlockedIdentities.set(keyIdentity.id, keyIdentity);
67
+ const db = await this.getDB();
68
+ return new Promise((resolve, reject) => {
69
+ const tx = db.transaction(this.STORE_NAME, "readwrite");
70
+ const store = tx.objectStore(this.STORE_NAME);
71
+ const req = store.put(serializedIdentity);
72
+ req.onsuccess = () => resolve();
73
+ req.onerror = () => reject(new KeyStoreError("Failed to store identity", req.error));
74
+ });
75
+ }
63
76
  static async putSerializedIdentity(identity) {
64
77
  const db = await this.getDB();
65
78
  return new Promise((resolve, reject) => {
@@ -493,7 +506,7 @@ export class KeyStore {
493
506
  }
494
507
  }
495
508
  // Derive AES key from mnemonic using Stablelib provider
496
- const salt = new TextEncoder().encode("MajikMessageMnemonicSalt");
509
+ const salt = new TextEncoder().encode(MAJIK_MNEMONIC_SALT);
497
510
  const keyBytes = providerDeriveKeyFromMnemonic(mnemonic, salt);
498
511
  const iv = generateRandomBytes(IV_LENGTH);
499
512
  const ciphertext = aesGcmEncrypt(keyBytes, iv, new Uint8Array(privRawBuf));
@@ -571,7 +584,7 @@ export class KeyStore {
571
584
  }
572
585
  }
573
586
  static async deriveKeyFromMnemonic(mnemonic) {
574
- const salt = new TextEncoder().encode("MajikMessageMnemonicSalt");
587
+ const salt = new TextEncoder().encode(MAJIK_MNEMONIC_SALT);
575
588
  const keyMaterial = await crypto.subtle.importKey("raw", new TextEncoder().encode(mnemonic), { name: "PBKDF2" }, false, ["deriveKey"]);
576
589
  return crypto.subtle.deriveKey({
577
590
  name: "PBKDF2",
@@ -0,0 +1,7 @@
1
+ export declare const ThreadStatus: {
2
+ readonly ONGOING: "ongoing";
3
+ readonly CLOSED: "closed";
4
+ readonly PENDING_DELETION: "pending_deletion";
5
+ readonly MARKED_FOR_DELETION: "marked_for_deletion";
6
+ };
7
+ export type ThreadStatus = (typeof ThreadStatus)[keyof typeof ThreadStatus];
@@ -0,0 +1,6 @@
1
+ export const ThreadStatus = {
2
+ ONGOING: "ongoing",
3
+ CLOSED: "closed",
4
+ PENDING_DELETION: "pending_deletion",
5
+ MARKED_FOR_DELETION: "marked_for_deletion",
6
+ };
@@ -0,0 +1,177 @@
1
+ import { ISODateString, MajikMessageAccountID, MajikMessageMailID, MajikMessagePublicKey, MajikMessageThreadID } from "../../../types";
2
+ import { MajikMessageIdentity } from "../../system/identity";
3
+ import { MajikMessageThread } from "../majik-message-thread";
4
+ export interface MailMetadata {
5
+ subject?: string;
6
+ attachments?: string[];
7
+ priority?: "low" | "medium" | "high" | "urgent";
8
+ labels?: string[];
9
+ isForwarded?: boolean;
10
+ isReply?: boolean;
11
+ }
12
+ export interface MajikMessageMailJSON {
13
+ id: MajikMessageMailID;
14
+ thread_id: MajikMessageThreadID;
15
+ account: MajikMessageAccountID;
16
+ message: string;
17
+ sender: MajikMessagePublicKey;
18
+ recipients: MajikMessagePublicKey[];
19
+ timestamp: ISODateString;
20
+ metadata: MailMetadata;
21
+ hash: string;
22
+ p_hash: string;
23
+ previous_mail_id?: MajikMessageMailID;
24
+ read_by: MajikMessagePublicKey[];
25
+ }
26
+ export declare class MajikMailError extends Error {
27
+ code: string;
28
+ constructor(message: string, code: string);
29
+ }
30
+ export declare class MailValidationError extends MajikMailError {
31
+ constructor(message: string);
32
+ }
33
+ export declare class MailOperationError extends MajikMailError {
34
+ constructor(message: string);
35
+ }
36
+ export declare class HashIntegrityError extends MajikMailError {
37
+ constructor(message: string);
38
+ }
39
+ export declare class MajikMessageMail {
40
+ private readonly _id;
41
+ private readonly _threadID;
42
+ private readonly _account;
43
+ private _message;
44
+ private readonly _sender;
45
+ private _recipients;
46
+ private readonly _timestamp;
47
+ private _metadata;
48
+ private readonly _hash;
49
+ private readonly _p_hash;
50
+ private readonly _previousMailID?;
51
+ private _readBy;
52
+ private static readonly MAX_MESSAGE_LENGTH;
53
+ private constructor();
54
+ get id(): MajikMessageMailID;
55
+ get threadID(): MajikMessageThreadID;
56
+ get account(): MajikMessageAccountID;
57
+ get sender(): MajikMessagePublicKey;
58
+ get recipients(): readonly MajikMessagePublicKey[];
59
+ get timestamp(): Date;
60
+ get metadata(): Readonly<MailMetadata>;
61
+ get hash(): string;
62
+ get p_hash(): string;
63
+ get previousMailID(): MajikMessageMailID | undefined;
64
+ get readBy(): readonly MajikMessagePublicKey[];
65
+ get message(): string;
66
+ /**
67
+ * Creates the first mail item in a thread.
68
+ * Uses the thread's hash as the p_hash since this is the first item.
69
+ *
70
+ * @param thread - The MajikMessageThread this mail belongs to
71
+ * @param identity - The sender's MajikMessageIdentity
72
+ * @param message - Plain text message (encrypted)
73
+ * @param recipients - Array of recipient public keys (excluding sender)
74
+ * @param metadata - Optional mail metadata
75
+ * @returns Promise resolving to new MajikMessageMail instance
76
+ * @throws Error if validation fails or thread is closed
77
+ */
78
+ static create(thread: MajikMessageThread, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata): Promise<MajikMessageMail>;
79
+ /**
80
+ * Creates a reply to an existing mail item in the thread.
81
+ * Uses the previous mail's hash as part of the p_hash.
82
+ *
83
+ * @param thread - The MajikMessageThread this mail belongs to
84
+ * @param previousMail - The mail being replied to
85
+ * @param identity - The sender's MajikMessageIdentity
86
+ * @param message - Plain text message (encrypted)
87
+ * @param recipients - Array of recipient public keys (excluding sender)
88
+ * @param metadata - Optional mail metadata
89
+ * @returns Promise resolving to new MajikMessageMail instance
90
+ * @throws Error if validation fails or thread is closed
91
+ */
92
+ static reply(thread: MajikMessageThread, previousMail: MajikMessageMail, identity: MajikMessageIdentity, message: string, recipients: MajikMessagePublicKey[], metadata?: MailMetadata): Promise<MajikMessageMail>;
93
+ /**
94
+ * Generates the hash for the current mail item.
95
+ * Format: SHA256(id:message:sender:recipients:timestamp)
96
+ */
97
+ private static generateHash;
98
+ /**
99
+ * Generates the previous hash (blockchain link).
100
+ * Format: SHA256(currentHash:previousHash)
101
+ */
102
+ private static generatePHash;
103
+ /**
104
+ * Validates the current mail item's integrity.
105
+ * Checks hash and p_hash validity.
106
+ */
107
+ validate(): boolean;
108
+ /**
109
+ * Validates the p_hash against a previous hash.
110
+ * Used to verify blockchain integrity.
111
+ *
112
+ * @param previousHash - The hash from the previous item (or thread hash for first item)
113
+ * @returns true if p_hash is valid
114
+ */
115
+ validatePHash(previousHash: string): boolean;
116
+ private validateMessage;
117
+ /**
118
+ * Validates an entire chain of mail items in a thread.
119
+ * Verifies both hash and p_hash integrity for all items.
120
+ *
121
+ * @param thread - The thread these mail items belong to
122
+ * @param mailItems - Array of mail items ordered chronologically (oldest first)
123
+ * @returns Validation result with details
124
+ */
125
+ static validateMailChain(thread: MajikMessageThread, mailItems: MajikMessageMail[]): {
126
+ isValid: boolean;
127
+ errors: string[];
128
+ tamperedItems: string[];
129
+ };
130
+ /**
131
+ * Marks this mail as read by a recipient.
132
+ * @param recipientPublicKey - The public key of the recipient marking as read
133
+ * @returns true if successfully marked, false if already read
134
+ */
135
+ markAsRead(recipientPublicKey: MajikMessagePublicKey): boolean;
136
+ /**
137
+ * Checks if a specific user has read this mail.
138
+ */
139
+ hasUserRead(recipientPublicKey: MajikMessagePublicKey): boolean;
140
+ /**
141
+ * Checks if all recipients have read this mail.
142
+ */
143
+ isReadByAll(): boolean;
144
+ /**
145
+ * Gets the list of recipients who haven't read this mail yet.
146
+ */
147
+ getUnreadRecipients(): MajikMessagePublicKey[];
148
+ /**
149
+ * Gets the read percentage.
150
+ */
151
+ getReadPercentage(): number;
152
+ /**
153
+ * Checks if a user can access this mail (is sender or recipient).
154
+ */
155
+ canUserAccess(userPublicKey: MajikMessagePublicKey): boolean;
156
+ /**
157
+ * Checks if a user is the sender of this mail.
158
+ */
159
+ isSender(userPublicKey: MajikMessagePublicKey): boolean;
160
+ /**
161
+ * Checks if a user is a recipient of this mail.
162
+ */
163
+ isRecipient(userPublicKey: MajikMessagePublicKey): boolean;
164
+ /**
165
+ * Updates the metadata for this mail.
166
+ */
167
+ updateMetadata(metadata: Partial<MailMetadata>): void;
168
+ toJSON(): MajikMessageMailJSON;
169
+ static fromJSON(json: MajikMessageMailJSON | string): MajikMessageMail;
170
+ toString(): string;
171
+ clone(): MajikMessageMail;
172
+ /**
173
+ * Validates raw message length
174
+ */
175
+ private static validateRawMessageLength;
176
+ private static isValidJSON;
177
+ }