@majikah/majik-key 0.1.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/LICENSE +67 -0
- package/README.md +911 -0
- package/dist/core/crypto/constants.d.ts +6 -0
- package/dist/core/crypto/constants.js +3 -0
- package/dist/core/crypto/crypto-provider.d.ts +21 -0
- package/dist/core/crypto/crypto-provider.js +74 -0
- package/dist/core/crypto/encryption-engine.d.ts +36 -0
- package/dist/core/crypto/encryption-engine.js +114 -0
- package/dist/core/database/system/identity.d.ts +61 -0
- package/dist/core/database/system/identity.js +171 -0
- package/dist/core/error.d.ts +4 -0
- package/dist/core/error.js +11 -0
- package/dist/core/majik-contact.d.ts +72 -0
- package/dist/core/majik-contact.js +195 -0
- package/dist/core/types.d.ts +19 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +28 -0
- package/dist/core/utils.js +107 -0
- package/dist/core/validator.d.ts +10 -0
- package/dist/core/validator.js +80 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/majik-key.d.ts +269 -0
- package/dist/majik-key.js +763 -0
- package/package.json +63 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const IV_LENGTH = 12;
|
|
2
|
+
export declare function generateRandomBytes(len: number): Uint8Array;
|
|
3
|
+
export declare function generateEd25519Keypair(): {
|
|
4
|
+
edPublic: Uint8Array;
|
|
5
|
+
edSecret: Uint8Array;
|
|
6
|
+
xPublic: Uint8Array | null;
|
|
7
|
+
xSecret: Uint8Array | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function deriveEd25519FromSeed(seed32: Uint8Array): {
|
|
10
|
+
edPublic: Uint8Array;
|
|
11
|
+
edSecret: Uint8Array;
|
|
12
|
+
xPublic: Uint8Array | null;
|
|
13
|
+
xSecret: Uint8Array | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function fingerprintFromPublicRaw(rawPublic: Uint8Array): string;
|
|
16
|
+
export declare function aesGcmEncrypt(keyBytes: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
|
|
17
|
+
export declare function aesGcmDecrypt(keyBytes: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array | null;
|
|
18
|
+
export declare function deriveKeyFromPassphrase(passphrase: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
|
|
19
|
+
export declare function deriveKeyFromMnemonic(mnemonic: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
|
|
20
|
+
export declare function x25519SharedSecret(privRaw: Uint8Array, pubRaw: Uint8Array): Uint8Array;
|
|
21
|
+
export declare function sha256(input: string): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as ed25519 from "@stablelib/ed25519";
|
|
2
|
+
import ed2curve from "ed2curve";
|
|
3
|
+
import { AES } from "@stablelib/aes";
|
|
4
|
+
import { GCM } from "@stablelib/gcm";
|
|
5
|
+
import { deriveKey } from "@stablelib/pbkdf2";
|
|
6
|
+
import { hash, SHA256 } from "@stablelib/sha256";
|
|
7
|
+
import * as x25519 from "@stablelib/x25519";
|
|
8
|
+
import { arrayToBase64 } from "../utils";
|
|
9
|
+
export const IV_LENGTH = 12;
|
|
10
|
+
export function generateRandomBytes(len) {
|
|
11
|
+
const b = new Uint8Array(len);
|
|
12
|
+
crypto.getRandomValues(b);
|
|
13
|
+
return b;
|
|
14
|
+
}
|
|
15
|
+
export function generateEd25519Keypair() {
|
|
16
|
+
const ed = ed25519.generateKeyPair();
|
|
17
|
+
const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
|
|
18
|
+
const skCurve = ed2curve.convertSecretKey(ed.secretKey);
|
|
19
|
+
return {
|
|
20
|
+
edPublic: ed.publicKey,
|
|
21
|
+
edSecret: ed.secretKey,
|
|
22
|
+
xPublic: pkCurve ? new Uint8Array(pkCurve) : null,
|
|
23
|
+
xSecret: skCurve ? new Uint8Array(skCurve) : null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function deriveEd25519FromSeed(seed32) {
|
|
27
|
+
const ed = ed25519.generateKeyPairFromSeed(seed32);
|
|
28
|
+
const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
|
|
29
|
+
const skCurve = ed2curve.convertSecretKey(ed.secretKey);
|
|
30
|
+
return {
|
|
31
|
+
edPublic: ed.publicKey,
|
|
32
|
+
edSecret: ed.secretKey,
|
|
33
|
+
xPublic: pkCurve ? new Uint8Array(pkCurve) : null,
|
|
34
|
+
xSecret: skCurve ? new Uint8Array(skCurve) : null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function fingerprintFromPublicRaw(rawPublic) {
|
|
38
|
+
const digest = hash(rawPublic);
|
|
39
|
+
return arrayToBase64(digest);
|
|
40
|
+
}
|
|
41
|
+
export function aesGcmEncrypt(keyBytes, iv, plaintext) {
|
|
42
|
+
const aes = new AES(keyBytes);
|
|
43
|
+
const gcm = new GCM(aes);
|
|
44
|
+
return gcm.seal(iv, plaintext);
|
|
45
|
+
}
|
|
46
|
+
export function aesGcmDecrypt(keyBytes, iv, ciphertext) {
|
|
47
|
+
const aes = new AES(keyBytes);
|
|
48
|
+
const gcm = new GCM(aes);
|
|
49
|
+
return gcm.open(iv, ciphertext);
|
|
50
|
+
}
|
|
51
|
+
export function deriveKeyFromPassphrase(passphrase, salt, iterations = 250000, keyLen = 32) {
|
|
52
|
+
const pw = new TextEncoder().encode(passphrase);
|
|
53
|
+
return deriveKey(SHA256, pw, salt, iterations, keyLen);
|
|
54
|
+
}
|
|
55
|
+
export function deriveKeyFromMnemonic(mnemonic, salt, iterations = 200000, keyLen = 32) {
|
|
56
|
+
const m = new TextEncoder().encode(mnemonic);
|
|
57
|
+
return deriveKey(SHA256, m, salt, iterations, keyLen);
|
|
58
|
+
}
|
|
59
|
+
export function x25519SharedSecret(privRaw, pubRaw) {
|
|
60
|
+
// Use @stablelib/x25519 for scalar multiplication / shared secret
|
|
61
|
+
const priv = new Uint8Array(privRaw);
|
|
62
|
+
const pub = new Uint8Array(pubRaw);
|
|
63
|
+
if (x25519.scalarMult) {
|
|
64
|
+
return x25519.scalarMult(priv, pub);
|
|
65
|
+
}
|
|
66
|
+
if (x25519.sharedKey) {
|
|
67
|
+
return x25519.sharedKey(priv, pub);
|
|
68
|
+
}
|
|
69
|
+
throw new Error("@stablelib/x25519: compatible API not found");
|
|
70
|
+
}
|
|
71
|
+
export function sha256(input) {
|
|
72
|
+
const hashed = hash(new TextEncoder().encode(input));
|
|
73
|
+
return arrayToBase64(hashed);
|
|
74
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface EncryptionIdentity {
|
|
2
|
+
publicKey: CryptoKey | {
|
|
3
|
+
raw: Uint8Array;
|
|
4
|
+
};
|
|
5
|
+
privateKey: CryptoKey | {
|
|
6
|
+
raw: Uint8Array;
|
|
7
|
+
};
|
|
8
|
+
fingerprint: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* EncryptionEngine
|
|
12
|
+
* ----------------
|
|
13
|
+
* Core cryptographic engine.
|
|
14
|
+
*/
|
|
15
|
+
export declare class EncryptionEngine {
|
|
16
|
+
/**
|
|
17
|
+
* Generates a long-term X25519 identity keypair.
|
|
18
|
+
*/
|
|
19
|
+
static generateIdentity(): Promise<EncryptionIdentity>;
|
|
20
|
+
/**
|
|
21
|
+
* Derive an identity deterministically from a BIP39 mnemonic.
|
|
22
|
+
* Uses Stablelib Ed25519 to derive a keypair from seed and converts to X25519.
|
|
23
|
+
*/
|
|
24
|
+
static deriveIdentityFromMnemonic(mnemonic: string): Promise<EncryptionIdentity>;
|
|
25
|
+
/**
|
|
26
|
+
* Generates a SHA-256 fingerprint from a public key.
|
|
27
|
+
*/
|
|
28
|
+
static fingerprintFromPublicKey(publicKey: CryptoKey | {
|
|
29
|
+
raw: Uint8Array;
|
|
30
|
+
}): Promise<string>;
|
|
31
|
+
private static assertPublicKey;
|
|
32
|
+
}
|
|
33
|
+
export declare class CryptoError extends Error {
|
|
34
|
+
cause?: unknown;
|
|
35
|
+
constructor(message: string, cause?: unknown);
|
|
36
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
2
|
+
import * as ed25519 from "@stablelib/ed25519";
|
|
3
|
+
import ed2curve from "ed2curve";
|
|
4
|
+
import { fingerprintFromPublicRaw, } from "./crypto-provider";
|
|
5
|
+
/**
|
|
6
|
+
* EncryptionEngine
|
|
7
|
+
* ----------------
|
|
8
|
+
* Core cryptographic engine.
|
|
9
|
+
*/
|
|
10
|
+
export class EncryptionEngine {
|
|
11
|
+
/* ================================
|
|
12
|
+
* Identity
|
|
13
|
+
* ================================ */
|
|
14
|
+
/**
|
|
15
|
+
* Generates a long-term X25519 identity keypair.
|
|
16
|
+
*/
|
|
17
|
+
static async generateIdentity() {
|
|
18
|
+
try {
|
|
19
|
+
// Generate an Ed25519 keypair (stablelib) and convert to Curve25519
|
|
20
|
+
const ed = ed25519.generateKeyPair();
|
|
21
|
+
const skCurve = ed2curve.convertSecretKey(ed.secretKey);
|
|
22
|
+
const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
|
|
23
|
+
if (!skCurve || !pkCurve) {
|
|
24
|
+
throw new CryptoError("Failed to convert Ed25519 keys to Curve25519");
|
|
25
|
+
}
|
|
26
|
+
const pkBytes = new Uint8Array(pkCurve);
|
|
27
|
+
const skBytes = new Uint8Array(skCurve);
|
|
28
|
+
// Use raw key wrappers (Stablelib-backed) to avoid WebCrypto import variability
|
|
29
|
+
const publicKey = { type: "public", raw: pkBytes };
|
|
30
|
+
const privateKey = { type: "private", raw: skBytes };
|
|
31
|
+
const fingerprint = fingerprintFromPublicRaw(pkBytes);
|
|
32
|
+
return { publicKey, privateKey, fingerprint };
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new CryptoError("Failed to generate identity", err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Derive an identity deterministically from a BIP39 mnemonic.
|
|
40
|
+
* Uses Stablelib Ed25519 to derive a keypair from seed and converts to X25519.
|
|
41
|
+
*/
|
|
42
|
+
static async deriveIdentityFromMnemonic(mnemonic) {
|
|
43
|
+
try {
|
|
44
|
+
if (typeof mnemonic !== "string" || mnemonic.trim().length === 0) {
|
|
45
|
+
throw new CryptoError("Mnemonic must be a non-empty string");
|
|
46
|
+
}
|
|
47
|
+
// Convert mnemonic to seed (64 bytes) then reduce to 32 bytes
|
|
48
|
+
const seed = mnemonicToSeedSync(mnemonic); // Buffer
|
|
49
|
+
const seed32 = new Uint8Array(seed.slice(0, 32));
|
|
50
|
+
// Derive Ed25519 keypair from seed (stablelib)
|
|
51
|
+
const ed = ed25519.generateKeyPairFromSeed(seed32);
|
|
52
|
+
// Convert Ed25519 keys to X25519 (curve25519)
|
|
53
|
+
const skCurve = ed2curve.convertSecretKey(ed.secretKey);
|
|
54
|
+
const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
|
|
55
|
+
if (!skCurve || !pkCurve) {
|
|
56
|
+
throw new CryptoError("Failed to convert derived Ed25519 keys to Curve25519");
|
|
57
|
+
}
|
|
58
|
+
// Ensure plain Uint8Array
|
|
59
|
+
const pkCurveBytes = new Uint8Array(pkCurve);
|
|
60
|
+
const skCurveBytes = new Uint8Array(skCurve);
|
|
61
|
+
const publicKey = { type: "public", raw: pkCurveBytes };
|
|
62
|
+
const privateKey = { type: "private", raw: skCurveBytes };
|
|
63
|
+
const fingerprint = fingerprintFromPublicRaw(pkCurveBytes);
|
|
64
|
+
return { publicKey, privateKey, fingerprint };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new CryptoError("Failed to derive identity from mnemonic", err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/* ================================
|
|
71
|
+
* Fingerprinting
|
|
72
|
+
* ================================ */
|
|
73
|
+
/**
|
|
74
|
+
* Generates a SHA-256 fingerprint from a public key.
|
|
75
|
+
*/
|
|
76
|
+
static async fingerprintFromPublicKey(publicKey) {
|
|
77
|
+
// Accept both CryptoKey and raw wrappers; use stablelib sha256 via provider
|
|
78
|
+
const anyKey = publicKey;
|
|
79
|
+
let rawBytes;
|
|
80
|
+
if (anyKey && anyKey.raw instanceof Uint8Array) {
|
|
81
|
+
rawBytes = anyKey.raw;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.assertPublicKey(publicKey);
|
|
85
|
+
const exported = await crypto.subtle.exportKey("raw", publicKey);
|
|
86
|
+
rawBytes = new Uint8Array(exported);
|
|
87
|
+
}
|
|
88
|
+
return fingerprintFromPublicRaw(rawBytes);
|
|
89
|
+
}
|
|
90
|
+
/* ================================
|
|
91
|
+
* Validation Helpers
|
|
92
|
+
* ================================ */
|
|
93
|
+
static assertPublicKey(key) {
|
|
94
|
+
const anyKey = key;
|
|
95
|
+
if (!key)
|
|
96
|
+
throw new CryptoError("Invalid public key");
|
|
97
|
+
if (anyKey.raw instanceof Uint8Array)
|
|
98
|
+
return; // raw wrapper
|
|
99
|
+
if (key.type !== "public") {
|
|
100
|
+
throw new CryptoError("Invalid public key");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/* ================================
|
|
105
|
+
* Errors
|
|
106
|
+
* ================================ */
|
|
107
|
+
export class CryptoError extends Error {
|
|
108
|
+
cause;
|
|
109
|
+
constructor(message, cause) {
|
|
110
|
+
super(message);
|
|
111
|
+
this.name = "CryptoError";
|
|
112
|
+
this.cause = cause;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MajikUser } from "@thezelijah/majik-user";
|
|
2
|
+
import { SerializedMajikContact } from "../../majik-contact";
|
|
3
|
+
export interface MajikMessageIdentityJSON {
|
|
4
|
+
id: string;
|
|
5
|
+
user_id: string;
|
|
6
|
+
public_key: string;
|
|
7
|
+
phash: string;
|
|
8
|
+
label: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
restricted: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* MajikMessageIdentity
|
|
14
|
+
* Immutable identity container with integrity verification
|
|
15
|
+
*/
|
|
16
|
+
export declare class MajikMessageIdentity {
|
|
17
|
+
private readonly _id;
|
|
18
|
+
private readonly _userId;
|
|
19
|
+
private readonly _publicKey;
|
|
20
|
+
private readonly _phash;
|
|
21
|
+
private _label;
|
|
22
|
+
private readonly _timestamp;
|
|
23
|
+
private readonly _restricted;
|
|
24
|
+
/**
|
|
25
|
+
* Constructor is private to enforce controlled creation
|
|
26
|
+
*/
|
|
27
|
+
private constructor();
|
|
28
|
+
/**
|
|
29
|
+
* Create a new immutable identity from MajikUser
|
|
30
|
+
*/
|
|
31
|
+
static create(user: MajikUser, account: SerializedMajikContact, options?: {
|
|
32
|
+
label?: string;
|
|
33
|
+
restricted?: boolean;
|
|
34
|
+
}): MajikMessageIdentity;
|
|
35
|
+
get id(): string;
|
|
36
|
+
get userID(): string;
|
|
37
|
+
get publicKey(): string;
|
|
38
|
+
get phash(): string;
|
|
39
|
+
get label(): string;
|
|
40
|
+
get timestamp(): string;
|
|
41
|
+
get restricted(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Only mutable field
|
|
44
|
+
*/
|
|
45
|
+
set label(label: string);
|
|
46
|
+
/**
|
|
47
|
+
* Returns true if identity is restricted
|
|
48
|
+
*/
|
|
49
|
+
isRestricted(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Verify identity integrity
|
|
52
|
+
* Detects tampering of id/public_key
|
|
53
|
+
*/
|
|
54
|
+
validateIntegrity(): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Explicit verification helper
|
|
57
|
+
*/
|
|
58
|
+
matches(userId: string, publicKey: string): boolean;
|
|
59
|
+
toJSON(): MajikMessageIdentityJSON;
|
|
60
|
+
static fromJSON(json: string | MajikMessageIdentityJSON): MajikMessageIdentity;
|
|
61
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { hash } from "@stablelib/sha256";
|
|
2
|
+
import { arrayToBase64 } from "../../utils";
|
|
3
|
+
/**
|
|
4
|
+
* Utility assertions
|
|
5
|
+
*/
|
|
6
|
+
function assert(condition, message) {
|
|
7
|
+
if (!condition)
|
|
8
|
+
throw new Error(message);
|
|
9
|
+
}
|
|
10
|
+
function assertString(value, field) {
|
|
11
|
+
assert(typeof value === "string" && value.trim().length > 0, `${field} must be a non-empty string`);
|
|
12
|
+
}
|
|
13
|
+
function assertISODate(value, field) {
|
|
14
|
+
const date = new Date(value);
|
|
15
|
+
assert(!isNaN(date.getTime()), `${field} must be a valid ISO timestamp`);
|
|
16
|
+
}
|
|
17
|
+
function sha256(input) {
|
|
18
|
+
const hashed = hash(new TextEncoder().encode(input));
|
|
19
|
+
return arrayToBase64(hashed);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* MajikMessageIdentity
|
|
23
|
+
* Immutable identity container with integrity verification
|
|
24
|
+
*/
|
|
25
|
+
export class MajikMessageIdentity {
|
|
26
|
+
// 🔒 Private backing fields
|
|
27
|
+
_id;
|
|
28
|
+
_userId;
|
|
29
|
+
_publicKey;
|
|
30
|
+
_phash;
|
|
31
|
+
_label;
|
|
32
|
+
_timestamp;
|
|
33
|
+
_restricted;
|
|
34
|
+
/**
|
|
35
|
+
* Constructor is private to enforce controlled creation
|
|
36
|
+
*/
|
|
37
|
+
constructor(params) {
|
|
38
|
+
assertString(params.id, "id");
|
|
39
|
+
assertString(params.userId, "user_id");
|
|
40
|
+
assertString(params.publicKey, "public_key");
|
|
41
|
+
assertString(params.phash, "phash");
|
|
42
|
+
assertString(params.label, "label");
|
|
43
|
+
assertISODate(params.timestamp, "timestamp");
|
|
44
|
+
assert(typeof params.restricted === "boolean", "restricted must be boolean");
|
|
45
|
+
this._id = params.id;
|
|
46
|
+
this._userId = params.userId;
|
|
47
|
+
this._publicKey = params.publicKey;
|
|
48
|
+
this._phash = params.phash;
|
|
49
|
+
this._label = params.label;
|
|
50
|
+
this._timestamp = params.timestamp;
|
|
51
|
+
this._restricted = params.restricted;
|
|
52
|
+
// Final integrity check at construction
|
|
53
|
+
assert(this.validateIntegrity(), "Identity integrity validation failed");
|
|
54
|
+
}
|
|
55
|
+
// ─────────────────────────────
|
|
56
|
+
// Static factory
|
|
57
|
+
// ─────────────────────────────
|
|
58
|
+
/**
|
|
59
|
+
* Create a new immutable identity from MajikUser
|
|
60
|
+
*/
|
|
61
|
+
static create(user, account, options) {
|
|
62
|
+
assert(user, "MajikUser is required");
|
|
63
|
+
const userValidResult = user.validate();
|
|
64
|
+
if (!userValidResult.isValid) {
|
|
65
|
+
throw new Error(`Invalid MajikUser: ${userValidResult.errors.join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
const label = options?.label || account?.meta?.label || user.displayName;
|
|
68
|
+
assertString(label, "label");
|
|
69
|
+
const timestamp = new Date().toISOString();
|
|
70
|
+
const publicKey = account.publicKeyBase64;
|
|
71
|
+
const phash = sha256(`${user.id}:${publicKey}:${account.id}`);
|
|
72
|
+
return new MajikMessageIdentity({
|
|
73
|
+
id: account.id,
|
|
74
|
+
userId: user.id,
|
|
75
|
+
publicKey: publicKey,
|
|
76
|
+
phash,
|
|
77
|
+
label,
|
|
78
|
+
timestamp,
|
|
79
|
+
restricted: options?.restricted ?? false,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ─────────────────────────────
|
|
83
|
+
// Getters (safe, read-only)
|
|
84
|
+
// ─────────────────────────────
|
|
85
|
+
get id() {
|
|
86
|
+
return this._id;
|
|
87
|
+
}
|
|
88
|
+
get userID() {
|
|
89
|
+
return this._userId;
|
|
90
|
+
}
|
|
91
|
+
get publicKey() {
|
|
92
|
+
return this._publicKey;
|
|
93
|
+
}
|
|
94
|
+
get phash() {
|
|
95
|
+
return this._phash;
|
|
96
|
+
}
|
|
97
|
+
get label() {
|
|
98
|
+
return this._label;
|
|
99
|
+
}
|
|
100
|
+
get timestamp() {
|
|
101
|
+
return this._timestamp;
|
|
102
|
+
}
|
|
103
|
+
get restricted() {
|
|
104
|
+
return this._restricted;
|
|
105
|
+
}
|
|
106
|
+
// ─────────────────────────────
|
|
107
|
+
// Mutators (restricted)
|
|
108
|
+
// ─────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Only mutable field
|
|
111
|
+
*/
|
|
112
|
+
set label(label) {
|
|
113
|
+
assertString(label, "label");
|
|
114
|
+
this._label = label;
|
|
115
|
+
}
|
|
116
|
+
// ─────────────────────────────
|
|
117
|
+
// Identity checks
|
|
118
|
+
// ─────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Returns true if identity is restricted
|
|
121
|
+
*/
|
|
122
|
+
isRestricted() {
|
|
123
|
+
return this._restricted === true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Verify identity integrity
|
|
127
|
+
* Detects tampering of id/public_key
|
|
128
|
+
*/
|
|
129
|
+
validateIntegrity() {
|
|
130
|
+
const expected = sha256(`${this._userId}:${this._publicKey}:${this._id}`);
|
|
131
|
+
return expected === this._phash;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Explicit verification helper
|
|
135
|
+
*/
|
|
136
|
+
matches(userId, publicKey) {
|
|
137
|
+
assertString(userId, "userId");
|
|
138
|
+
assertString(publicKey, "publicKey");
|
|
139
|
+
const hash = sha256(`${userId}:${publicKey}:${this._id}`);
|
|
140
|
+
return hash === this._phash;
|
|
141
|
+
}
|
|
142
|
+
// ─────────────────────────────
|
|
143
|
+
// Serialization
|
|
144
|
+
// ─────────────────────────────
|
|
145
|
+
toJSON() {
|
|
146
|
+
return {
|
|
147
|
+
id: this._id,
|
|
148
|
+
user_id: this._userId,
|
|
149
|
+
public_key: this._publicKey,
|
|
150
|
+
phash: this._phash,
|
|
151
|
+
label: this._label,
|
|
152
|
+
timestamp: this._timestamp,
|
|
153
|
+
restricted: this._restricted,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
static fromJSON(json) {
|
|
157
|
+
const obj = typeof json === "string" ? JSON.parse(json) : json;
|
|
158
|
+
assert(typeof obj === "object" && obj !== null, "Invalid JSON object");
|
|
159
|
+
const identity = new MajikMessageIdentity({
|
|
160
|
+
id: obj.id,
|
|
161
|
+
userId: obj.user_id,
|
|
162
|
+
publicKey: obj.public_key,
|
|
163
|
+
phash: obj.phash,
|
|
164
|
+
label: obj.label,
|
|
165
|
+
timestamp: obj.timestamp,
|
|
166
|
+
restricted: obj.restricted,
|
|
167
|
+
});
|
|
168
|
+
assert(identity.validateIntegrity(), "Invalid phash in JSON");
|
|
169
|
+
return identity;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { MajikMessageIdentityJSON } from "./database/system/identity";
|
|
2
|
+
import { ISODateString } from "./types";
|
|
3
|
+
export type SerializedMajikContact = Omit<MajikContactData, "publicKey"> & {
|
|
4
|
+
publicKeyBase64: string;
|
|
5
|
+
};
|
|
6
|
+
export interface MajikContactMeta {
|
|
7
|
+
label?: string;
|
|
8
|
+
notes?: string;
|
|
9
|
+
blocked?: boolean;
|
|
10
|
+
createdAt?: ISODateString;
|
|
11
|
+
updatedAt?: ISODateString;
|
|
12
|
+
}
|
|
13
|
+
export interface MajikContactData {
|
|
14
|
+
id: string;
|
|
15
|
+
publicKey: CryptoKey | {
|
|
16
|
+
raw: Uint8Array;
|
|
17
|
+
};
|
|
18
|
+
fingerprint: string;
|
|
19
|
+
meta?: MajikContactMeta;
|
|
20
|
+
majikah_registered?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface MajikContactCard {
|
|
23
|
+
id: string;
|
|
24
|
+
publicKey: string;
|
|
25
|
+
fingerprint: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}
|
|
28
|
+
export declare class MajikContactError extends Error {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
constructor(message: string, cause?: unknown);
|
|
31
|
+
}
|
|
32
|
+
export declare class MajikContact {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly publicKey: CryptoKey | {
|
|
35
|
+
raw: Uint8Array;
|
|
36
|
+
};
|
|
37
|
+
readonly fingerprint: string;
|
|
38
|
+
meta: MajikContactMeta;
|
|
39
|
+
private majikah_registered?;
|
|
40
|
+
constructor(data: MajikContactData);
|
|
41
|
+
static create(id: string, publicKey: CryptoKey | {
|
|
42
|
+
raw: Uint8Array;
|
|
43
|
+
}, fingerprint: string, meta?: Partial<MajikContactMeta>): MajikContact;
|
|
44
|
+
private assertId;
|
|
45
|
+
private assertPublicKey;
|
|
46
|
+
private assertFingerprint;
|
|
47
|
+
private updateTimestamp;
|
|
48
|
+
updateLabel(label: string): this;
|
|
49
|
+
updateNotes(notes: string): this;
|
|
50
|
+
isBlocked(): boolean;
|
|
51
|
+
setBlocked(blocked: boolean): this;
|
|
52
|
+
block(): this;
|
|
53
|
+
unblock(): this;
|
|
54
|
+
isMajikahIdentityChecked(): boolean;
|
|
55
|
+
isMajikahRegistered(): boolean;
|
|
56
|
+
setMajikahStatus(status: boolean): this;
|
|
57
|
+
getDisplayName(): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Support both CryptoKey and raw-key wrappers (fallbacks when WebCrypto X25519 unsupported)
|
|
60
|
+
*/
|
|
61
|
+
getPublicKeyBase64(): Promise<string>;
|
|
62
|
+
toJSON(): Promise<SerializedMajikContact>;
|
|
63
|
+
/**
|
|
64
|
+
* Reconstruct a MajikContact from its serialized form
|
|
65
|
+
*/
|
|
66
|
+
static fromJSON(serialized: SerializedMajikContact): MajikContact;
|
|
67
|
+
/**
|
|
68
|
+
* Create a new MajikContact from a MajikMessageIdentityJSON
|
|
69
|
+
*/
|
|
70
|
+
static fromIdentityJSON(identityJSON: MajikMessageIdentityJSON): Promise<MajikContact>;
|
|
71
|
+
static isBlocked(contact: MajikContact): boolean;
|
|
72
|
+
}
|