@majikah/majik-signature-client 0.3.0 → 0.3.2
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/client-state-manager.js +4 -1
- package/dist/core/stamp/majik-signature-stamp-manager.d.ts +21 -1
- package/dist/core/stamp/majik-signature-stamp-manager.js +57 -0
- package/dist/core/stamp/majik-signature-stamp.d.ts +16 -4
- package/dist/core/stamp/majik-signature-stamp.js +23 -11
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/majik-signature-client.d.ts +6 -0
- package/dist/majik-signature-client.js +34 -1
- package/package.json +3 -3
|
@@ -189,7 +189,10 @@ export class ClientStateManager {
|
|
|
189
189
|
return DEFAULT_USER_APP_PREFERENCES;
|
|
190
190
|
}
|
|
191
191
|
try {
|
|
192
|
-
return
|
|
192
|
+
return {
|
|
193
|
+
...DEFAULT_USER_APP_PREFERENCES,
|
|
194
|
+
...JSON.parse(raw),
|
|
195
|
+
};
|
|
193
196
|
}
|
|
194
197
|
catch (e) {
|
|
195
198
|
console.warn("ClientStateManager: Problem retrieving user app preferences: ", e);
|
|
@@ -1,18 +1,38 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BatchLockResult, type MajikFileIdentity } from "@majikah/majik-file";
|
|
2
2
|
import { MajikSignatureStamp, MajikSignatureStampJSON, StampAssetKind, CreateStampOptions } from "./majik-signature-stamp";
|
|
3
3
|
import { StorageQuery } from "../storage/storage-adapter";
|
|
4
4
|
import { MajikSignatureStampStorageAdapter } from "../storage";
|
|
5
|
+
import { MajikKey } from "@majikah/majik-key";
|
|
5
6
|
export declare class MajikSignatureStampManagerError extends Error {
|
|
6
7
|
cause?: unknown;
|
|
7
8
|
constructor(message: string, cause?: unknown);
|
|
8
9
|
}
|
|
9
10
|
export declare class MajikSignatureStampManager {
|
|
10
11
|
private readonly _cache;
|
|
12
|
+
private _decryptErrors;
|
|
11
13
|
private _adapter;
|
|
12
14
|
constructor(adapter?: MajikSignatureStampStorageAdapter);
|
|
13
15
|
get adapter(): MajikSignatureStampStorageAdapter;
|
|
14
16
|
setAdapter(adapter: MajikSignatureStampStorageAdapter): void;
|
|
15
17
|
hydrate(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Hydrate every stamp for `fingerprint`. If `key` is given (must be
|
|
20
|
+
* unlocked, matching that fingerprint), each stamp is decrypted so its
|
|
21
|
+
* plaintext is ready for preview. Stamps that fail to decrypt are
|
|
22
|
+
* silently excluded from the cache — never a hard throw. Reasons are
|
|
23
|
+
* kept in getDecryptError() for optional diagnostics.
|
|
24
|
+
*
|
|
25
|
+
* NOTE: this intentionally does NOT call MajikFile.batchDecrypt() —
|
|
26
|
+
* that method gates on MajikFile.canDecrypt(), which currently compares
|
|
27
|
+
* key.fingerprint against a list of public keys and will reject every
|
|
28
|
+
* file. Once that's fixed upstream, this can call batchDecrypt() directly
|
|
29
|
+
* instead of looping decryptHydrate() manually.
|
|
30
|
+
*/
|
|
31
|
+
hydrateForFingerprint(fingerprint: string, key?: MajikKey): Promise<void>;
|
|
32
|
+
getDecryptError(id: string): string | undefined;
|
|
33
|
+
/** Wipe decrypted plaintext from every currently cached stamp's MajikFile.
|
|
34
|
+
* MajikFile.batchLock() has no canDecrypt-style gate, safe to use as-is. */
|
|
35
|
+
lockAll(): BatchLockResult;
|
|
16
36
|
toJSON(): Promise<MajikSignatureStampJSON[]>;
|
|
17
37
|
bulkRestoreFromJSON(data: MajikSignatureStampJSON[]): Promise<void>;
|
|
18
38
|
static fromJSON(data: MajikSignatureStampJSON[], adapter?: MajikSignatureStampStorageAdapter): Promise<MajikSignatureStampManager>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MajikFile, } from "@majikah/majik-file";
|
|
1
2
|
import { MajikSignatureStamp, } from "./majik-signature-stamp";
|
|
2
3
|
import { InMemoryStampstoreAdapter, } from "../storage";
|
|
3
4
|
export class MajikSignatureStampManagerError extends Error {
|
|
@@ -10,6 +11,7 @@ export class MajikSignatureStampManagerError extends Error {
|
|
|
10
11
|
}
|
|
11
12
|
export class MajikSignatureStampManager {
|
|
12
13
|
_cache = new Map();
|
|
14
|
+
_decryptErrors = new Map();
|
|
13
15
|
_adapter;
|
|
14
16
|
constructor(adapter) {
|
|
15
17
|
this._adapter = adapter ?? new InMemoryStampstoreAdapter();
|
|
@@ -34,6 +36,61 @@ export class MajikSignatureStampManager {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Hydrate every stamp for `fingerprint`. If `key` is given (must be
|
|
41
|
+
* unlocked, matching that fingerprint), each stamp is decrypted so its
|
|
42
|
+
* plaintext is ready for preview. Stamps that fail to decrypt are
|
|
43
|
+
* silently excluded from the cache — never a hard throw. Reasons are
|
|
44
|
+
* kept in getDecryptError() for optional diagnostics.
|
|
45
|
+
*
|
|
46
|
+
* NOTE: this intentionally does NOT call MajikFile.batchDecrypt() —
|
|
47
|
+
* that method gates on MajikFile.canDecrypt(), which currently compares
|
|
48
|
+
* key.fingerprint against a list of public keys and will reject every
|
|
49
|
+
* file. Once that's fixed upstream, this can call batchDecrypt() directly
|
|
50
|
+
* instead of looping decryptHydrate() manually.
|
|
51
|
+
*/
|
|
52
|
+
async hydrateForFingerprint(fingerprint, key) {
|
|
53
|
+
this._cache.clear();
|
|
54
|
+
this._decryptErrors.clear();
|
|
55
|
+
const rows = this._adapter.query
|
|
56
|
+
? await this._adapter.query({ where: { fingerprint } })
|
|
57
|
+
: (await this._adapter.list()).filter((r) => r.fingerprint === fingerprint);
|
|
58
|
+
const stamps = [];
|
|
59
|
+
for (const json of rows) {
|
|
60
|
+
try {
|
|
61
|
+
stamps.push(MajikSignatureStamp.fromJSON(json));
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.warn(`hydrateForFingerprint: skipping malformed stamp "${json?.id}":`, err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!key) {
|
|
68
|
+
for (const s of stamps)
|
|
69
|
+
this._cache.set(s.id, s);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const results = await Promise.allSettled(stamps.map((stamp) => stamp.toMajikFile().decryptHydrate(key)));
|
|
73
|
+
results.forEach((result, i) => {
|
|
74
|
+
const stamp = stamps[i];
|
|
75
|
+
if (result.status === "fulfilled") {
|
|
76
|
+
this._cache.set(stamp.id, stamp);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this._decryptErrors.set(stamp.id, result.reason instanceof Error
|
|
80
|
+
? result.reason.message
|
|
81
|
+
: String(result.reason));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
getDecryptError(id) {
|
|
86
|
+
return this._decryptErrors.get(id);
|
|
87
|
+
}
|
|
88
|
+
/** Wipe decrypted plaintext from every currently cached stamp's MajikFile.
|
|
89
|
+
* MajikFile.batchLock() has no canDecrypt-style gate, safe to use as-is. */
|
|
90
|
+
lockAll() {
|
|
91
|
+
const files = this.list().map((s) => s.toMajikFile());
|
|
92
|
+
return MajikFile.batchLock(files);
|
|
93
|
+
}
|
|
37
94
|
// ── Serialization ─────────────────────────────────────────────────────
|
|
38
95
|
async toJSON() {
|
|
39
96
|
return this._adapter.list();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { MajikFile } from "@majikah/majik-file";
|
|
2
|
+
import type { MajikFileDecryptIdentity, MajikFileIdentity, MajikFileJSON } from "@majikah/majik-file";
|
|
2
3
|
export type StampAssetKind = "image" | "audio" | "video" | "text";
|
|
3
4
|
export interface MajikSignatureStampJSON {
|
|
4
5
|
id: string;
|
|
@@ -10,6 +11,9 @@ export interface MajikSignatureStampJSON {
|
|
|
10
11
|
data: string;
|
|
11
12
|
timestamp: string;
|
|
12
13
|
lastUpdate: string;
|
|
14
|
+
/** Full MajikFile metadata — required to reconstruct a working MajikFile
|
|
15
|
+
* instance via MajikFile.fromJSON() for decryptHydrate()/secureLock(). */
|
|
16
|
+
fileJson: MajikFileJSON;
|
|
13
17
|
}
|
|
14
18
|
export interface CreateStampOptions {
|
|
15
19
|
data: Uint8Array | ArrayBuffer;
|
|
@@ -30,6 +34,8 @@ export declare class MajikSignatureStamp {
|
|
|
30
34
|
private readonly _sizeBytes;
|
|
31
35
|
private readonly _fingerprint;
|
|
32
36
|
private readonly _timestamp;
|
|
37
|
+
private readonly _fileJson;
|
|
38
|
+
private _file;
|
|
33
39
|
private _lastUpdate;
|
|
34
40
|
private _encryptedBinary;
|
|
35
41
|
private constructor();
|
|
@@ -46,7 +52,13 @@ export declare class MajikSignatureStamp {
|
|
|
46
52
|
rename(newName: string): void;
|
|
47
53
|
toJSON(): MajikSignatureStampJSON;
|
|
48
54
|
static fromJSON(json: MajikSignatureStampJSON): MajikSignatureStamp;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
/** Reconstruct (or return the cached) MajikFile backing this stamp. */
|
|
56
|
+
toMajikFile(): MajikFile;
|
|
57
|
+
/** Cached decrypted bytes, if hydrateForFingerprint()/decryptHydrate() has
|
|
58
|
+
* already run for this stamp this session. Undefined otherwise, or after
|
|
59
|
+
* a lock. */
|
|
60
|
+
get decryptedBytes(): Uint8Array | undefined;
|
|
61
|
+
/** Accepts a full MajikKey OR a bare {fingerprint, mlKemSecretKey} —
|
|
62
|
+
* matches MajikFile's own MajikFileDecryptIdentity shape. */
|
|
63
|
+
decryptContent(identity: MajikFileDecryptIdentity): Promise<Uint8Array>;
|
|
52
64
|
}
|
|
@@ -9,6 +9,8 @@ export class MajikSignatureStamp {
|
|
|
9
9
|
_sizeBytes;
|
|
10
10
|
_fingerprint;
|
|
11
11
|
_timestamp;
|
|
12
|
+
_fileJson;
|
|
13
|
+
_file = null; // reconstructed lazily, cached per instance
|
|
12
14
|
_lastUpdate;
|
|
13
15
|
_encryptedBinary; // the .mjkb bytes — never the plaintext
|
|
14
16
|
constructor(json, binary) {
|
|
@@ -21,6 +23,7 @@ export class MajikSignatureStamp {
|
|
|
21
23
|
this._timestamp = json.timestamp;
|
|
22
24
|
this._lastUpdate = json.lastUpdate;
|
|
23
25
|
this._encryptedBinary = binary;
|
|
26
|
+
this._fileJson = json.fileJson;
|
|
24
27
|
}
|
|
25
28
|
get id() {
|
|
26
29
|
return this._id;
|
|
@@ -77,8 +80,11 @@ export class MajikSignatureStamp {
|
|
|
77
80
|
data: arrayToBase64(binary),
|
|
78
81
|
timestamp: fileJson.timestamp ?? now,
|
|
79
82
|
lastUpdate: fileJson.last_update ?? now,
|
|
83
|
+
fileJson,
|
|
80
84
|
};
|
|
81
|
-
|
|
85
|
+
const instance = new MajikSignatureStamp(json, binary);
|
|
86
|
+
instance._file = file; // reuse — already fully constructed, no need to refetch
|
|
87
|
+
return instance;
|
|
82
88
|
}
|
|
83
89
|
/** Plaintext-only rename — no re-encryption, no MajikFile involvement. */
|
|
84
90
|
rename(newName) {
|
|
@@ -87,7 +93,6 @@ export class MajikSignatureStamp {
|
|
|
87
93
|
this._name = newName.trim();
|
|
88
94
|
this._lastUpdate = new Date().toISOString();
|
|
89
95
|
}
|
|
90
|
-
// ── SERIALISATION ───────────────────────────────────────────────────────
|
|
91
96
|
toJSON() {
|
|
92
97
|
return {
|
|
93
98
|
id: this._id,
|
|
@@ -99,21 +104,28 @@ export class MajikSignatureStamp {
|
|
|
99
104
|
data: arrayToBase64(this._encryptedBinary),
|
|
100
105
|
timestamp: this._timestamp,
|
|
101
106
|
lastUpdate: this._lastUpdate,
|
|
107
|
+
fileJson: this._fileJson,
|
|
102
108
|
};
|
|
103
109
|
}
|
|
104
110
|
static fromJSON(json) {
|
|
105
111
|
return new MajikSignatureStamp(json, base64ToUint8Array(json.data));
|
|
106
112
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
throw new Error(`decryptContent: stamp was encrypted under fingerprint "${this._fingerprint}", ` +
|
|
112
|
-
`but the provided identity has fingerprint "${identity.fingerprint}"`);
|
|
113
|
+
/** Reconstruct (or return the cached) MajikFile backing this stamp. */
|
|
114
|
+
toMajikFile() {
|
|
115
|
+
if (!this._file) {
|
|
116
|
+
this._file = MajikFile.fromJSON(this._fileJson, this._encryptedBinary);
|
|
113
117
|
}
|
|
114
|
-
return
|
|
118
|
+
return this._file;
|
|
119
|
+
}
|
|
120
|
+
/** Cached decrypted bytes, if hydrateForFingerprint()/decryptHydrate() has
|
|
121
|
+
* already run for this stamp this session. Undefined otherwise, or after
|
|
122
|
+
* a lock. */
|
|
123
|
+
get decryptedBytes() {
|
|
124
|
+
return this._file?.hasDecryptedFile ? this._file.decryptedFile : undefined;
|
|
115
125
|
}
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
/** Accepts a full MajikKey OR a bare {fingerprint, mlKemSecretKey} —
|
|
127
|
+
* matches MajikFile's own MajikFileDecryptIdentity shape. */
|
|
128
|
+
async decryptContent(identity) {
|
|
129
|
+
return MajikFile.decrypt(this._encryptedBinary, identity);
|
|
118
130
|
}
|
|
119
131
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from "./core/crypto/constants";
|
|
|
7
7
|
export * from "./core/crypto/keystore-manager";
|
|
8
8
|
export * from "./core/storage";
|
|
9
9
|
export * from "./core/stamp/majik-signature-stamp";
|
|
10
|
+
export { DEFAULT_USER_APP_PREFERENCES } from "./core/client-state-manager";
|
|
10
11
|
export * from "./core/utils/utilities";
|
|
11
12
|
export * from "./core/identity";
|
|
12
13
|
export { migrateMajikMessageJSON } from "./core/contacts/majik-contact-migration";
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./core/crypto/constants";
|
|
|
6
6
|
export * from "./core/crypto/keystore-manager";
|
|
7
7
|
export * from "./core/storage";
|
|
8
8
|
export * from "./core/stamp/majik-signature-stamp";
|
|
9
|
+
export { DEFAULT_USER_APP_PREFERENCES } from "./core/client-state-manager";
|
|
9
10
|
export * from "./core/utils/utilities";
|
|
10
11
|
export * from "./core/identity";
|
|
11
12
|
export { migrateMajikMessageJSON } from "./core/contacts/majik-contact-migration";
|
|
@@ -132,6 +132,8 @@ export declare class MajikSignatureClient {
|
|
|
132
132
|
resetUserAppPreferences(): Promise<void>;
|
|
133
133
|
isAnalyticsEnabled(): Promise<boolean>;
|
|
134
134
|
isAutoSealEnabled(): Promise<boolean>;
|
|
135
|
+
isAutoLockOnMinimizeEnabled(): Promise<boolean>;
|
|
136
|
+
autoLockInterval(): Promise<number | undefined>;
|
|
135
137
|
generateMnemonic(strength?: 128 | 256, language?: MnemonicLanguage): Promise<string>;
|
|
136
138
|
createAccount(mnemonic: string, passphrase: string, label?: string): Promise<{
|
|
137
139
|
id: string;
|
|
@@ -836,6 +838,10 @@ export declare class MajikSignatureClient {
|
|
|
836
838
|
accountId?: string;
|
|
837
839
|
}): Promise<MajikSignatureStamp>;
|
|
838
840
|
listStamps(kind?: StampAssetKind): MajikSignatureStamp[];
|
|
841
|
+
hydrateStampsForActiveAccount(): Promise<void>;
|
|
842
|
+
lockStamps(): void;
|
|
843
|
+
getDecryptedStampBytes(id: string): Uint8Array | undefined;
|
|
844
|
+
listStampsForActiveAccount(kind?: StampAssetKind): MajikSignatureStamp[];
|
|
839
845
|
getStamp(id: string): Promise<MajikSignatureStamp | null>;
|
|
840
846
|
decryptStampContent(id: string, accountId?: string): Promise<Uint8Array>;
|
|
841
847
|
renameStamp(id: string, newName: string): Promise<MajikSignatureStamp>;
|
|
@@ -200,6 +200,14 @@ export class MajikSignatureClient {
|
|
|
200
200
|
const appPreferences = await this._state.getUserAppPreferences();
|
|
201
201
|
return appPreferences.signing.autoSeal ?? false;
|
|
202
202
|
}
|
|
203
|
+
async isAutoLockOnMinimizeEnabled() {
|
|
204
|
+
const appPreferences = await this._state.getUserAppPreferences();
|
|
205
|
+
return appPreferences.security?.key?.autoLockOnMinimize ?? false;
|
|
206
|
+
}
|
|
207
|
+
async autoLockInterval() {
|
|
208
|
+
const appPreferences = await this._state.getUserAppPreferences();
|
|
209
|
+
return appPreferences.security?.key?.autoLockInterval;
|
|
210
|
+
}
|
|
203
211
|
// ==========================================================================
|
|
204
212
|
// ── ACCOUNT MANAGEMENT ────────────────────────────────────────────────────
|
|
205
213
|
// ==========================================================================
|
|
@@ -1312,7 +1320,7 @@ export class MajikSignatureClient {
|
|
|
1312
1320
|
*/
|
|
1313
1321
|
async stripSignature(file, options) {
|
|
1314
1322
|
try {
|
|
1315
|
-
return MajikSignature.stripFrom(file, options);
|
|
1323
|
+
return await MajikSignature.stripFrom(file, options);
|
|
1316
1324
|
}
|
|
1317
1325
|
catch (err) {
|
|
1318
1326
|
this._emit("error", err, { context: "stripSignature" });
|
|
@@ -1675,6 +1683,31 @@ export class MajikSignatureClient {
|
|
|
1675
1683
|
listStamps(kind) {
|
|
1676
1684
|
return kind ? this._stamps.listByKind(kind) : this._stamps.list();
|
|
1677
1685
|
}
|
|
1686
|
+
async hydrateStampsForActiveAccount() {
|
|
1687
|
+
const id = this.getActiveAccount()?.id;
|
|
1688
|
+
if (!id)
|
|
1689
|
+
throw new Error("No active account — call setActiveAccount() first");
|
|
1690
|
+
const key = this._keys.get(id);
|
|
1691
|
+
if (!key)
|
|
1692
|
+
throw new Error(`Account not found in keystore: "${id}"`);
|
|
1693
|
+
if (key.isLocked) {
|
|
1694
|
+
throw new Error(`Account "${id}" is locked. Call unlockAccount() before hydrating stamps.`);
|
|
1695
|
+
}
|
|
1696
|
+
await this._stamps.hydrateForFingerprint(key.fingerprint, key); // pass key directly, no identity-builder needed
|
|
1697
|
+
}
|
|
1698
|
+
lockStamps() {
|
|
1699
|
+
this._stamps.lockAll();
|
|
1700
|
+
}
|
|
1701
|
+
getDecryptedStampBytes(id) {
|
|
1702
|
+
return this._stamps.get(id)?.decryptedBytes;
|
|
1703
|
+
}
|
|
1704
|
+
listStampsForActiveAccount(kind) {
|
|
1705
|
+
const key = this.getActiveAccountKey();
|
|
1706
|
+
if (!key)
|
|
1707
|
+
return [];
|
|
1708
|
+
const all = kind ? this._stamps.listByKind(kind) : this._stamps.list();
|
|
1709
|
+
return all.filter((s) => s.fingerprint === key.fingerprint);
|
|
1710
|
+
}
|
|
1678
1711
|
async getStamp(id) {
|
|
1679
1712
|
return this._stamps.load(id);
|
|
1680
1713
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@majikah/majik-signature-client",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Majik Signature Client is a hybrid post-quantum content signing and verification library for the Majikah ecosystem. Built on top of Majik Key, it provides tamper-proof, forgery-resistant digital signatures for any content format — using a dual-algorithm architecture that combines classical Ed25519 with post-quantum ML-DSA-87 (FIPS-204).",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.2",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"url": "https://github.com/Majikah/majik-signature-client/issues"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
-
"update": "npm install @majikah/majik-key@latest @majikah/majik-signature@latest @majikah/majik-contact@latest",
|
|
46
|
+
"update": "npm install @majikah/majik-key@latest @majikah/majik-signature@latest @majikah/majik-contact@latest @majikah/majik-file@latest",
|
|
47
47
|
"build": "tsc",
|
|
48
48
|
"typecheck": "tsc --noEmit",
|
|
49
49
|
"prepublishOnly": "npm run build",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@majikah/majik-cjson": "^0.0.3",
|
|
56
56
|
"@majikah/majik-contact": "^0.0.6",
|
|
57
|
-
"@majikah/majik-file": "^0.1.
|
|
57
|
+
"@majikah/majik-file": "^0.1.8",
|
|
58
58
|
"@majikah/majik-key": "^0.2.12",
|
|
59
59
|
"@majikah/majik-signature": "^0.0.30",
|
|
60
60
|
"@stablelib/ed25519": "^2.1.0",
|