zeyra 1.0.1 → 1.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.
package/README.md CHANGED
@@ -1,69 +1,113 @@
1
- # Zeyra
2
-
3
- WebCrypto helpers for zero-knowledge–friendly flows: generate AES-GCM + ECDSA keysets as JWKs and wrap them with tiny agent classes for encrypt, decrypt, sign, and verify.
4
-
5
- ## Features
6
- - AES-GCM 256 encryption/decryption via `CipherAgent`
7
- - ECDSA P-256 signing/verification via `SigningAgent` and `VerificationAgent`
8
- - `generateKeyset()` produces an exportable JWK bundle you can store or transport
9
- - Pure WebCrypto, no native add-ons; ships as ESM
10
- - Plays nicely with `bytecodec` for UTF-8 and base64url conversions
11
-
12
- ## Requirements
13
- - Node.js 18+ (global `crypto.subtle`)
14
- - ESM environment (`"type": "module"` in `package.json`)
15
-
16
- ## Installation
17
- ```bash
18
- npm install zeyra
19
- ```
20
-
21
- ## Quickstart
22
- ```js
23
- import { Bytes } from "bytecodec";
24
- import {
25
- generateKeyset,
26
- CipherAgent,
27
- SigningAgent,
28
- VerificationAgent,
29
- } from "zeyra";
30
-
31
- // One-time key material for a resource
32
- const { symmetricJwk, privateJwk, publicJwk } = await generateKeyset();
33
-
34
- // Writers: encrypt + sign
35
- const cipher = new CipherAgent(symmetricJwk);
36
- const signer = new SigningAgent(privateJwk);
37
- const payload = await cipher.encrypt(Bytes.fromString("hello world"));
38
- const signature = await signer.sign(payload.ciphertext);
39
-
40
- // Readers / servers: verify ownership + decrypt
41
- const verifier = new VerificationAgent(publicJwk);
42
- const authorized = await verifier.verify(payload.ciphertext, signature);
43
- const plaintext = Bytes.toString(await cipher.decrypt(payload));
44
- ```
45
-
46
- ## API
47
- - `generateKeyset()` -> `{ symmetricJwk, publicJwk, privateJwk }` (all exportable JWKs)
48
- - `new CipherAgent(symmetricJwk)`
49
- - `.encrypt(Uint8Array)` -> `{ iv: Uint8Array, ciphertext: ArrayBuffer }`
50
- - `.decrypt({ iv, ciphertext })` -> `Uint8Array`
51
- - `new SigningAgent(privateJwk)`
52
- - `.sign(Uint8Array | ArrayBuffer)` -> `ArrayBuffer` (ECDSA P-256 / SHA-256)
53
- - `new VerificationAgent(publicJwk)`
54
- - `.verify(Uint8Array | ArrayBuffer, ArrayBuffer)` -> `boolean`
55
-
56
- See the implementations in `src/index.js` and friends for details.
57
-
58
- ## Testing and benchmarks
59
- - Run tests: `npm test` (uses Node’s built-in `node:test` runner against `test.js`)
60
- - Run microbenchmarks (skipped by default): `npm run bench`
61
- - Pass iterations: `npm run bench -- --iterations=500`
62
- - Reports ops/sec for encryption and the full encrypt/sign/verify/decrypt pipeline.
63
-
64
- ## Notes
65
- - Keys are intentionally exportable to move them between client/storage; encrypt them at rest according to your threat model.
66
- - AES-GCM already authenticates ciphertext/IV; ECDSA signatures add an explicit ownership check for multi-party flows.
67
-
68
- ## License
69
- MIT
1
+ # Zeyra
2
+
3
+ Managed WebCrypto helpers for storage-ready AES-GCM + ECDSA keysets, with lightweight agents and weakly cached clusters.
4
+
5
+ ## Features
6
+
7
+ - AES-GCM 256 encryption/decryption via `CipherAgent`
8
+ - ECDSA P-256 signing/verification via `SigningAgent` and `VerificationAgent`
9
+ - Managed clusters (`CipherCluster`, `SigningCluster`, `VerificationCluster`) cache agents with WeakRef for large keysets
10
+ - `generateKeyset()` produces an exportable JWK bundle you can store or transport
11
+ - Storage/transport-ready artifacts with base64url payloads and SHA-256 digests
12
+ - Pure WebCrypto, no native add-ons; ships as ESM
13
+ - Works with `bytecodec` for UTF-8, compression, and base64url conversions
14
+
15
+ ## Requirements
16
+
17
+ - Node.js 18+ (global `crypto.subtle`)
18
+ - ESM environment (`"type": "module"` in `package.json`)
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install zeyra
24
+ ```
25
+
26
+ ## Quickstart (agents)
27
+
28
+ ```js
29
+ import { Bytes } from "bytecodec";
30
+ import {
31
+ generateKeyset,
32
+ CipherAgent,
33
+ SigningAgent,
34
+ VerificationAgent,
35
+ } from "zeyra";
36
+
37
+ // One-time key material for a resource
38
+ const { symmetricJwk, privateJwk, publicJwk } = await generateKeyset();
39
+
40
+ // Writers: encrypt + sign
41
+ const cipher = new CipherAgent(symmetricJwk);
42
+ const signer = new SigningAgent(privateJwk);
43
+ const payload = await cipher.encrypt(Bytes.fromString("hello world"));
44
+ const signature = await signer.sign(payload.ciphertext);
45
+
46
+ // Readers / servers: verify ownership + decrypt
47
+ const verifier = new VerificationAgent(publicJwk);
48
+ const authorized = await verifier.verify(payload.ciphertext, signature);
49
+ const plaintext = Bytes.toString(await cipher.decrypt(payload));
50
+ ```
51
+
52
+ ## Managed cluster flow
53
+
54
+ ```js
55
+ import {
56
+ generateKeyset,
57
+ CipherCluster,
58
+ SigningCluster,
59
+ VerificationCluster,
60
+ } from "zeyra";
61
+
62
+ const { symmetricJwk, privateJwk, publicJwk } = await generateKeyset();
63
+
64
+ const resource = { id: "file-123", body: "hello world" };
65
+ const artifact = await CipherCluster.encrypt(symmetricJwk, resource);
66
+ const signature = await SigningCluster.sign(privateJwk, resource.id);
67
+
68
+ // VerificationCluster is designed to run on a per-resource server node.
69
+ const authorized = await VerificationCluster.verify(
70
+ publicJwk,
71
+ resource.id,
72
+ signature
73
+ );
74
+
75
+ const decrypted = await CipherCluster.decrypt(symmetricJwk, artifact);
76
+ ```
77
+
78
+ ## API
79
+
80
+ - `generateKeyset()` -> `{ symmetricJwk, publicJwk, privateJwk }` (all exportable JWKs)
81
+ - `new CipherAgent(symmetricJwk)`
82
+ - `.encrypt(Uint8Array)` -> `{ iv: Uint8Array, ciphertext: ArrayBuffer }`
83
+ - `.decrypt({ iv, ciphertext })` -> `Uint8Array`
84
+ - `new SigningAgent(privateJwk)`
85
+ - `.sign(Uint8Array | ArrayBuffer)` -> `ArrayBuffer` (ECDSA P-256 / SHA-256)
86
+ - `new VerificationAgent(publicJwk)`
87
+ - `.verify(Uint8Array | ArrayBuffer, ArrayBuffer)` -> `boolean`
88
+ - `CipherCluster.encrypt(symmetricJwk, resource)`
89
+ - -> `{ digest, ciphertext, iv }` (all base64url strings; digest is SHA-256 of JSON bytes, pre-encryption, useful for version checks)
90
+ - `CipherCluster.decrypt(symmetricJwk, artifact)`
91
+ - -> `{ digest, ...resource }` (resource object restored from compressed JSON)
92
+ - `SigningCluster.sign(privateJwk, value)` -> `Base64URLString`
93
+ - `VerificationCluster.verify(publicJwk, value, signature)` -> `boolean`
94
+
95
+ See the implementations in `src/index.js` and friends for details.
96
+
97
+ ## Testing and benchmarks
98
+
99
+ - Run tests: `npm test` (uses Node's built-in `node:test` runner against `test.js`)
100
+ - Run microbenchmarks (skipped by default): `npm run bench`
101
+ - Pass iterations: `npm run bench -- --iterations=500`
102
+ - Reports ops/sec for encryption and the full encrypt/sign/verify/decrypt pipeline.
103
+
104
+ ## Notes
105
+
106
+ - CipherCluster assumes one unique random key per resource (no derivations or shared usage).
107
+ - Cluster classes are intended for client-side usage; `VerificationCluster`/`VerificationAgent` can be hosted per-resource to pre-verify access before downstream identity or ACL checks.
108
+ - Cluster serialization uses JSON and adds a `digest` field; avoid using `digest` in resource objects.
109
+ - WeakRef caching keeps memory usage loose and GC-friendly by design.
110
+
111
+ ## License
112
+
113
+ MIT
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "zeyra",
3
- "version": "1.0.1",
4
- "description": "WebCrypto helper for generating JWK keysets plus AES-GCM encryption and ECDSA signing agents.",
3
+ "version": "1.1.0",
4
+ "description": "Managed WebCrypto helpers for AES-GCM + ECDSA JWK keysets with storage-ready cluster APIs.",
5
5
  "keywords": [
6
6
  "webcrypto",
7
7
  "aes-gcm",
8
8
  "ecdsa",
9
9
  "jwk",
10
10
  "encryption",
11
- "signature"
11
+ "signature",
12
+ "cluster",
13
+ "keyset",
14
+ "base64url",
15
+ "compression",
16
+ "storage",
17
+ "transport"
12
18
  ],
13
19
  "license": "MIT",
14
20
  "type": "module",
@@ -27,7 +33,7 @@
27
33
  },
28
34
  "homepage": "https://github.com/jortsupetterson/zeyra#readme",
29
35
  "dependencies": {
30
- "bytecodec": "^1.2.1"
36
+ "bytecodec": "^1.3.0"
31
37
  },
32
38
  "engines": {
33
39
  "node": ">=18"
@@ -0,0 +1,57 @@
1
+ import { Bytes } from "bytecodec";
2
+ import { CipherAgent } from "../CipherAgent/class.js";
3
+
4
+ export class CipherCluster {
5
+ /** @type {WeakMap<JsonWebKey, WeakRef<CipherAgent>>} */
6
+ static #agents = new WeakMap();
7
+
8
+ /**
9
+ * @param {JsonWebKey} symmetricJwk
10
+ * @returns {CipherAgent}
11
+ */
12
+ static #loadAgent(symmetricJwk) {
13
+ const weakRef = CipherCluster.#agents.get(symmetricJwk);
14
+ /** @type {CipherAgent | undefined} */
15
+ let agent = weakRef?.deref();
16
+ if (!agent) {
17
+ agent = new CipherAgent(symmetricJwk);
18
+ CipherCluster.#agents.set(symmetricJwk, new WeakRef(agent));
19
+ }
20
+ return agent;
21
+ }
22
+
23
+ /**
24
+ * @param {JsonWebKey} symmetricJwk
25
+ * @param {any} resource
26
+ * @returns {Promise<{ digest: Base64URLString, ciphertext: Base64URLString, iv: Base64URLString }>}
27
+ */
28
+ static async encrypt(symmetricJwk, resource) {
29
+ const agent = CipherCluster.#loadAgent(symmetricJwk);
30
+ const bytes = Bytes.fromJSON(resource);
31
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
32
+ const compressed = await Bytes.toCompressed(bytes);
33
+ const envelope = await agent.encrypt(compressed);
34
+ return {
35
+ digest: Bytes.toBase64UrlString(digest),
36
+ ciphertext: Bytes.toBase64UrlString(envelope.ciphertext),
37
+ iv: Bytes.toBase64UrlString(envelope.iv),
38
+ };
39
+ }
40
+
41
+ /**
42
+ * @param {JsonWebKey} symmetricJwk
43
+ * @param {{ digest: Base64URLString, ciphertext: Base64URLString, iv: Base64URLString }} artifact
44
+ * @returns {Promise<any>}
45
+ */
46
+ static async decrypt(symmetricJwk, artifact) {
47
+ const envelope = {
48
+ ciphertext: Bytes.fromBase64UrlString(artifact.ciphertext),
49
+ iv: Bytes.fromBase64UrlString(artifact.iv),
50
+ };
51
+ const agent = CipherCluster.#loadAgent(symmetricJwk);
52
+ const bytes = await agent.decrypt(envelope);
53
+ const decompressed = await Bytes.fromCompressed(bytes);
54
+ const resource = Bytes.toJSON(decompressed);
55
+ return { digest: artifact.digest, ...resource };
56
+ }
57
+ }
@@ -0,0 +1,34 @@
1
+ import { Bytes } from "bytecodec";
2
+ import { SigningAgent } from "../SigningAgent/class.js";
3
+
4
+ export class SigningCluster {
5
+ /** @type {WeakMap<JsonWebKey, WeakRef<SigningAgent>>} */
6
+ static #agents = new WeakMap();
7
+
8
+ /**
9
+ * @param {JsonWebKey} privateJwk
10
+ * @returns {SigningAgent}
11
+ */
12
+ static #loadAgent(privateJwk) {
13
+ const weakRef = SigningCluster.#agents.get(privateJwk);
14
+ /** @type {SigningAgent | undefined} */
15
+ let agent = weakRef?.deref();
16
+ if (!agent) {
17
+ agent = new SigningAgent(privateJwk);
18
+ SigningCluster.#agents.set(privateJwk, new WeakRef(agent));
19
+ }
20
+ return agent;
21
+ }
22
+
23
+ /**
24
+ * @param {JsonWebKey} privateJwk
25
+ * @param {any} value
26
+ * @returns {Promise<Base64URLString>}
27
+ */
28
+ static async sign(privateJwk, value) {
29
+ const agent = SigningCluster.#loadAgent(privateJwk);
30
+ const bytes = Bytes.fromJSON(value);
31
+ const signature = await agent.sign(bytes);
32
+ return Bytes.toBase64UrlString(signature);
33
+ }
34
+ }
@@ -0,0 +1,35 @@
1
+ import { Bytes } from "bytecodec";
2
+ import { VerificationAgent } from "../VerificationAgent/class.js";
3
+
4
+ export class VerificationCluster {
5
+ /** @type {WeakMap<JsonWebKey, WeakRef<VerificationAgent>>} */
6
+ static #agents = new WeakMap();
7
+
8
+ /**
9
+ * @param {JsonWebKey} publicJwk
10
+ * @returns {VerificationAgent}
11
+ */
12
+ static #loadAgent(publicJwk) {
13
+ const weakRef = VerificationCluster.#agents.get(publicJwk);
14
+ /** @type {VerificationAgent | undefined} */
15
+ let agent = weakRef?.deref();
16
+ if (!agent) {
17
+ agent = new VerificationAgent(publicJwk);
18
+ VerificationCluster.#agents.set(publicJwk, new WeakRef(agent));
19
+ }
20
+ return agent;
21
+ }
22
+
23
+ /**
24
+ * @param {JsonWebKey} publicJwk
25
+ * @param {any} value
26
+ * @param {Base64URLString} signature
27
+ * @returns {Promise<boolean>}
28
+ */
29
+ static async verify(publicJwk, value, signature) {
30
+ const agent = VerificationCluster.#loadAgent(publicJwk);
31
+ const valueBytes = Bytes.fromJSON(value);
32
+ const signatureBytes = Bytes.fromBase64UrlString(signature);
33
+ return await agent.verify(valueBytes, signatureBytes);
34
+ }
35
+ }
package/src/index.js CHANGED
@@ -2,4 +2,15 @@ import { generateKeyset } from "./generateKeyset/index.js";
2
2
  import { CipherAgent } from "./CipherAgent/class.js";
3
3
  import { SigningAgent } from "./SigningAgent/class.js";
4
4
  import { VerificationAgent } from "./VerificationAgent/class.js";
5
- export { generateKeyset, CipherAgent, SigningAgent, VerificationAgent };
5
+ import { CipherCluster } from "./CipherCluster/class.js";
6
+ import { SigningCluster } from "./SigningCluster/class.js";
7
+ import { VerificationCluster } from "./VerificationCluster/class.js";
8
+ export {
9
+ generateKeyset,
10
+ CipherAgent,
11
+ SigningAgent,
12
+ VerificationAgent,
13
+ CipherCluster,
14
+ SigningCluster,
15
+ VerificationCluster,
16
+ };