@majikah/majik-message 0.2.12 → 0.2.14

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.
@@ -8,7 +8,7 @@ import { MajikMessageChat } from "./core/database/chat/majik-message-chat";
8
8
  import { MajikMessageIdentity } from "./core/database/system/identity";
9
9
  import { MajikKey } from "@majikah/majik-key";
10
10
  import { MajikFile, MajikFileJSON } from "@majikah/majik-file";
11
- import { MajikSignature, type MajikSignatureJSON, type MajikSignerPublicKeys, type VerificationResult } from "@majikah/majik-signature";
11
+ import { EnvelopeInfo, MajikSignature, SealInfo, SealVerificationResult, type MajikSignatureJSON, type MajikSignerPublicKeys, type VerificationResult } from "@majikah/majik-signature";
12
12
  type MajikMessageEvents = "message" | "envelope" | "untrusted" | "error" | "new-account" | "new-contact" | "removed-account" | "removed-contact" | "active-account-change";
13
13
  interface MajikMessageStatic<T extends MajikMessage> {
14
14
  new (config: MajikMessageConfig, id?: string): T;
@@ -720,13 +720,13 @@ export declare class MajikMessage {
720
720
  key?: MajikKey;
721
721
  expectedSignerId?: string;
722
722
  }): Promise<Array<VerificationResult & {
723
- handler: string | null;
724
- mimeType: string | null;
723
+ handler: string | undefined;
724
+ mimeType: string | undefined;
725
725
  error: Error | null;
726
726
  }>>;
727
727
  /**
728
728
  * Extract the embedded MajikSignature from a file.
729
- * Returns a fully typed MajikSignature instance, or null if not found.
729
+ * Returns an array of fully typed MajikSignature instances, or empty if none found.
730
730
  *
731
731
  * Does not verify — use verifyFile() to verify.
732
732
  *
@@ -736,7 +736,7 @@ export declare class MajikMessage {
736
736
  */
737
737
  extractSignature(file: Blob, options?: {
738
738
  mimeType?: string;
739
- }): Promise<MajikSignature | null>;
739
+ }): Promise<MajikSignature[]>;
740
740
  /**
741
741
  * Return a clean copy of the file with any embedded signature removed.
742
742
  * The returned bytes are exactly what was originally signed.
@@ -817,7 +817,73 @@ export declare class MajikMessage {
817
817
  */
818
818
  getFileSignatureInfo(file: Blob, options?: {
819
819
  mimeType?: string;
820
- }): Promise<MajikSignature | null>;
820
+ }): Promise<MajikSignature[]>;
821
+ /**
822
+ * Return a complete summary of the envelope state in one file read.
823
+ * Covers: isMultiSig, isSealed, issuer, all signatories, allowlist, seal info.
824
+ * Useful for rendering a signing status UI without multiple separate calls.
825
+ *
826
+ * Returns null if the file has no envelope.
827
+ *
828
+ * @example
829
+ * const info = await majik.getEnvelopeInfo(file);
830
+ * if (info?.isSealed) console.log("Sealed by", info.sealInfo?.sealedBy);
831
+ * console.log(`${info?.signatories?.signed.length} of ${info?.signatories?.all.length} signed`);
832
+ */
833
+ getEnvelopeInfo(file: Blob, options?: {
834
+ mimeType?: string;
835
+ }): Promise<EnvelopeInfo | null>;
836
+ /**
837
+ * Seal a restricted multi-sig file, preventing any further signatures.
838
+ *
839
+ * Only the issuer (the signer who established the allowlist) may seal.
840
+ * Resolves the signing key from the keystore — the account must be loaded
841
+ * but does NOT need to be unlocked (sealing does not use private keys).
842
+ *
843
+ * @example
844
+ * const { blob, sealInfo } = await majik.seal(signedFile);
845
+ * console.log("Sealed at", sealInfo.sealTimestamp);
846
+ */
847
+ seal(file: Blob, options?: {
848
+ mimeType?: string;
849
+ timestamp?: string;
850
+ accountId?: string;
851
+ }): Promise<{
852
+ blob: Blob;
853
+ sealInfo: SealInfo;
854
+ handler: string;
855
+ mimeType: string;
856
+ }>;
857
+ /**
858
+ * Verify the seal hash against the current signatories and seal timestamp.
859
+ * Returns invalid if the envelope is not sealed.
860
+ * Does NOT verify individual cryptographic signatures — call verifyFile() for that.
861
+ *
862
+ * @example
863
+ * const result = await majik.verifySeal(file);
864
+ * if (result.valid) console.log("Sealed by", result.sealedBy, "at", result.sealTimestamp);
865
+ */
866
+ verifySeal(file: Blob, options?: {
867
+ mimeType?: string;
868
+ }): Promise<SealVerificationResult>;
869
+ /**
870
+ * Get seal metadata without verifying.
871
+ * Returns null if the file is not sealed or has no envelope.
872
+ *
873
+ * @example
874
+ * const info = await majik.getSealInfo(file);
875
+ * if (info) console.log("Sealed by", majik.resolveSignerLabel(info.sealedBy));
876
+ */
877
+ getSealInfo(file: Blob, options?: {
878
+ mimeType?: string;
879
+ }): Promise<SealInfo | null>;
880
+ /**
881
+ * Returns true if the file has a sealed envelope (structural check, no crypto).
882
+ * Use verifySeal() to confirm the seal hash is intact.
883
+ */
884
+ isSealed(file: Blob, options?: {
885
+ mimeType?: string;
886
+ }): Promise<boolean>;
821
887
  /**
822
888
  * Resolve MajikSignerPublicKeys from whichever signer hint was provided.
823
889
  * Returns null if no hint was given (caller should fall back to self-reported keys).
@@ -1550,16 +1550,18 @@ export class MajikMessage {
1550
1550
  try {
1551
1551
  const publicKeys = await this._resolveSignerPublicKeys(options);
1552
1552
  if (publicKeys) {
1553
- return MajikSignature.verifyFile(file, publicKeys, {
1553
+ const results = await MajikSignature.verifyFile(file, publicKeys, {
1554
1554
  expectedSignerId: options?.expectedSignerId,
1555
1555
  mimeType: options?.mimeType,
1556
- });
1556
+ }, true);
1557
+ return results[0];
1557
1558
  }
1558
- // No signer provided — extract and use self-reported keys
1559
+ // No signer provided — extract and use self-reported keys from first signature.
1560
+ // For full multi-sig verification, pass a contactId or publicKeyBase64.
1559
1561
  const extracted = await MajikSignature.extractFrom(file, {
1560
1562
  mimeType: options?.mimeType,
1561
1563
  });
1562
- if (!extracted) {
1564
+ if (!extracted.length) {
1563
1565
  return {
1564
1566
  valid: false,
1565
1567
  signerId: "",
@@ -1568,10 +1570,12 @@ export class MajikMessage {
1568
1570
  reason: "No embedded signature found",
1569
1571
  };
1570
1572
  }
1571
- return MajikSignature.verifyFile(file, extracted.extractPublicKeys(), {
1572
- expectedSignerId: options?.expectedSignerId,
1573
+ const firstSig = extracted[0];
1574
+ const results = await MajikSignature.verifyFile(file, firstSig.extractPublicKeys(), {
1575
+ expectedSignerId: firstSig.signerId,
1573
1576
  mimeType: options?.mimeType,
1574
- });
1577
+ }, true);
1578
+ return results[0];
1575
1579
  }
1576
1580
  catch (err) {
1577
1581
  this.emit("error", err, { context: "verifyFile" });
@@ -1609,34 +1613,36 @@ export class MajikMessage {
1609
1613
  try {
1610
1614
  let result;
1611
1615
  if (publicKeys) {
1612
- result = await MajikSignature.verifyFile(file, publicKeys, {
1616
+ const results = await MajikSignature.verifyFile(file, publicKeys, {
1613
1617
  mimeType,
1614
1618
  expectedSignerId,
1615
1619
  });
1620
+ result = results[0];
1616
1621
  }
1617
1622
  else {
1618
- // No signer hint — use self-reported keys from each file's envelope
1619
1623
  const extracted = await MajikSignature.extractFrom(file, {
1620
1624
  mimeType,
1621
1625
  });
1622
- if (!extracted) {
1626
+ if (!extracted.length) {
1623
1627
  return {
1624
1628
  valid: false,
1625
- signerId: "",
1626
- contentHash: "",
1629
+ signerId: undefined,
1630
+ contentHash: undefined,
1627
1631
  timestamp: new Date().toISOString(),
1628
1632
  reason: "No embedded signature found",
1629
- handler: null,
1630
- mimeType: mimeType ?? null,
1633
+ handler: undefined,
1634
+ mimeType,
1631
1635
  error: null,
1632
1636
  };
1633
1637
  }
1634
- result = await MajikSignature.verifyFile(file, extracted.extractPublicKeys(), { mimeType, expectedSignerId });
1638
+ const firstSig = extracted[0];
1639
+ const results = await MajikSignature.verifyFile(file, firstSig.extractPublicKeys(), { mimeType, expectedSignerId: firstSig.signerId });
1640
+ result = results[0];
1635
1641
  }
1636
1642
  return {
1637
1643
  ...result,
1638
- handler: result.handler ?? null,
1639
- mimeType: mimeType ?? null,
1644
+ handler: result.handler,
1645
+ mimeType,
1640
1646
  error: null,
1641
1647
  };
1642
1648
  }
@@ -1644,11 +1650,11 @@ export class MajikMessage {
1644
1650
  this.emit("error", err, { context: "batchVerifyFiles" });
1645
1651
  return {
1646
1652
  valid: false,
1647
- signerId: "",
1648
- contentHash: "",
1653
+ signerId: undefined,
1654
+ contentHash: undefined,
1649
1655
  timestamp: new Date().toISOString(),
1650
- handler: null,
1651
- mimeType: mimeType ?? null,
1656
+ handler: undefined,
1657
+ mimeType,
1652
1658
  error: err instanceof Error ? err : new Error(String(err)),
1653
1659
  };
1654
1660
  }
@@ -1657,7 +1663,7 @@ export class MajikMessage {
1657
1663
  // ── Signature Utilities ───────────────────────────────────────────────────
1658
1664
  /**
1659
1665
  * Extract the embedded MajikSignature from a file.
1660
- * Returns a fully typed MajikSignature instance, or null if not found.
1666
+ * Returns an array of fully typed MajikSignature instances, or empty if none found.
1661
1667
  *
1662
1668
  * Does not verify — use verifyFile() to verify.
1663
1669
  *
@@ -1779,6 +1785,105 @@ export class MajikMessage {
1779
1785
  throw err;
1780
1786
  }
1781
1787
  }
1788
+ /**
1789
+ * Return a complete summary of the envelope state in one file read.
1790
+ * Covers: isMultiSig, isSealed, issuer, all signatories, allowlist, seal info.
1791
+ * Useful for rendering a signing status UI without multiple separate calls.
1792
+ *
1793
+ * Returns null if the file has no envelope.
1794
+ *
1795
+ * @example
1796
+ * const info = await majik.getEnvelopeInfo(file);
1797
+ * if (info?.isSealed) console.log("Sealed by", info.sealInfo?.sealedBy);
1798
+ * console.log(`${info?.signatories?.signed.length} of ${info?.signatories?.all.length} signed`);
1799
+ */
1800
+ async getEnvelopeInfo(file, options) {
1801
+ try {
1802
+ return MajikSignature.getEnvelopeInfo(file, options);
1803
+ }
1804
+ catch (err) {
1805
+ this.emit("error", err, { context: "getEnvelopeInfo" });
1806
+ throw err;
1807
+ }
1808
+ }
1809
+ // ── Seal ──────────────────────────────────────────────────────────────────
1810
+ /**
1811
+ * Seal a restricted multi-sig file, preventing any further signatures.
1812
+ *
1813
+ * Only the issuer (the signer who established the allowlist) may seal.
1814
+ * Resolves the signing key from the keystore — the account must be loaded
1815
+ * but does NOT need to be unlocked (sealing does not use private keys).
1816
+ *
1817
+ * @example
1818
+ * const { blob, sealInfo } = await majik.seal(signedFile);
1819
+ * console.log("Sealed at", sealInfo.sealTimestamp);
1820
+ */
1821
+ async seal(file, options) {
1822
+ const id = options?.accountId ?? this.getActiveAccount()?.id;
1823
+ if (!id)
1824
+ throw new Error("No active account — call setActiveAccount() first");
1825
+ try {
1826
+ const key = MajikKeyStore.get(id);
1827
+ if (!key)
1828
+ throw new Error(`Account not found in keystore: "${id}"`);
1829
+ return MajikSignature.seal(file, key, {
1830
+ mimeType: options?.mimeType,
1831
+ timestamp: options?.timestamp,
1832
+ });
1833
+ }
1834
+ catch (err) {
1835
+ this.emit("error", err, { context: "seal" });
1836
+ throw err;
1837
+ }
1838
+ }
1839
+ /**
1840
+ * Verify the seal hash against the current signatories and seal timestamp.
1841
+ * Returns invalid if the envelope is not sealed.
1842
+ * Does NOT verify individual cryptographic signatures — call verifyFile() for that.
1843
+ *
1844
+ * @example
1845
+ * const result = await majik.verifySeal(file);
1846
+ * if (result.valid) console.log("Sealed by", result.sealedBy, "at", result.sealTimestamp);
1847
+ */
1848
+ async verifySeal(file, options) {
1849
+ try {
1850
+ return MajikSignature.verifySeal(file, options);
1851
+ }
1852
+ catch (err) {
1853
+ this.emit("error", err, { context: "verifySeal" });
1854
+ throw err;
1855
+ }
1856
+ }
1857
+ /**
1858
+ * Get seal metadata without verifying.
1859
+ * Returns null if the file is not sealed or has no envelope.
1860
+ *
1861
+ * @example
1862
+ * const info = await majik.getSealInfo(file);
1863
+ * if (info) console.log("Sealed by", majik.resolveSignerLabel(info.sealedBy));
1864
+ */
1865
+ async getSealInfo(file, options) {
1866
+ try {
1867
+ return MajikSignature.getSealInfo(file, options);
1868
+ }
1869
+ catch (err) {
1870
+ this.emit("error", err, { context: "getSealInfo" });
1871
+ throw err;
1872
+ }
1873
+ }
1874
+ /**
1875
+ * Returns true if the file has a sealed envelope (structural check, no crypto).
1876
+ * Use verifySeal() to confirm the seal hash is intact.
1877
+ */
1878
+ async isSealed(file, options) {
1879
+ try {
1880
+ return MajikSignature.isSealed(file, options);
1881
+ }
1882
+ catch (err) {
1883
+ this.emit("error", err, { context: "isSealed" });
1884
+ throw err;
1885
+ }
1886
+ }
1782
1887
  // ── Private: Signer resolution ────────────────────────────────────────────
1783
1888
  /**
1784
1889
  * Resolve MajikSignerPublicKeys from whichever signer hint was provided.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@majikah/majik-message",
3
3
  "type": "module",
4
4
  "description": "Post-quantum end-to-end encryption with ML-KEM-768. Seed phrase–based accounts. Auto-expiring messages. Offline-ready. Exportable encrypted messages. Tamper-proof threads with blockchain-like integrity. Quantum-resistant messaging.",
5
- "version": "0.2.12",
5
+ "version": "0.2.14",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -81,9 +81,9 @@
81
81
  "dependencies": {
82
82
  "@bokuweb/zstd-wasm": "^0.0.27",
83
83
  "@majikah/majik-envelope": "^0.0.1",
84
- "@majikah/majik-file": "^0.0.20",
84
+ "@majikah/majik-file": "^0.0.21",
85
85
  "@majikah/majik-key": "^0.2.3",
86
- "@majikah/majik-signature": "^0.0.10",
86
+ "@majikah/majik-signature": "^0.0.14",
87
87
  "@noble/hashes": "^2.0.1",
88
88
  "@noble/post-quantum": "^0.5.4",
89
89
  "@scure/bip39": "^1.6.0",