raz-encrypt 0.1.0

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/dist/canonicalJson.d.ts +3 -0
  4. package/dist/canonicalJson.js +25 -0
  5. package/dist/canonicalJson.js.map +1 -0
  6. package/dist/core.d.ts +23 -0
  7. package/dist/core.js +19 -0
  8. package/dist/core.js.map +1 -0
  9. package/dist/crypto/classical.d.ts +10 -0
  10. package/dist/crypto/classical.js +21 -0
  11. package/dist/crypto/classical.js.map +1 -0
  12. package/dist/crypto/frameCipher.d.ts +23 -0
  13. package/dist/crypto/frameCipher.js +58 -0
  14. package/dist/crypto/frameCipher.js.map +1 -0
  15. package/dist/crypto/hybrid.d.ts +117 -0
  16. package/dist/crypto/hybrid.js +180 -0
  17. package/dist/crypto/hybrid.js.map +1 -0
  18. package/dist/crypto/nodeHybrid.d.ts +42 -0
  19. package/dist/crypto/nodeHybrid.js +65 -0
  20. package/dist/crypto/nodeHybrid.js.map +1 -0
  21. package/dist/crypto/pqc.d.ts +19 -0
  22. package/dist/crypto/pqc.js +28 -0
  23. package/dist/crypto/pqc.js.map +1 -0
  24. package/dist/fileCrypto.d.ts +6 -0
  25. package/dist/fileCrypto.js +45 -0
  26. package/dist/fileCrypto.js.map +1 -0
  27. package/dist/fingerprint.d.ts +11 -0
  28. package/dist/fingerprint.js +18 -0
  29. package/dist/fingerprint.js.map +1 -0
  30. package/dist/identity.d.ts +42 -0
  31. package/dist/identity.js +87 -0
  32. package/dist/identity.js.map +1 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.js +7 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/store.d.ts +28 -0
  37. package/dist/store.js +62 -0
  38. package/dist/store.js.map +1 -0
  39. package/dist/trust.d.ts +41 -0
  40. package/dist/trust.js +98 -0
  41. package/dist/trust.js.map +1 -0
  42. package/package.json +48 -0
@@ -0,0 +1,180 @@
1
+ import { gcm } from "@noble/ciphers/aes.js";
2
+ import { hkdf } from "@noble/hashes/hkdf.js";
3
+ import { sha384 } from "@noble/hashes/sha2.js";
4
+ import { bytesToHex, concatBytes, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
5
+ import { canonicalize, hashDocument } from "../canonicalJson.js";
6
+ import { classicalSharedSecret, classicalSign, classicalVerify } from "./classical.js";
7
+ import { pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify } from "./pqc.js";
8
+ // No import of ../store.js or ../trust.js here (only their LogEntry TYPE,
9
+ // which is erased at compile time) — this file must stay resolvable by a
10
+ // browser bundler on its own. The node:fs-backed default wiring for
11
+ // sign/verify/encryptFor/decryptFrom lives in ./nodeHybrid.ts instead,
12
+ // which is what "." (not "./core") re-exports.
13
+ const HYBRID_INFO = utf8ToBytes("raz-encrypt-hybrid-v1");
14
+ const ALGORITHMS = { classical: "ECDSA-P384", pqc: "ML-DSA-87" };
15
+ export class UntrustedPartyError extends Error {
16
+ constructor(alias) {
17
+ super(`"${alias}" is not in the local trust list. Call trustParty() with their public card first.`);
18
+ this.name = "UntrustedPartyError";
19
+ }
20
+ }
21
+ /**
22
+ * The exact bytes both signatures are computed over. Binds `expiresAt` into
23
+ * the signed payload (not just `documentHash`) so a certificate's expiry
24
+ * can't be stripped or altered in transit without invalidating both
25
+ * signatures — an untrusted intermediary who only has the certificate
26
+ * (no secret key) must not be able to turn an expired certificate back into
27
+ * one that verify() accepts.
28
+ */
29
+ function signedMessage(documentHash, expiresAt) {
30
+ return utf8ToBytes(canonicalize({ documentHash, expiresAt: expiresAt ?? null }));
31
+ }
32
+ async function requireTrusted(checkTrust, counterpart) {
33
+ const status = await checkTrust(counterpart);
34
+ if (status !== "trusted")
35
+ throw new UntrustedPartyError(counterpart.alias);
36
+ }
37
+ async function log(onLog, entry) {
38
+ if (onLog)
39
+ await onLog(entry);
40
+ }
41
+ /**
42
+ * Signs `document` with both the classical (ECDSA-P384) and post-quantum
43
+ * (ML-DSA-87) keys. Permanent by default; pass `options.expiresAt` to make
44
+ * verify() reject it once that time has passed. Takes no trust dependency —
45
+ * signing never checks anyone else's trust status, only your own keys.
46
+ */
47
+ export async function signCore(identity, document, options = {}, deps = {}) {
48
+ const documentHash = hashDocument(document);
49
+ const message = signedMessage(documentHash, options.expiresAt);
50
+ const certificate = {
51
+ documentHash,
52
+ classicalSignature: bytesToHex(classicalSign(message, identity.classical.secretKey)),
53
+ pqcSignature: bytesToHex(pqcSign(message, identity.pqcSign.secretKey)),
54
+ algorithms: ALGORITHMS,
55
+ signer: {
56
+ alias: identity.alias,
57
+ classicalPublicKey: bytesToHex(identity.classical.publicKey),
58
+ pqcSignPublicKey: bytesToHex(identity.pqcSign.publicKey),
59
+ },
60
+ signedAt: new Date().toISOString(),
61
+ ...(options.expiresAt ? { expiresAt: options.expiresAt } : {}),
62
+ };
63
+ await log(deps.onLog, { action: "sign", documentHash });
64
+ return certificate;
65
+ }
66
+ /**
67
+ * Verifies `certificate` over `document` using an injected trust check
68
+ * (`deps.checkTrust`) instead of a hard-wired trust store. Requires BOTH the
69
+ * classical and post-quantum signatures to check out — either one failing
70
+ * fails the whole verification.
71
+ */
72
+ export async function verifyCore(identity, document, certificate, deps) {
73
+ const record = (result) => {
74
+ void log(deps.onLog, {
75
+ action: "verify",
76
+ documentHash: certificate.documentHash,
77
+ counterpartAlias: certificate.signer.alias,
78
+ result: result.valid ? "valid" : result.reason,
79
+ });
80
+ return result;
81
+ };
82
+ const status = await deps.checkTrust(certificate.signer);
83
+ if (status === "untrusted") {
84
+ return record({ valid: false, reason: "untrusted-signer" });
85
+ }
86
+ if (status === "revoked") {
87
+ return record({ valid: false, reason: "revoked-signer" });
88
+ }
89
+ if (hashDocument(document) !== certificate.documentHash) {
90
+ return record({ valid: false, reason: "hash-mismatch" });
91
+ }
92
+ if (certificate.expiresAt && Date.now() >= Date.parse(certificate.expiresAt)) {
93
+ return record({ valid: false, reason: "expired" });
94
+ }
95
+ const message = signedMessage(certificate.documentHash, certificate.expiresAt);
96
+ const classicalOk = classicalVerify(hexToBytes(certificate.classicalSignature), message, hexToBytes(certificate.signer.classicalPublicKey));
97
+ if (!classicalOk)
98
+ return record({ valid: false, reason: "classical-signature-mismatch" });
99
+ const pqcOk = pqcVerify(hexToBytes(certificate.pqcSignature), message, hexToBytes(certificate.signer.pqcSignPublicKey));
100
+ if (!pqcOk)
101
+ return record({ valid: false, reason: "pqc-signature-mismatch" });
102
+ return record({ valid: true });
103
+ }
104
+ function deriveHybridKey(ecdhSecret, kemSecret) {
105
+ return hkdf(sha384, concatBytes(ecdhSecret, kemSecret), undefined, HYBRID_INFO, 32);
106
+ }
107
+ /**
108
+ * Encrypts `data` for `counterpart` using a symmetric key derived from BOTH
109
+ * an ECDH-P384 exchange and an ML-KEM-1024 encapsulation (HKDF-SHA384
110
+ * combines the two secrets), then AES-256-GCM. Throws UntrustedPartyError
111
+ * unless `deps.checkTrust(counterpart)` reports "trusted".
112
+ */
113
+ export async function encryptForCore(identity, counterpart, data, deps) {
114
+ await requireTrusted(deps.checkTrust, counterpart);
115
+ const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
116
+ const { cipherText: kemCipherText, sharedSecret: kemSecret } = pqcEncapsulate(hexToBytes(counterpart.pqcKemPublicKey));
117
+ const key = deriveHybridKey(ecdhSecret, kemSecret);
118
+ const nonce = randomBytes(12);
119
+ const ciphertext = gcm(key, nonce).encrypt(utf8ToBytes(canonicalize(data)));
120
+ await log(deps.onLog, { action: "encrypt", counterpartAlias: counterpart.alias });
121
+ return { kemCipherText: bytesToHex(kemCipherText), nonce: bytesToHex(nonce), ciphertext: bytesToHex(ciphertext) };
122
+ }
123
+ /**
124
+ * Decrypts a box produced by `encryptFor`. `counterpart` is the sender's
125
+ * public card. Throws UntrustedPartyError unless `deps.checkTrust(counterpart)`
126
+ * reports "trusted" — this stops a caller from accidentally decrypting "as
127
+ * if" a message came from someone they never actually trusted.
128
+ */
129
+ export async function decryptFromCore(identity, counterpart, box, deps) {
130
+ await requireTrusted(deps.checkTrust, counterpart);
131
+ const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
132
+ const kemSecret = pqcDecapsulate(hexToBytes(box.kemCipherText), identity.pqcKem.secretKey);
133
+ const key = deriveHybridKey(ecdhSecret, kemSecret);
134
+ const plaintext = gcm(key, hexToBytes(box.nonce)).decrypt(hexToBytes(box.ciphertext));
135
+ await log(deps.onLog, { action: "decrypt", counterpartAlias: counterpart.alias });
136
+ return JSON.parse(new TextDecoder().decode(plaintext));
137
+ }
138
+ // --- Session handshake ------------------------------------------------
139
+ //
140
+ // encryptForCore/decryptFromCore above do a full ECDH + ML-KEM encapsulation
141
+ // PER CALL — fine for a one-off document, prohibitively expensive if called
142
+ // once per 16 KiB chunk of a large file transfer. The functions below run
143
+ // that hybrid exchange ONCE per connection and hand back a pair of
144
+ // directional keys the caller reuses (via a separate per-frame cipher, not
145
+ // implemented here) for as many messages as the connection carries.
146
+ const SESSION_INFO_INITIATOR_TO_RESPONDER = utf8ToBytes("raz-encrypt-session-v1|initiator-to-responder");
147
+ const SESSION_INFO_RESPONDER_TO_INITIATOR = utf8ToBytes("raz-encrypt-session-v1|responder-to-initiator");
148
+ function deriveSessionKeys(ecdhSecret, kemSecret) {
149
+ const ikm = concatBytes(ecdhSecret, kemSecret);
150
+ return {
151
+ initiatorToResponder: hkdf(sha384, ikm, undefined, SESSION_INFO_INITIATOR_TO_RESPONDER, 32),
152
+ responderToInitiator: hkdf(sha384, ikm, undefined, SESSION_INFO_RESPONDER_TO_INITIATOR, 32),
153
+ };
154
+ }
155
+ /**
156
+ * Establishes a session as the connection's initiator (the side that
157
+ * created the WebRTC offer, in raz-encrypt-agnostic terms: whichever side
158
+ * owns the single ML-KEM encapsulation this handshake performs). Computes
159
+ * the ECDH-P384 secret with `counterpart` and one ML-KEM-1024 encapsulation
160
+ * toward their KEM public key, then HKDF-splits the combined secret into two
161
+ * directional AES-256-GCM keys. No trust dependency: establishing a session
162
+ * never checks anyone's trust status, same as signCore.
163
+ */
164
+ export function establishSessionInitiatorCore(identity, counterpart) {
165
+ const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
166
+ const { cipherText, sharedSecret: kemSecret } = pqcEncapsulate(hexToBytes(counterpart.pqcKemPublicKey));
167
+ return { kemCipherText: bytesToHex(cipherText), keys: deriveSessionKeys(ecdhSecret, kemSecret) };
168
+ }
169
+ /**
170
+ * Establishes a session as the connection's responder: computes the same
171
+ * ECDH-P384 secret, decapsulates the initiator's `kemCipherText` with our
172
+ * own KEM secret key, and derives the same two directional keys the
173
+ * initiator derived.
174
+ */
175
+ export function establishSessionResponderCore(identity, counterpart, kemCipherText) {
176
+ const ecdhSecret = classicalSharedSecret(identity.classical.secretKey, hexToBytes(counterpart.classicalPublicKey));
177
+ const kemSecret = pqcDecapsulate(hexToBytes(kemCipherText), identity.pqcKem.secretKey);
178
+ return deriveSessionKeys(ecdhSecret, kemSecret);
179
+ }
180
+ //# sourceMappingURL=hybrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid.js","sourceRoot":"","sources":["../../src/crypto/hybrid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Default (Node, fs-backed) wiring for the "." package export — the
3
+ * library's original public API. Behavior is unchanged from before the
4
+ * injectable-trust/log refactor: these still check identity's persisted
5
+ * trust store and append to its persisted audit log.
6
+ *
7
+ * This file is deliberately separate from ./hybrid.ts: it's the only place
8
+ * that imports ../trust.js and ../store.js (both node:fs-backed), so
9
+ * ./hybrid.ts — and therefore the "./core" export built from it — stays
10
+ * resolvable by a browser bundler with no node:fs in its module graph.
11
+ */
12
+ import type { Identity, PublicCard } from "../identity.js";
13
+ import { type Certificate, type EncryptedBox, type SignOptions, type VerifyResult } from "./hybrid.js";
14
+ export { UntrustedPartyError } from "./hybrid.js";
15
+ export type { Certificate, EncryptedBox, SignOptions, VerifyFailureReason, VerifyResult } from "./hybrid.js";
16
+ /**
17
+ * Signs `document` with both the classical (ECDSA-P384) and post-quantum
18
+ * (ML-DSA-87) keys. Permanent by default; pass `options.expiresAt` to make
19
+ * verify() reject it once that time has passed.
20
+ */
21
+ export declare function sign(identity: Identity, document: unknown, options?: SignOptions): Promise<Certificate>;
22
+ /**
23
+ * Verifies `certificate` over `document`. Requires the signer to already be
24
+ * in `identity`'s local trust list (see trustParty), and requires BOTH the
25
+ * classical and post-quantum signatures to check out — either one failing
26
+ * fails the whole verification.
27
+ */
28
+ export declare function verify(identity: Identity, document: unknown, certificate: Certificate): Promise<VerifyResult>;
29
+ /**
30
+ * Encrypts `data` for `counterpart` using a symmetric key derived from BOTH
31
+ * an ECDH-P384 exchange and an ML-KEM-1024 encapsulation (HKDF-SHA384
32
+ * combines the two secrets), then AES-256-GCM. Throws UntrustedPartyError
33
+ * unless `counterpart` is already in `identity`'s local trust list.
34
+ */
35
+ export declare function encryptFor(identity: Identity, counterpart: PublicCard, data: unknown): Promise<EncryptedBox>;
36
+ /**
37
+ * Decrypts a box produced by `encryptFor`. `counterpart` is the sender's
38
+ * public card. Throws UntrustedPartyError unless `counterpart` is already
39
+ * in `identity`'s local trust list — this stops a caller from accidentally
40
+ * decrypting "as if" a message came from someone they never actually trusted.
41
+ */
42
+ export declare function decryptFrom<T = unknown>(identity: Identity, counterpart: PublicCard, box: EncryptedBox): Promise<T>;
@@ -0,0 +1,65 @@
1
+ import { appendLogEntry } from "../store.js";
2
+ import { getTrustRecordsByAlias, getTrustedPartiesByAlias } from "../trust.js";
3
+ import { decryptFromCore, encryptForCore, signCore, verifyCore, } from "./hybrid.js";
4
+ export { UntrustedPartyError } from "./hybrid.js";
5
+ async function trustStatusForSigner(identity, signer) {
6
+ const allRecords = await getTrustRecordsByAlias(identity, signer.alias);
7
+ const matchingRecord = allRecords.find((r) => r.card.classicalPublicKey === signer.classicalPublicKey && r.card.pqcSignPublicKey === signer.pqcSignPublicKey);
8
+ if (!matchingRecord)
9
+ return "untrusted";
10
+ if (matchingRecord.revokedAt)
11
+ return "revoked";
12
+ return "trusted";
13
+ }
14
+ async function trustStatusForCard(identity, card) {
15
+ const candidates = await getTrustedPartiesByAlias(identity, card.alias);
16
+ const isTrusted = candidates.some((trusted) => trusted.classicalPublicKey === card.classicalPublicKey &&
17
+ trusted.pqcSignPublicKey === card.pqcSignPublicKey &&
18
+ trusted.pqcKemPublicKey === card.pqcKemPublicKey);
19
+ return isTrusted ? "trusted" : "untrusted";
20
+ }
21
+ /**
22
+ * Signs `document` with both the classical (ECDSA-P384) and post-quantum
23
+ * (ML-DSA-87) keys. Permanent by default; pass `options.expiresAt` to make
24
+ * verify() reject it once that time has passed.
25
+ */
26
+ export async function sign(identity, document, options = {}) {
27
+ return signCore(identity, document, options, { onLog: (entry) => appendLogEntry(identity, entry) });
28
+ }
29
+ /**
30
+ * Verifies `certificate` over `document`. Requires the signer to already be
31
+ * in `identity`'s local trust list (see trustParty), and requires BOTH the
32
+ * classical and post-quantum signatures to check out — either one failing
33
+ * fails the whole verification.
34
+ */
35
+ export async function verify(identity, document, certificate) {
36
+ return verifyCore(identity, document, certificate, {
37
+ checkTrust: (signer) => trustStatusForSigner(identity, signer),
38
+ onLog: (entry) => appendLogEntry(identity, entry),
39
+ });
40
+ }
41
+ /**
42
+ * Encrypts `data` for `counterpart` using a symmetric key derived from BOTH
43
+ * an ECDH-P384 exchange and an ML-KEM-1024 encapsulation (HKDF-SHA384
44
+ * combines the two secrets), then AES-256-GCM. Throws UntrustedPartyError
45
+ * unless `counterpart` is already in `identity`'s local trust list.
46
+ */
47
+ export async function encryptFor(identity, counterpart, data) {
48
+ return encryptForCore(identity, counterpart, data, {
49
+ checkTrust: (card) => trustStatusForCard(identity, card),
50
+ onLog: (entry) => appendLogEntry(identity, entry),
51
+ });
52
+ }
53
+ /**
54
+ * Decrypts a box produced by `encryptFor`. `counterpart` is the sender's
55
+ * public card. Throws UntrustedPartyError unless `counterpart` is already
56
+ * in `identity`'s local trust list — this stops a caller from accidentally
57
+ * decrypting "as if" a message came from someone they never actually trusted.
58
+ */
59
+ export async function decryptFrom(identity, counterpart, box) {
60
+ return decryptFromCore(identity, counterpart, box, {
61
+ checkTrust: (card) => trustStatusForCard(identity, card),
62
+ onLog: (entry) => appendLogEntry(identity, entry),
63
+ });
64
+ }
65
+ //# sourceMappingURL=nodeHybrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeHybrid.js","sourceRoot":"","sources":["../../src/crypto/nodeHybrid.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface PqcSignKeypair {
2
+ publicKey: Uint8Array;
3
+ secretKey: Uint8Array;
4
+ }
5
+ export interface PqcKemKeypair {
6
+ publicKey: Uint8Array;
7
+ secretKey: Uint8Array;
8
+ }
9
+ /** ML-DSA-87 (FIPS 204, category-5 parameter set) — the post-quantum signing layer. */
10
+ export declare function generatePqcSignKeypair(): PqcSignKeypair;
11
+ export declare function pqcSign(message: Uint8Array, secretKey: Uint8Array): Uint8Array;
12
+ export declare function pqcVerify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean;
13
+ /** ML-KEM-1024 (FIPS 203, category-5 parameter set) — the post-quantum key-exchange layer. */
14
+ export declare function generatePqcKemKeypair(): PqcKemKeypair;
15
+ export declare function pqcEncapsulate(theirPublicKey: Uint8Array): {
16
+ cipherText: Uint8Array;
17
+ sharedSecret: Uint8Array;
18
+ };
19
+ export declare function pqcDecapsulate(cipherText: Uint8Array, secretKey: Uint8Array): Uint8Array;
@@ -0,0 +1,28 @@
1
+ import { ml_dsa87 } from "@noble/post-quantum/ml-dsa.js";
2
+ import { ml_kem1024 } from "@noble/post-quantum/ml-kem.js";
3
+ /** ML-DSA-87 (FIPS 204, category-5 parameter set) — the post-quantum signing layer. */
4
+ export function generatePqcSignKeypair() {
5
+ return ml_dsa87.keygen();
6
+ }
7
+ export function pqcSign(message, secretKey) {
8
+ return ml_dsa87.sign(message, secretKey);
9
+ }
10
+ export function pqcVerify(signature, message, publicKey) {
11
+ try {
12
+ return ml_dsa87.verify(signature, message, publicKey);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ /** ML-KEM-1024 (FIPS 203, category-5 parameter set) — the post-quantum key-exchange layer. */
19
+ export function generatePqcKemKeypair() {
20
+ return ml_kem1024.keygen();
21
+ }
22
+ export function pqcEncapsulate(theirPublicKey) {
23
+ return ml_kem1024.encapsulate(theirPublicKey);
24
+ }
25
+ export function pqcDecapsulate(cipherText, secretKey) {
26
+ return ml_kem1024.decapsulate(cipherText, secretKey);
27
+ }
28
+ //# sourceMappingURL=pqc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pqc.js","sourceRoot":"","sources":["../../src/crypto/pqc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAY3D,uFAAuF;AACvF,MAAM,UAAU,sBAAsB;IACpC,OAAO,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAmB,EAAE,SAAqB;IAChE,OAAO,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,SAAqB,EAAE,OAAmB,EAAE,SAAqB;IACzF,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,qBAAqB;IACnC,OAAO,UAAU,CAAC,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,cAA0B;IACvD,OAAO,UAAU,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,UAAsB,EAAE,SAAqB;IAC1E,OAAO,UAAU,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,6 @@
1
+ /** Encrypts `data` (any JSON-serializable value) at rest under a passphrase-derived AES-256-GCM key. */
2
+ export declare function encryptToFile(filePath: string, passphrase: string, data: unknown): Promise<void>;
3
+ export declare class DecryptionFailedError extends Error {
4
+ constructor();
5
+ }
6
+ export declare function decryptFromFile<T>(filePath: string, passphrase: string): Promise<T>;
@@ -0,0 +1,45 @@
1
+ import { gcm } from "@noble/ciphers/aes.js";
2
+ import { scrypt } from "@noble/hashes/scrypt.js";
3
+ import { bytesToHex, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
4
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import { dirname } from "node:path";
6
+ const SCRYPT_PARAMS = { N: 2 ** 17, r: 8, p: 1, dkLen: 32 };
7
+ function deriveKey(passphrase, salt) {
8
+ return scrypt(utf8ToBytes(passphrase), salt, SCRYPT_PARAMS);
9
+ }
10
+ /** Encrypts `data` (any JSON-serializable value) at rest under a passphrase-derived AES-256-GCM key. */
11
+ export async function encryptToFile(filePath, passphrase, data) {
12
+ const salt = randomBytes(16);
13
+ const nonce = randomBytes(12);
14
+ const key = deriveKey(passphrase, salt);
15
+ const plaintext = utf8ToBytes(JSON.stringify(data));
16
+ const ciphertext = gcm(key, nonce).encrypt(plaintext);
17
+ const payload = {
18
+ salt: bytesToHex(salt),
19
+ nonce: bytesToHex(nonce),
20
+ ciphertext: bytesToHex(ciphertext),
21
+ };
22
+ await mkdir(dirname(filePath), { recursive: true });
23
+ await writeFile(filePath, JSON.stringify(payload), { mode: 0o600 });
24
+ }
25
+ export class DecryptionFailedError extends Error {
26
+ constructor() {
27
+ super("Failed to decrypt local store: wrong passphrase or corrupted/tampered file.");
28
+ this.name = "DecryptionFailedError";
29
+ }
30
+ }
31
+ export async function decryptFromFile(filePath, passphrase) {
32
+ const raw = await readFile(filePath, "utf8");
33
+ const payload = JSON.parse(raw);
34
+ const salt = hexToBytes(payload.salt);
35
+ const nonce = hexToBytes(payload.nonce);
36
+ const key = deriveKey(passphrase, salt);
37
+ try {
38
+ const plaintext = gcm(key, nonce).decrypt(hexToBytes(payload.ciphertext));
39
+ return JSON.parse(new TextDecoder().decode(plaintext));
40
+ }
41
+ catch {
42
+ throw new DecryptionFailedError();
43
+ }
44
+ }
45
+ //# sourceMappingURL=fileCrypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileCrypto.js","sourceRoot":"","sources":["../src/fileCrypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,aAAa,GAAG,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAQ5D,SAAS,SAAS,CAAC,UAAkB,EAAE,IAAgB;IACrD,OAAO,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;AAC9D,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,UAAkB,EAAE,IAAa;IACrF,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtD,MAAM,OAAO,GAAkB;QAC7B,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC;QACtB,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC;QACxB,UAAU,EAAE,UAAU,CAAC,UAAU,CAAC;KACnC,CAAC;IAEF,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C;QACE,KAAK,CAAC,6EAA6E,CAAC,CAAC;QACrF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,QAAgB,EAAE,UAAkB;IAC3E,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IACjD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;QAC1E,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAM,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,qBAAqB,EAAE,CAAC;IACpC,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { PublicCard } from "./identity.js";
2
+ /**
3
+ * A short, human-comparable fingerprint of a PublicCard (PGP/Signal
4
+ * "safety number" style: grouped hex). Two parties should read this aloud
5
+ * or compare it over a side channel (phone call, in person) BEFORE calling
6
+ * trustParty() with a card they received over an unauthenticated channel
7
+ * (email, chat) — this is what catches a spoofed or intercepted card.
8
+ *
9
+ * Does not change trustParty() itself; this is an additive, optional check.
10
+ */
11
+ export declare function fingerprint(card: PublicCard): string;
@@ -0,0 +1,18 @@
1
+ import { sha384 } from "@noble/hashes/sha2.js";
2
+ import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
3
+ import { canonicalize } from "./canonicalJson.js";
4
+ /**
5
+ * A short, human-comparable fingerprint of a PublicCard (PGP/Signal
6
+ * "safety number" style: grouped hex). Two parties should read this aloud
7
+ * or compare it over a side channel (phone call, in person) BEFORE calling
8
+ * trustParty() with a card they received over an unauthenticated channel
9
+ * (email, chat) — this is what catches a spoofed or intercepted card.
10
+ *
11
+ * Does not change trustParty() itself; this is an additive, optional check.
12
+ */
13
+ export function fingerprint(card) {
14
+ const hash = sha384(utf8ToBytes(canonicalize(card)));
15
+ const hex = bytesToHex(hash.slice(0, 20)).toUpperCase();
16
+ return hex.match(/.{1,4}/g).join(" ");
17
+ }
18
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,IAAgB;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACxD,OAAO,GAAG,CAAC,KAAK,CAAC,SAAS,CAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type ClassicalKeypair } from "./crypto/classical.js";
2
+ import { type PqcKemKeypair, type PqcSignKeypair } from "./crypto/pqc.js";
3
+ export interface Identity {
4
+ alias: string;
5
+ storageDir: string;
6
+ classical: ClassicalKeypair;
7
+ pqcSign: PqcSignKeypair;
8
+ pqcKem: PqcKemKeypair;
9
+ }
10
+ /** The public half of an Identity — safe to hand to the other party. */
11
+ export interface PublicCard {
12
+ alias: string;
13
+ classicalPublicKey: string;
14
+ pqcSignPublicKey: string;
15
+ pqcKemPublicKey: string;
16
+ }
17
+ export declare const MIN_PASSPHRASE_LENGTH = 12;
18
+ export interface CreateIdentityOptions {
19
+ storageDir: string;
20
+ passphrase: string;
21
+ alias: string;
22
+ /** Overwrite an existing identity at this storageDir. Defaults to false. */
23
+ overwrite?: boolean;
24
+ }
25
+ /**
26
+ * Generates a new hybrid classical+post-quantum identity (one ECDSA/ECDH-P384
27
+ * keypair, one ML-DSA-87 signing keypair, one ML-KEM-1024 key-exchange
28
+ * keypair) and persists it encrypted-at-rest under `storageDir`.
29
+ *
30
+ * Requires a passphrase of at least MIN_PASSPHRASE_LENGTH characters — this
31
+ * check only applies here, not to loadIdentity, so an identity created
32
+ * before this check existed can still be loaded with its original passphrase.
33
+ */
34
+ export declare function createIdentity(options: CreateIdentityOptions): Promise<Identity>;
35
+ export interface LoadIdentityOptions {
36
+ storageDir: string;
37
+ passphrase: string;
38
+ }
39
+ /** Loads a previously-created identity back from disk. */
40
+ export declare function loadIdentity(options: LoadIdentityOptions): Promise<Identity>;
41
+ /** The shareable public half of an identity — hand this to the other party out of band. */
42
+ export declare function exportPublicIdentity(identity: Identity): PublicCard;
@@ -0,0 +1,87 @@
1
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { generateClassicalKeypair } from "./crypto/classical.js";
5
+ import { decryptFromFile, encryptToFile } from "./fileCrypto.js";
6
+ import { generatePqcKemKeypair, generatePqcSignKeypair } from "./crypto/pqc.js";
7
+ function identityFilePath(storageDir) {
8
+ return join(storageDir, "identity.enc.json");
9
+ }
10
+ function serialize(identity) {
11
+ return {
12
+ alias: identity.alias,
13
+ classical: {
14
+ publicKey: bytesToHex(identity.classical.publicKey),
15
+ secretKey: bytesToHex(identity.classical.secretKey),
16
+ },
17
+ pqcSign: {
18
+ publicKey: bytesToHex(identity.pqcSign.publicKey),
19
+ secretKey: bytesToHex(identity.pqcSign.secretKey),
20
+ },
21
+ pqcKem: {
22
+ publicKey: bytesToHex(identity.pqcKem.publicKey),
23
+ secretKey: bytesToHex(identity.pqcKem.secretKey),
24
+ },
25
+ };
26
+ }
27
+ function deserialize(storageDir, data) {
28
+ return {
29
+ alias: data.alias,
30
+ storageDir,
31
+ classical: {
32
+ publicKey: hexToBytes(data.classical.publicKey),
33
+ secretKey: hexToBytes(data.classical.secretKey),
34
+ },
35
+ pqcSign: {
36
+ publicKey: hexToBytes(data.pqcSign.publicKey),
37
+ secretKey: hexToBytes(data.pqcSign.secretKey),
38
+ },
39
+ pqcKem: {
40
+ publicKey: hexToBytes(data.pqcKem.publicKey),
41
+ secretKey: hexToBytes(data.pqcKem.secretKey),
42
+ },
43
+ };
44
+ }
45
+ export const MIN_PASSPHRASE_LENGTH = 12;
46
+ /**
47
+ * Generates a new hybrid classical+post-quantum identity (one ECDSA/ECDH-P384
48
+ * keypair, one ML-DSA-87 signing keypair, one ML-KEM-1024 key-exchange
49
+ * keypair) and persists it encrypted-at-rest under `storageDir`.
50
+ *
51
+ * Requires a passphrase of at least MIN_PASSPHRASE_LENGTH characters — this
52
+ * check only applies here, not to loadIdentity, so an identity created
53
+ * before this check existed can still be loaded with its original passphrase.
54
+ */
55
+ export async function createIdentity(options) {
56
+ if (options.passphrase.length < MIN_PASSPHRASE_LENGTH) {
57
+ throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LENGTH} characters long.`);
58
+ }
59
+ const filePath = identityFilePath(options.storageDir);
60
+ if (!options.overwrite && existsSync(filePath)) {
61
+ throw new Error(`An identity already exists at ${filePath}. Pass { overwrite: true } to replace it.`);
62
+ }
63
+ const identity = {
64
+ alias: options.alias,
65
+ storageDir: options.storageDir,
66
+ classical: generateClassicalKeypair(),
67
+ pqcSign: generatePqcSignKeypair(),
68
+ pqcKem: generatePqcKemKeypair(),
69
+ };
70
+ await encryptToFile(filePath, options.passphrase, serialize(identity));
71
+ return identity;
72
+ }
73
+ /** Loads a previously-created identity back from disk. */
74
+ export async function loadIdentity(options) {
75
+ const data = await decryptFromFile(identityFilePath(options.storageDir), options.passphrase);
76
+ return deserialize(options.storageDir, data);
77
+ }
78
+ /** The shareable public half of an identity — hand this to the other party out of band. */
79
+ export function exportPublicIdentity(identity) {
80
+ return {
81
+ alias: identity.alias,
82
+ classicalPublicKey: bytesToHex(identity.classical.publicKey),
83
+ pqcSignPublicKey: bytesToHex(identity.pqcSign.publicKey),
84
+ pqcKemPublicKey: bytesToHex(identity.pqcKem.publicKey),
85
+ };
86
+ }
87
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAyB,MAAM,uBAAuB,CAAC;AACxF,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,sBAAsB,EAA2C,MAAM,iBAAiB,CAAC;AAyBzH,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,OAAO,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,SAAS,CAAC,QAAkB;IACnC,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,SAAS,EAAE;YACT,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;YACnD,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;SACpD;QACD,OAAO,EAAE;YACP,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;YACjD,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;SAClD;QACD,MAAM,EAAE;YACN,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;YAChD,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;SACjD;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,UAAkB,EAAE,IAAwB;IAC/D,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU;QACV,SAAS,EAAE;YACT,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;YAC/C,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;SAChD;QACD,OAAO,EAAE;YACP,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;YAC7C,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;SAC9C;QACD,MAAM,EAAE;YACN,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAC5C,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;SAC7C;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAUxC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA8B;IACjE,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,+BAA+B,qBAAqB,mBAAmB,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,iCAAiC,QAAQ,2CAA2C,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,QAAQ,GAAa;QACzB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,SAAS,EAAE,wBAAwB,EAAE;QACrC,OAAO,EAAE,sBAAsB,EAAE;QACjC,MAAM,EAAE,qBAAqB,EAAE;KAChC,CAAC;IAEF,MAAM,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvE,OAAO,QAAQ,CAAC;AAClB,CAAC;AAOD,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAA4B;IAC7D,MAAM,IAAI,GAAG,MAAM,eAAe,CAAqB,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IACjH,OAAO,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,oBAAoB,CAAC,QAAkB;IACrD,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,kBAAkB,EAAE,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC;QAC5D,gBAAgB,EAAE,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC;QACxD,eAAe,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { canonicalize, hashDocument } from "./canonicalJson.js";
2
+ export { fingerprint } from "./fingerprint.js";
3
+ export { decryptFrom, encryptFor, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
4
+ export type { Certificate, EncryptedBox, VerifyFailureReason, VerifyResult } from "./crypto/nodeHybrid.js";
5
+ export { createIdentity, exportPublicIdentity, loadIdentity, MIN_PASSPHRASE_LENGTH } from "./identity.js";
6
+ export type { CreateIdentityOptions, Identity, LoadIdentityOptions, PublicCard } from "./identity.js";
7
+ export { readLog, verifyLogIntegrity } from "./store.js";
8
+ export type { LogEntry, LogIntegrityResult } from "./store.js";
9
+ export { getTrustedParty, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, trustParty, untrustParty, } from "./trust.js";
10
+ export type { TrustRecord } from "./trust.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { canonicalize, hashDocument } from "./canonicalJson.js";
2
+ export { fingerprint } from "./fingerprint.js";
3
+ export { decryptFrom, encryptFor, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
4
+ export { createIdentity, exportPublicIdentity, loadIdentity, MIN_PASSPHRASE_LENGTH } from "./identity.js";
5
+ export { readLog, verifyLogIntegrity } from "./store.js";
6
+ export { getTrustedParty, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, trustParty, untrustParty, } from "./trust.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEpG,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE1G,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,YAAY,GACb,MAAM,YAAY,CAAC"}