@majikah/majik-universal-id-client 0.0.1

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.
@@ -0,0 +1,757 @@
1
+ /**
2
+ * MajikUniversalIdClient.ts
3
+ *
4
+ */
5
+ import { MajikKey } from "@majikah/majik-key";
6
+ import { MajikContact, type MajikContactMeta, type SerializedMajikContact } from "./core/contacts/majik-contact";
7
+ import { MajikSignature } from "@majikah/majik-signature";
8
+ import type { MajikSignatureJSON, MajikSignerPublicKeys, SignOptions, VerificationResult } from "@majikah/majik-signature";
9
+ import { MajikContactDirectory, MajikContactDirectoryData } from "./core/contacts/majik-contact-directory";
10
+ import { MAJIK_API_RESPONSE } from "./core/types";
11
+ import { ImageSignatureStub, ImageSignOptions, ImageVerificationResult } from "@majikah/majik-signature/dist/core/stamp";
12
+ import { CreateUniversalIDOptions, MajikUniversalID } from "@majikah/majik-universal-id";
13
+ import { MajikUser } from "@thezelijah/majik-user";
14
+ type MajikUniversalIdClientEvents = "create-id" | "sign" | "verify" | "unlock" | "lock" | "new-account" | "new-contact" | "removed-account" | "removed-contact" | "active-account-change" | "error";
15
+ interface MajikUniversalIdClientStatic<T extends MajikUniversalIdClient> {
16
+ new (config: MajikUniversalIdClientConfig, id?: string): T;
17
+ fromJSON(json: MajikUniversalIdClientJSON): Promise<T>;
18
+ }
19
+ type EventCallback = (...args: any[]) => void;
20
+ export interface MajikUniversalIdClientConfig {
21
+ /**
22
+ * Shared contact directory. Pass the same instance used by MajikMessage
23
+ * so that contacts stay in sync between both clients automatically.
24
+ */
25
+ contactDirectory?: MajikContactDirectory;
26
+ user?: MajikUser;
27
+ }
28
+ export interface SignResult {
29
+ signature: MajikSignature;
30
+ signerId: string;
31
+ contentHash: string;
32
+ timestamp: string;
33
+ contentType?: string;
34
+ }
35
+ export interface VerifyResult extends VerificationResult {
36
+ signerLabel?: string;
37
+ }
38
+ export interface MajikUniversalIdClientJSON {
39
+ id: string;
40
+ contacts: MajikContactDirectoryData;
41
+ ownAccounts?: {
42
+ accounts: SerializedMajikContact[];
43
+ order: string[];
44
+ };
45
+ }
46
+ export declare class MajikUniversalIdClient {
47
+ private userProfile;
48
+ private readonly _id;
49
+ private _contactDirectory;
50
+ private _ownAccounts;
51
+ private _ownAccountsOrder;
52
+ private _listeners;
53
+ private autosaveTimer;
54
+ private autosaveIntervalId;
55
+ private readonly autosaveIntervalMs;
56
+ private readonly autosaveDebounceMs;
57
+ private user_data;
58
+ constructor(config: MajikUniversalIdClientConfig);
59
+ get user(): MajikUser | null;
60
+ set user(user: MajikUser);
61
+ clearUser(): void;
62
+ get id(): string;
63
+ /**
64
+ * Generate a new BIP-39 mnemonic phrase.
65
+ */
66
+ generateMnemonic(strength?: 128 | 256): string;
67
+ /**
68
+ * Create a new account from a mnemonic and register it.
69
+ * The account is immediately unlocked after creation.
70
+ */
71
+ createAccount(mnemonic: string, passphrase: string, label?: string): Promise<{
72
+ id: string;
73
+ fingerprint: string;
74
+ backup: string;
75
+ }>;
76
+ /**
77
+ * Import an account from a mnemonic-encrypted backup.
78
+ * Fully upgrades to Argon2id + ML-KEM + signing keys in one step.
79
+ */
80
+ importAccountFromMnemonicBackup(backupBase64: string, mnemonic: string, passphrase: string, label?: string): Promise<{
81
+ id: string;
82
+ fingerprint: string;
83
+ }>;
84
+ /**
85
+ * Export a mnemonic-encrypted backup for an account.
86
+ * The account must be unlocked.
87
+ */
88
+ exportAccountMnemonicBackup(id: string, mnemonic: string): Promise<string>;
89
+ /**
90
+ * Register an already-existing MajikContact as one of this client's own
91
+ * accounts. Useful when bootstrapping from a persisted MajikMessage state.
92
+ */
93
+ addOwnAccount(account: MajikContact): void;
94
+ /**
95
+ * Remove an own account from the instance.
96
+ * Does NOT delete it from MajikKeyStore — call MajikKeyStore.deleteIdentity()
97
+ * separately if permanent deletion is needed.
98
+ */
99
+ removeOwnAccount(id: string): boolean;
100
+ getOwnAccountById(id: string): MajikContact | undefined;
101
+ getActiveAccount(): MajikContact | null;
102
+ isAccountActive(id: string): boolean;
103
+ setActiveAccount(id: string): boolean;
104
+ listOwnAccounts(): MajikContact[];
105
+ /**
106
+ * Unlock an account with its passphrase.
107
+ * Required before signing. Not required for verification.
108
+ */
109
+ unlockAccount(id: string, passphrase: string): Promise<void>;
110
+ /**
111
+ * Lock an account — clears signing keys from memory.
112
+ */
113
+ lockAccount(id: string): void;
114
+ /**
115
+ * Lock all loaded accounts.
116
+ */
117
+ lockAllAccounts(): void;
118
+ /**
119
+ * Check whether an account's passphrase is correct without unlocking it.
120
+ */
121
+ verifyPassphrase(id: string, passphrase: string): Promise<boolean>;
122
+ /**
123
+ * Update the passphrase for an account. Re-encrypts all keys.
124
+ */
125
+ updatePassphrase(id: string, currentPassphrase: string, newPassphrase: string): Promise<void>;
126
+ /**
127
+ * Check whether an account has signing keys (Ed25519 + ML-DSA-87).
128
+ * Accounts created before the signing key upgrade need to be re-imported
129
+ * via importAccountFromMnemonicBackup() to gain signing capability.
130
+ */
131
+ accountHasSigningKeys(id: string): boolean;
132
+ /**
133
+ * Load all accounts persisted in MajikKeyStore into this instance.
134
+ * Call this on startup to hydrate from IDB.
135
+ */
136
+ loadAccountsFromStore(): Promise<void>;
137
+ getContactByID(id: string): MajikContact | null;
138
+ getContactByPublicKey(publicKeyBase64: string): Promise<MajikContact | null>;
139
+ exportContactAsJSON(contactId: string): Promise<string | null>;
140
+ exportContactAsString(contactId: string): Promise<string | null>;
141
+ importContactFromJSON(jsonStr: string): Promise<MAJIK_API_RESPONSE>;
142
+ importContactFromString(base64Str: string): Promise<MAJIK_API_RESPONSE>;
143
+ exportContactCompressed(contact: MajikContact): Promise<string>;
144
+ importContactCompressed(base64Str: string): Promise<MajikContact>;
145
+ addContact(contact: MajikContact): void;
146
+ removeContact(id: string): void;
147
+ getContactById(id: string): MajikContact | null;
148
+ listContacts(includeOwnAccounts?: boolean): MajikContact[];
149
+ updateContactMeta(id: string, meta: Partial<MajikContactMeta>): void;
150
+ /**
151
+ * Resolve a human-readable label for a signer ID.
152
+ * Checks own accounts first, then the contact directory.
153
+ * Returns the fingerprint truncated to 16 chars if no label is found.
154
+ */
155
+ resolveSignerLabel(signerId: string): string;
156
+ /**
157
+ * Sign content with the active account.
158
+ *
159
+ * The active account must be unlocked and have signing keys.
160
+ * Use unlockAccount() first if needed.
161
+ *
162
+ * @param content - Raw bytes or UTF-8 string to sign
163
+ * @param options - Optional content type and timestamp override
164
+ * @param accountId - Override which account signs. Defaults to active account.
165
+ */
166
+ sign(content: Uint8Array | string, options?: SignOptions, accountId?: string): Promise<SignResult>;
167
+ /**
168
+ * Sign content and immediately serialize to a base64 string.
169
+ * Convenience wrapper around sign() + serialize().
170
+ */
171
+ signAndSerialize(content: Uint8Array | string, options?: SignOptions, accountId?: string): Promise<string>;
172
+ /**
173
+ * Sign content and return the full JSON envelope.
174
+ * Convenience wrapper around sign() + toJSON().
175
+ */
176
+ signToJSON(content: Uint8Array | string, options?: SignOptions, accountId?: string): Promise<MajikSignatureJSON>;
177
+ /**
178
+ * Verify a signature against content.
179
+ *
180
+ * Public keys can be supplied directly, extracted from the envelope itself,
181
+ * or resolved from a known MajikKey account or contact in the directory.
182
+ *
183
+ * No private key is needed. Safe to call on locked accounts.
184
+ *
185
+ * @param content - The original content that was signed
186
+ * @param signature - MajikSignature instance, JSON object, or base64 string
187
+ * @param publicKeys - Optional. If omitted, public keys are extracted from
188
+ * the envelope (self-reported — cross-check signerId
189
+ * against a trusted source for full security).
190
+ */
191
+ verify(content: Uint8Array | string, signature: MajikSignature | MajikSignatureJSON | string, publicKeys?: MajikSignerPublicKeys): VerifyResult;
192
+ /**
193
+ * Verify against a specific known MajikKey account.
194
+ * Automatically extracts public keys from the key client.
195
+ * Works on locked accounts — only public key fields are used.
196
+ */
197
+ verifyWithAccount(content: Uint8Array | string, signature: MajikSignature | MajikSignatureJSON | string, accountId: string): VerifyResult;
198
+ /**
199
+ * Verify against a contact from the directory by their ID.
200
+ * Useful when you have the signer's contact card stored locally.
201
+ */
202
+ verifyWithContact(content: Uint8Array | string, signature: MajikSignature | MajikSignatureJSON | string, contactId: string): Promise<VerifyResult>;
203
+ /**
204
+ * Batch verify multiple signatures against the same content.
205
+ * Returns one VerifyResult per signature in the same order.
206
+ */
207
+ verifyBatch(content: Uint8Array | string, signatures: Array<MajikSignature | MajikSignatureJSON | string>, publicKeys?: MajikSignerPublicKeys): VerifyResult[];
208
+ /**
209
+ * Convenience alias for signing a plain string.
210
+ *
211
+ * Identical to signContent() but accepts only strings — makes call-sites
212
+ * that deal exclusively with text cleaner (no Uint8Array overload noise).
213
+ *
214
+ * @example
215
+ * const sig = await majik.signText("Hello world", { contentType: "text/plain" });
216
+ * const b64 = sig.serialize(); // store alongside the text
217
+ */
218
+ signText(text: string, options?: {
219
+ contentType?: string;
220
+ timestamp?: string;
221
+ accountId?: string;
222
+ }): Promise<MajikSignature>;
223
+ /**
224
+ * Sign content and return both the MajikSignature instance and a portable
225
+ * base64-serialized string in one call.
226
+ *
227
+ * The serialized string is safe to store in a database column, embed in a
228
+ * JSON field, pass in an HTTP header, or encode in a QR code alongside the
229
+ * original content. Pass it back to verifyDetached() to verify.
230
+ *
231
+ * @example — sign a document and store the detached signature
232
+ * const { serialized } = await majik.signAndDetach(docBytes, {
233
+ * contentType: "application/pdf",
234
+ * });
235
+ * await db.insert({ doc_id, signature: serialized });
236
+ *
237
+ * @example — sign a text message
238
+ * const { signature, serialized } = await majik.signAndDetach("Hello!", {
239
+ * contentType: "text/plain",
240
+ * });
241
+ */
242
+ signAndDetach(content: Uint8Array | string, options?: {
243
+ contentType?: string;
244
+ timestamp?: string;
245
+ accountId?: string;
246
+ }): Promise<{
247
+ signature: MajikSignature;
248
+ serialized: string;
249
+ }>;
250
+ /**
251
+ * Verify a plain string against a MajikSignature.
252
+ *
253
+ * Accepts the signature as a MajikSignature instance, a MajikSignatureJSON
254
+ * object, or a base64-serialized string — whichever form is easiest at the
255
+ * call-site.
256
+ *
257
+ * The signer can be identified by contact ID, raw public key base64, or a
258
+ * MajikKey client. If none is provided the public keys embedded in the
259
+ * signature envelope are used (self-reported — cross-check result.signerId
260
+ * against a known contact fingerprint before trusting).
261
+ *
262
+ * @example
263
+ * const result = await majik.verifyText("Hello world", sig, {
264
+ * contactId: "contact_abc",
265
+ * });
266
+ * if (result.valid) console.log("Authentic");
267
+ */
268
+ verifyText(text: string, signature: MajikSignature | MajikSignatureJSON | string, options?: {
269
+ contactId?: string;
270
+ publicKeyBase64?: string;
271
+ key?: MajikKey;
272
+ expectedSignerId?: string;
273
+ }): Promise<VerificationResult>;
274
+ /**
275
+ * Verify content against a base64-serialized detached signature string.
276
+ *
277
+ * This is the pair to signAndDetach() — designed for call-sites that retrieve
278
+ * a stored base64 signature from a database or API and want to verify without
279
+ * importing MajikSignature themselves.
280
+ *
281
+ * The signer can be identified by contact ID, raw public key base64, or a
282
+ * MajikKey. If none is provided, self-reported keys from the envelope are used
283
+ * (see security note on verifyContent).
284
+ *
285
+ * @example
286
+ * const row = await db.findOne({ doc_id });
287
+ * const result = await majik.verifyDetached(docBytes, row.signature, {
288
+ * contactId: row.signer_contact_id,
289
+ * });
290
+ * if (result.valid) console.log("Signed by", result.signerId);
291
+ */
292
+ verifyDetached(content: Uint8Array | string, serializedSignature: string, options?: {
293
+ contactId?: string;
294
+ publicKeyBase64?: string;
295
+ key?: MajikKey;
296
+ expectedSignerId?: string;
297
+ }): Promise<VerificationResult>;
298
+ /**
299
+ * Deserialize a base64 signature string into a MajikSignature client.
300
+ *
301
+ * Round-trip partner for MajikSignature.serialize() / sig.toString().
302
+ * Use when you have a stored base64 string and need to inspect or pass
303
+ * the instance to another method.
304
+ *
305
+ * Throws MajikSignatureSerializationError on malformed input.
306
+ *
307
+ * @example
308
+ * const sig = majik.deserializeSignature(storedBase64);
309
+ * console.log(sig.signerId, sig.timestamp);
310
+ */
311
+ deserializeSignature(serialized: string): MajikSignature;
312
+ /**
313
+ * Extract lightweight metadata from a base64 or JSON signature string
314
+ * without performing cryptographic verification.
315
+ *
316
+ * Useful for displaying "Signed by X at Y" in a UI before the user
317
+ * explicitly triggers a verification step.
318
+ *
319
+ * Returns null if the string cannot be parsed as a MajikSignature.
320
+ *
321
+ * @example
322
+ * const meta = majik.getSignatureMetadata(storedSig);
323
+ * if (meta) {
324
+ * const contact = majik.getContactByID(meta.signerId);
325
+ * console.log(`Signed by ${contact?.meta?.label ?? meta.signerId} at ${meta.timestamp}`);
326
+ * }
327
+ */
328
+ getSignatureMetadata(serialized: string): {
329
+ signerId: string;
330
+ timestamp: string;
331
+ contentType: string | undefined;
332
+ contentHash: string;
333
+ version: number;
334
+ } | null;
335
+ /**
336
+ * Check whether an account has signing keys without throwing.
337
+ *
338
+ * Use this as a fast boolean guard before showing signing UI or before
339
+ * calling any sign* method — those methods throw if signing keys are absent,
340
+ * so checking first lets you degrade gracefully (e.g. hide a "Sign" button).
341
+ *
342
+ * Checks the in-memory keystore cache only — the account must be loaded.
343
+ * Returns false for unknown accounts rather than throwing.
344
+ *
345
+ * @example
346
+ * if (!majik.hasSigningCapability()) {
347
+ * showUpgradePrompt("Re-import your account to enable signing");
348
+ * return;
349
+ * }
350
+ * const sig = await majik.signText(message);
351
+ */
352
+ hasSigningCapability(accountId?: string): boolean;
353
+ /**
354
+ * Sign raw bytes or a string using the active account.
355
+ *
356
+ * The active account is unlocked automatically if needed.
357
+ * This is the MajikMessage equivalent of MajikSignature.sign() — it resolves
358
+ * the signing key from the keystore so you don't have to manage it yourself.
359
+ *
360
+ * @example
361
+ * const sig = await majik.signContent(documentBytes, { contentType: "application/pdf" });
362
+ * const b64 = sig.serialize(); // store alongside the document
363
+ */
364
+ signContent(content: Uint8Array | string, options?: {
365
+ contentType?: string;
366
+ timestamp?: string;
367
+ accountId?: string;
368
+ }): Promise<MajikSignature>;
369
+ /**
370
+ * Sign a file and embed the signature directly into it using the active account.
371
+ *
372
+ * Format is auto-detected from magic bytes — PDF stays PDF, WAV stays WAV, etc.
373
+ * Strips any existing signature before signing (idempotent re-signing).
374
+ * The active account is unlocked automatically if needed.
375
+ *
376
+ * @example
377
+ * const { blob: signedPdf } = await majik.signFile(pdfBlob);
378
+ * // signedPdf is a valid PDF with the signature embedded in its metadata
379
+ *
380
+ * @example — non-active account
381
+ * const { blob } = await majik.signFile(wavBlob, { accountId: "acc_xyz" });
382
+ */
383
+ signFile(file: Blob, options?: {
384
+ contentType?: string;
385
+ timestamp?: string;
386
+ mimeType?: string;
387
+ accountId?: string;
388
+ }): Promise<{
389
+ blob: Blob;
390
+ signature: MajikSignature;
391
+ handler: string;
392
+ mimeType: string;
393
+ }>;
394
+ /**
395
+ * Sign multiple file blobs with the active (or specified) account in one call.
396
+ *
397
+ * Each file is signed independently — a failure on one does not abort the
398
+ * others. Check result.error on each item to handle partial failures.
399
+ *
400
+ * The hasSigningKeys check is done once upfront before any signing begins,
401
+ * so the whole batch fails fast if the account can't sign rather than
402
+ * discovering it mid-batch.
403
+ *
404
+ * @example
405
+ * const results = await majik.batchSignFiles([
406
+ * { file: pdfBlob, contentType: "application/pdf" },
407
+ * { file: wavBlob, contentType: "audio/wav" },
408
+ * { file: mp4Blob, contentType: "video/mp4" },
409
+ * ]);
410
+ * for (const r of results) {
411
+ * if (r.error) console.error("Failed:", r.error.message);
412
+ * else await r2.put(key, await r.blob!.arrayBuffer());
413
+ * }
414
+ */
415
+ batchSignFiles(files: Array<{
416
+ file: Blob;
417
+ contentType?: string;
418
+ timestamp?: string;
419
+ mimeType?: string;
420
+ }>, options?: {
421
+ accountId?: string;
422
+ }): Promise<Array<{
423
+ blob: Blob | null;
424
+ signature: MajikSignature | null;
425
+ serialized: string | null;
426
+ handler: string | null;
427
+ mimeType: string | null;
428
+ error: Error | null;
429
+ }>>;
430
+ /**
431
+ * Verify raw bytes or a string against a MajikSignature.
432
+ *
433
+ * The signer can be identified by:
434
+ * - A contact ID from the contact directory
435
+ * - A raw base64 public key string (same format used in contacts)
436
+ * - A MajikKey instance directly
437
+ *
438
+ * If no signer is provided, the public keys embedded in the signature
439
+ * envelope are used (self-reported — see security note below).
440
+ *
441
+ * > ⚠️ When no signer is provided, the extracted public keys are self-reported
442
+ * > by whoever created the signature. Always cross-check `result.signerId`
443
+ * > against a known contact fingerprint before trusting the result.
444
+ *
445
+ * @example — verify against a known contact
446
+ * const result = await majik.verifyContent(docBytes, sig, { contactId: "contact_abc" });
447
+ * if (result.valid) console.log("Authentic, signed by:", result.signerId);
448
+ *
449
+ * @example — verify using embedded keys (self-reported)
450
+ * const result = await majik.verifyContent(docBytes, sig);
451
+ * // always check result.signerId matches a known fingerprint
452
+ */
453
+ verifyContent(content: Uint8Array | string, signature: MajikSignature | MajikSignatureJSON, options?: {
454
+ contactId?: string;
455
+ publicKeyBase64?: string;
456
+ key?: MajikKey;
457
+ expectedSignerId?: string;
458
+ }): Promise<VerificationResult>;
459
+ /**
460
+ * Verify a file's embedded signature.
461
+ *
462
+ * The signer can be identified by:
463
+ * - A contact ID from the contact directory
464
+ * - A raw base64 public key string
465
+ * - A MajikKey instance directly
466
+ *
467
+ * If no signer is provided, the public keys embedded in the signature
468
+ * envelope are used (self-reported — see security note on verifyContent).
469
+ *
470
+ * @example — verify a signed PDF against a known contact
471
+ * const result = await majik.verifyFile(signedPdf, { contactId: "contact_abc" });
472
+ * if (result.valid) console.log("Verified:", result.signerId, result.timestamp);
473
+ *
474
+ * @example — check own signed file using active account
475
+ * const result = await majik.verifyFile(signedWav, {
476
+ * contactId: majik.getActiveAccount()?.id,
477
+ * });
478
+ */
479
+ verifyFile(file: Blob, options?: {
480
+ contactId?: string;
481
+ publicKeyBase64?: string;
482
+ key?: MajikKey;
483
+ expectedSignerId?: string;
484
+ mimeType?: string;
485
+ }): Promise<VerificationResult & {
486
+ handler?: string;
487
+ reason?: string;
488
+ }>;
489
+ /**
490
+ * Verify multiple files' embedded signatures against the same signer in
491
+ * one call.
492
+ *
493
+ * Each file is verified independently — a failed verification sets
494
+ * result.valid = false and populates result.error, it does not throw.
495
+ *
496
+ * @example
497
+ * const results = await majik.batchVerifyFiles(
498
+ * [pdfBlob, wavBlob, mp4Blob],
499
+ * { contactId: "contact_abc" },
500
+ * );
501
+ * const allValid = results.every(r => r.valid);
502
+ */
503
+ batchVerifyFiles(files: Array<Blob | {
504
+ file: Blob;
505
+ mimeType?: string;
506
+ expectedSignerId?: string;
507
+ }>, options?: {
508
+ contactId?: string;
509
+ publicKeyBase64?: string;
510
+ key?: MajikKey;
511
+ expectedSignerId?: string;
512
+ }): Promise<Array<VerificationResult & {
513
+ handler: string | null;
514
+ mimeType: string | null;
515
+ error: Error | null;
516
+ }>>;
517
+ /**
518
+ * Extract the embedded MajikSignature from a file.
519
+ * Returns a fully typed MajikSignature instance, or null if not found.
520
+ *
521
+ * Does not verify — use verifyFile() to verify.
522
+ *
523
+ * @example
524
+ * const sig = await majik.extractSignature(file);
525
+ * if (sig) console.log("Signed by:", sig.signerId, "at", sig.timestamp);
526
+ */
527
+ extractSignature(file: Blob, options?: {
528
+ mimeType?: string;
529
+ }): Promise<MajikSignature | null>;
530
+ /**
531
+ * Return a clean copy of the file with any embedded signature removed.
532
+ * The returned bytes are exactly what was originally signed.
533
+ *
534
+ * Useful before re-processing or re-encrypting a signed file.
535
+ *
536
+ * @example
537
+ * const originalBlob = await majik.stripSignature(signedMp4);
538
+ */
539
+ stripSignature(file: Blob, options?: {
540
+ mimeType?: string;
541
+ }): Promise<Blob>;
542
+ /**
543
+ * Check whether a file contains an embedded MajikSignature.
544
+ * Does not verify — purely a structural presence check.
545
+ *
546
+ * @example
547
+ * if (await majik.isFileSigned(file)) {
548
+ * const result = await majik.verifyFile(file, { contactId });
549
+ * }
550
+ */
551
+ isFileSigned(file: Blob, options?: {
552
+ mimeType?: string;
553
+ }): Promise<boolean>;
554
+ /**
555
+ * Get the public keys for the active account, ready for use with
556
+ * MajikSignature.verify() or for sharing with another party.
557
+ *
558
+ * Works on locked keys — only reads public fields.
559
+ *
560
+ * @example
561
+ * const myKeys = await majik.getSigningPublicKeys();
562
+ * // share myKeys with someone so they can verify your signatures
563
+ */
564
+ getSigningPublicKeys(accountId?: string): Promise<MajikSignerPublicKeys>;
565
+ /**
566
+ * Re-sign a file blob — strips any existing embedded signature, signs
567
+ * with the active (or specified) account, and returns the newly signed blob.
568
+ *
569
+ * Use after key rotation or when the signing account changes. The returned
570
+ * blob is the same format as the input — PDF stays PDF, WAV stays WAV.
571
+ *
572
+ * Distinct from resignMajikFile() which operates on a MajikFile instance
573
+ * (the encrypted .mjkb container). This operates on a plain file Blob.
574
+ *
575
+ * @example
576
+ * const { blob } = await majik.resignFile(oldSignedPdf);
577
+ * await r2.put(key, await blob.arrayBuffer());
578
+ */
579
+ resignFile(file: Blob, options?: {
580
+ contentType?: string;
581
+ timestamp?: string;
582
+ mimeType?: string;
583
+ accountId?: string;
584
+ }): Promise<{
585
+ blob: Blob;
586
+ signature: MajikSignature;
587
+ handler: string;
588
+ mimeType: string;
589
+ }>;
590
+ /**
591
+ * Extract metadata from a file's embedded signature without verifying it.
592
+ *
593
+ * Useful for rendering "Signed by X at Y" in a UI before the user
594
+ * explicitly triggers a verify step, or for routing to the correct
595
+ * contact record before calling verifyFile().
596
+ *
597
+ * Returns null if the file has no embedded signature or the JSON is
598
+ * structurally malformed.
599
+ *
600
+ * @example
601
+ * const info = await majik.getFileSignatureInfo(pdfBlob);
602
+ * if (info) {
603
+ * const contact = majik.getContactByID(info.signerId);
604
+ * console.log(`Signed by ${contact?.meta?.label ?? info.signerId}`);
605
+ * console.log(`Format handled by: ${info.handler}`);
606
+ * }
607
+ */
608
+ getFileSignatureInfo(file: Blob, options?: {
609
+ mimeType?: string;
610
+ }): Promise<MajikSignature | null>;
611
+ /**
612
+ * Sign an image with dual-layer embedding.
613
+ *
614
+ * Every signed image carries two independent proofs:
615
+ *
616
+ * Layer 1 — Pixel rows appended at the bottom (+~6px height)
617
+ * Full MajikSignature: Ed25519 + ML-DSA-87 (post-quantum)
618
+ * Survives: direct sharing, email attachments, Slack, internal tools
619
+ * Stripped by: platforms that crop/resize (Gmail, LinkedIn, Facebook)
620
+ *
621
+ * Layer 2 — DCT coefficient steganography (invisible, no size change)
622
+ * Ed25519-only stub + Reed-Solomon ECC (205 bytes)
623
+ * Survives: Q70+ JPEG recompression, WebP conversion, platform uploads
624
+ * Does not survive: screenshots, heavy crop, below-Q70 recompression
625
+ *
626
+ * Output is PNG by default. When uploaded to a platform, Layer 1 may be
627
+ * stripped but Layer 2 survives — verifyStamp() handles both automatically.
628
+ *
629
+ * Minimum image size: 600×600px (smaller images are padded with white).
630
+ *
631
+ * @param image Any image format the browser supports (JPEG, PNG, WebP…)
632
+ * @param key Unlocked MajikKey with signing keys
633
+ * @param options Output MIME type, JPEG quality, timestamp override
634
+ * @returns blob (signed image), stub (DCT layer metadata),
635
+ * fullEnvelope (complete MajikSignatureJSON for Layer 1)
636
+ *
637
+ * @example
638
+ * const { blob, stub } = await MajikSignature.stampImage(imageBlob, key);
639
+ * // blob → upload or attach; visually identical to the original
640
+ * // stub → signerId, timestamp, pHash for display
641
+ */
642
+ static stampImage(image: Blob, key: MajikKey, options?: ImageSignOptions): Promise<{
643
+ blob: Blob;
644
+ stub: ImageSignatureStub;
645
+ fullEnvelope: MajikSignatureJSON;
646
+ }>;
647
+ /**
648
+ * Verify a stamped image's embedded MajikImageSignature.
649
+ *
650
+ * Tries both layers automatically:
651
+ * - Both present → both must pass (maximum integrity, post-quantum proof)
652
+ * - Pixel row only → pixel row must pass (full Ed25519 + ML-DSA-87)
653
+ * - DCT only → DCT must pass (Ed25519 fallback, typical after platform upload)
654
+ * - Neither → invalid
655
+ *
656
+ * The `layer` field in the result communicates the trust level so callers
657
+ * can surface it in UI: 'both' > 'pixel-row' > 'dct-only'.
658
+ *
659
+ * @param image The image to verify — may be platform-compressed
660
+ * @param options hammingThreshold override (default 8 — strict)
661
+ *
662
+ * @example
663
+ * const result = await MajikSignature.verifyStamp(imageBlob);
664
+ * if (result.valid) {
665
+ * console.log(`✓ Signed by ${result.signerId}`);
666
+ * console.log(` Verified via: ${result.layer}`);
667
+ * // result.layer: 'both' | 'pixel-row' | 'dct-only'
668
+ * }
669
+ */
670
+ static verifyStamp(image: Blob, options?: {
671
+ hammingThreshold?: number;
672
+ }): Promise<ImageVerificationResult>;
673
+ /**
674
+ * Inspect which stamp layers are present without verifying.
675
+ *
676
+ * Fast — useful for rendering a "Signed by X on Y" badge in a UI before
677
+ * committing to a full cryptographic verify call.
678
+ *
679
+ * Does NOT confirm the signatures are valid — call verifyStamp() for that.
680
+ *
681
+ * @example
682
+ * const info = await MajikSignature.inspectStamp(imageBlob);
683
+ * if (info.hasPixelRow) console.log('Full post-quantum proof present');
684
+ * if (info.hasDct) console.log('Compression-resistant stub present');
685
+ * info.dctMeta?.signerId // signer ID (unverified — display only)
686
+ * info.pixelRowMeta?.timestamp // timestamp (unverified — display only)
687
+ */
688
+ static inspectStamp(image: Blob): Promise<{
689
+ hasPixelRow: boolean;
690
+ hasDct: boolean;
691
+ pixelRowMeta?: {
692
+ signerId: string;
693
+ timestamp: string;
694
+ };
695
+ dctMeta?: {
696
+ signerId: string;
697
+ timestamp: string;
698
+ pHash: string;
699
+ };
700
+ }>;
701
+ /**
702
+ * Returns true if the image contains any MajikImageSignature layer.
703
+ *
704
+ * Does not verify — structural presence check only.
705
+ * Use verifyStamp() to confirm the signature is cryptographically valid.
706
+ *
707
+ * @example
708
+ * if (await MajikSignature.isStamped(imageBlob)) { ... }
709
+ */
710
+ static isStamped(image: Blob): Promise<boolean>;
711
+ /**
712
+ * Ensure an identity is unlocked.
713
+ * Delegates entirely to MajikKeyStore.ensureUnlocked() — passphrase prompting
714
+ * is handled there via onUnlockRequested or the optional promptFn.
715
+ */
716
+ ensureIdentityUnlocked(id: string, promptFn?: (id: string) => string | Promise<string>): Promise<CryptoKey | {
717
+ raw: Uint8Array;
718
+ }>;
719
+ isPassphraseValid(passphrase: string, id?: string): Promise<boolean>;
720
+ /**
721
+ * Resolve MajikSignerPublicKeys from whichever signer hint was provided.
722
+ * Returns null if no hint was given (caller should fall back to self-reported keys).
723
+ *
724
+ * Mirrors the _resolveRecipients / _resolveFileIdentity pattern used
725
+ * throughout MajikMessage — consistent account/contact resolution in one place.
726
+ */
727
+ private _resolveSignerPublicKeys;
728
+ toJSON(): Promise<MajikUniversalIdClientJSON>;
729
+ static fromJSON<T extends MajikUniversalIdClient>(this: new (config: MajikUniversalIdClientConfig, id?: string) => T, json: MajikUniversalIdClientJSON, config?: MajikUniversalIdClientConfig): Promise<T>;
730
+ on(event: MajikUniversalIdClientEvents, callback: EventCallback): void;
731
+ off(event: MajikUniversalIdClientEvents, callback?: EventCallback): void;
732
+ private _registerOwnAccount;
733
+ private _emit;
734
+ private attachAutosaveHandlers;
735
+ startAutosave(): void;
736
+ stopAutosave(): void;
737
+ private scheduleAutosave;
738
+ saveState(): Promise<void>;
739
+ loadState(): Promise<void>;
740
+ static loadOrCreate<T extends MajikUniversalIdClient>(this: MajikUniversalIdClientStatic<T>, config: MajikUniversalIdClientConfig, userProfile?: string): Promise<T>;
741
+ resetData(userProfile?: string): Promise<void>;
742
+ /**
743
+ * Create a new MajikUniversalID from a MajikUser and an unlocked MajikKey.
744
+ *
745
+ * The key must be unlocked and have all key fields: edPublicKey, mlDsaPublicKey,
746
+ * mlKemPublicKey (for encryption), and mlKemSecretKey is not needed here —
747
+ * only the public key is used during creation.
748
+ *
749
+ * Private personal info is immediately encrypted with the bound key's
750
+ * ML-KEM-768 public key. The rehydrated value is kept in-memory so
751
+ * privateInfo is accessible right after create() without a separate call.
752
+ *
753
+ * The identity starts at IDTier.UNVERIFIED.
754
+ */
755
+ createUniversalID(user: MajikUser, key: MajikKey, options: CreateUniversalIDOptions): Promise<MajikUniversalID>;
756
+ }
757
+ export {};