@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.
- package/dist/core/contacts/majik-contact-directory.d.ts +37 -0
- package/dist/core/contacts/majik-contact-directory.js +191 -0
- package/dist/core/contacts/majik-contact.d.ts +89 -0
- package/dist/core/contacts/majik-contact.js +212 -0
- package/dist/core/crypto/constants.d.ts +56 -0
- package/dist/core/crypto/constants.js +51 -0
- package/dist/core/crypto/keystore.d.ts +228 -0
- package/dist/core/crypto/keystore.js +575 -0
- package/dist/core/identity.d.ts +63 -0
- package/dist/core/identity.js +177 -0
- package/dist/core/types.d.ts +86 -0
- package/dist/core/types.js +7 -0
- package/dist/core/utils/APITranscoder.d.ts +114 -0
- package/dist/core/utils/APITranscoder.js +305 -0
- package/dist/core/utils/idb-majik-system.d.ts +15 -0
- package/dist/core/utils/idb-majik-system.js +44 -0
- package/dist/core/utils/majik-file-utils.d.ts +16 -0
- package/dist/core/utils/majik-file-utils.js +153 -0
- package/dist/core/utils/utilities.d.ts +18 -0
- package/dist/core/utils/utilities.js +80 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/majik-universal-id-client.d.ts +757 -0
- package/dist/majik-universal-id-client.js +1618 -0
- package/package.json +55 -0
|
@@ -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 {};
|