zeyra 1.0.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/LICENSE +21 -0
- package/README.md +69 -0
- package/package.json +39 -0
- package/src/CipherAgent/class.js +43 -0
- package/src/SigningAgent/class.js +23 -0
- package/src/VerificationAgent/class.js +29 -0
- package/src/generateKeyset/index.js +36 -0
- package/src/index.js +5 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 zeyra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
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
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zeyra",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebCrypto helper for generating JWK keysets plus AES-GCM encryption and ECDSA signing agents.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"webcrypto",
|
|
8
|
+
"aes-gcm",
|
|
9
|
+
"ecdsa",
|
|
10
|
+
"jwk",
|
|
11
|
+
"encryption",
|
|
12
|
+
"signature"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test test.js",
|
|
18
|
+
"bench": "node test.js --bench",
|
|
19
|
+
"prepublishOnly": "npm test"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"bytecodec": "^1.1.0"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./src/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"src",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"sideEffects": false
|
|
39
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class CipherAgent {
|
|
2
|
+
/**
|
|
3
|
+
* @param {JsonWebKey} symmetricJwk // AES-GCM (kty:"oct", alg:"A256GCM")
|
|
4
|
+
*/
|
|
5
|
+
constructor(symmetricJwk) {
|
|
6
|
+
this.keyPromise = crypto.subtle.importKey(
|
|
7
|
+
"jwk",
|
|
8
|
+
symmetricJwk,
|
|
9
|
+
{ name: "AES-GCM" },
|
|
10
|
+
false,
|
|
11
|
+
["encrypt", "decrypt"]
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Uint8Array} plaintext
|
|
17
|
+
* @returns {Promise<{ iv: Uint8Array, ciphertext: ArrayBuffer }>}
|
|
18
|
+
*/
|
|
19
|
+
async encrypt(plaintext) {
|
|
20
|
+
const key = await this.keyPromise;
|
|
21
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
22
|
+
const ciphertext = await crypto.subtle.encrypt(
|
|
23
|
+
{ name: "AES-GCM", iv },
|
|
24
|
+
key,
|
|
25
|
+
plaintext
|
|
26
|
+
);
|
|
27
|
+
return { iv, ciphertext };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ iv: Uint8Array, ciphertext: ArrayBuffer }} payload
|
|
32
|
+
* @returns {Promise<Uint8Array>}
|
|
33
|
+
*/
|
|
34
|
+
async decrypt({ iv, ciphertext }) {
|
|
35
|
+
const key = await this.keyPromise;
|
|
36
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
37
|
+
{ name: "AES-GCM", iv },
|
|
38
|
+
key,
|
|
39
|
+
ciphertext
|
|
40
|
+
);
|
|
41
|
+
return new Uint8Array(plaintext);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class SigningAgent {
|
|
2
|
+
/**
|
|
3
|
+
* @param {JsonWebKey} privateJwk // ECDSA P-256 private key
|
|
4
|
+
*/
|
|
5
|
+
constructor(privateJwk) {
|
|
6
|
+
this.keyPromise = crypto.subtle.importKey(
|
|
7
|
+
"jwk",
|
|
8
|
+
privateJwk,
|
|
9
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
10
|
+
false,
|
|
11
|
+
["sign"]
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Uint8Array} bytes
|
|
17
|
+
* @returns {Promise<ArrayBuffer>}
|
|
18
|
+
*/
|
|
19
|
+
async sign(bytes) {
|
|
20
|
+
const key = await this.keyPromise;
|
|
21
|
+
return crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, key, bytes);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export class VerificationAgent {
|
|
2
|
+
/**
|
|
3
|
+
* @param {JsonWebKey} publicJwk // ECDSA P-256 public key
|
|
4
|
+
*/
|
|
5
|
+
constructor(publicJwk) {
|
|
6
|
+
this.keyPromise = crypto.subtle.importKey(
|
|
7
|
+
"jwk",
|
|
8
|
+
publicJwk,
|
|
9
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
10
|
+
false,
|
|
11
|
+
["verify"]
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Uint8Array} bytes
|
|
17
|
+
* @param {ArrayBuffer} signature
|
|
18
|
+
* @returns {Promise<boolean>}
|
|
19
|
+
*/
|
|
20
|
+
async verify(bytes, signature) {
|
|
21
|
+
const key = await this.keyPromise;
|
|
22
|
+
return crypto.subtle.verify(
|
|
23
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
24
|
+
key,
|
|
25
|
+
signature,
|
|
26
|
+
bytes
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a cryptographic keyset for a single resource.
|
|
3
|
+
*
|
|
4
|
+
* Returned keys:
|
|
5
|
+
* - symmetricJwk: AES-GCM 256-bit key (JWK, kty:"oct") for encrypt/decrypt
|
|
6
|
+
* - publicJwk: ECDSA P-256 public key (JWK) for verify
|
|
7
|
+
* - privateJwk: ECDSA P-256 private key (JWK) for sign
|
|
8
|
+
*
|
|
9
|
+
* All keys are extractable and intended to be stored encrypted
|
|
10
|
+
* or transported as data. Type definitions are expected to live
|
|
11
|
+
* in a separate TypeScript types file.
|
|
12
|
+
*
|
|
13
|
+
* @returns {Promise<{
|
|
14
|
+
* symmetricJwk: JsonWebKey,
|
|
15
|
+
* publicJwk: JsonWebKey,
|
|
16
|
+
* privateJwk: JsonWebKey
|
|
17
|
+
* }>}
|
|
18
|
+
*/
|
|
19
|
+
export async function generateKeyset() {
|
|
20
|
+
const aesKey = await crypto.subtle.generateKey(
|
|
21
|
+
{ name: "AES-GCM", length: 256 },
|
|
22
|
+
true,
|
|
23
|
+
["encrypt", "decrypt"]
|
|
24
|
+
);
|
|
25
|
+
const symmetricJwk = await crypto.subtle.exportKey("jwk", aesKey);
|
|
26
|
+
|
|
27
|
+
const keyPair = await crypto.subtle.generateKey(
|
|
28
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
29
|
+
true,
|
|
30
|
+
["sign", "verify"]
|
|
31
|
+
);
|
|
32
|
+
const publicJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey);
|
|
33
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
|
|
34
|
+
|
|
35
|
+
return { symmetricJwk, publicJwk, privateJwk };
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { generateKeyset } from "./generateKeyset/index.js";
|
|
2
|
+
import { CipherAgent } from "./CipherAgent/class.js";
|
|
3
|
+
import { SigningAgent } from "./SigningAgent/class.js";
|
|
4
|
+
import { VerificationAgent } from "./VerificationAgent/class.js";
|
|
5
|
+
export { generateKeyset, CipherAgent, SigningAgent, VerificationAgent };
|