sietch 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.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/acl.d.ts +134 -0
- package/dist/acl.js +258 -0
- package/dist/acl.js.map +1 -0
- package/dist/crypto.d.ts +184 -0
- package/dist/crypto.js +459 -0
- package/dist/crypto.js.map +1 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.js +68 -0
- package/dist/errors.js.map +1 -0
- package/dist/eviction.d.ts +75 -0
- package/dist/eviction.js +216 -0
- package/dist/eviction.js.map +1 -0
- package/dist/identity.d.ts +69 -0
- package/dist/identity.js +127 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/observability.d.ts +103 -0
- package/dist/observability.js +91 -0
- package/dist/observability.js.map +1 -0
- package/dist/query-index.d.ts +97 -0
- package/dist/query-index.js +246 -0
- package/dist/query-index.js.map +1 -0
- package/dist/server/acl-authority.d.ts +68 -0
- package/dist/server/acl-authority.js +233 -0
- package/dist/server/acl-authority.js.map +1 -0
- package/dist/server/auth.d.ts +88 -0
- package/dist/server/auth.js +173 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.js +15 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/pg-adapter.d.ts +57 -0
- package/dist/server/pg-adapter.js +241 -0
- package/dist/server/pg-adapter.js.map +1 -0
- package/dist/server/query-bridge.d.ts +166 -0
- package/dist/server/query-bridge.js +258 -0
- package/dist/server/query-bridge.js.map +1 -0
- package/dist/server/sync-service.d.ts +98 -0
- package/dist/server/sync-service.js +209 -0
- package/dist/server/sync-service.js.map +1 -0
- package/dist/storage.d.ts +53 -0
- package/dist/storage.js +340 -0
- package/dist/storage.js.map +1 -0
- package/dist/store.d.ts +195 -0
- package/dist/store.js +479 -0
- package/dist/store.js.map +1 -0
- package/dist/sync-engine.d.ts +187 -0
- package/dist/sync-engine.js +824 -0
- package/dist/sync-engine.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/yjs-engine.d.ts +107 -0
- package/dist/yjs-engine.js +422 -0
- package/dist/yjs-engine.js.map +1 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hans Rakotomanga
|
|
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
|
+
# sietch
|
|
2
|
+
|
|
3
|
+
An offline-first database library with CRDT-based sync, post-quantum encryption,
|
|
4
|
+
and a PostgreSQL/AGE/pgvector server backend. Inspired by GunJS and TopGun.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Offline-first** — full CRUD without network, automatic sync when online
|
|
9
|
+
- **CRDT sync** — Yjs-powered conflict-free replication across peers and servers
|
|
10
|
+
- **P2P networking** — libp2p transport with NAT traversal (Circuit Relay v2)
|
|
11
|
+
- **Post-quantum encryption** — hybrid Ed25519+ML-DSA-44 signing, X25519+ML-KEM-768 key exchange
|
|
12
|
+
- **Per-subtree ACL** — server-signed reference nodes with three tiers (open / policy / crypto)
|
|
13
|
+
- **Graph + vector** — local Orama/graphology indexing, server-side AGE graph + pgvector search
|
|
14
|
+
- **Transparent eviction** — two-tier (memory → disk → network), with pinning support
|
|
15
|
+
- **Zero-config** — sensible defaults, `createStore()` works out of the box
|
|
16
|
+
|
|
17
|
+
## Getting Started
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Install dependencies
|
|
21
|
+
pnpm install
|
|
22
|
+
|
|
23
|
+
# Run tests
|
|
24
|
+
pnpm test
|
|
25
|
+
|
|
26
|
+
# Type check
|
|
27
|
+
pnpm tsc --noEmit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Minimal Example
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createStore } from 'sietch';
|
|
34
|
+
|
|
35
|
+
const db = await createStore();
|
|
36
|
+
await db.put('greeting.msg', 'hello world');
|
|
37
|
+
const result = await db.get('greeting.msg');
|
|
38
|
+
console.log(result.value); // 'hello world'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
| Client Module | Purpose |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Store | Public API facade |
|
|
46
|
+
| Yjs Engine | CRDT engine (subdocument per subtree) |
|
|
47
|
+
| ACL Module | Per-subtree access control |
|
|
48
|
+
| Crypto Module | PQ hybrid signing, KEX, symmetric encryption |
|
|
49
|
+
| Identity Module | Keypair lifecycle, device linking |
|
|
50
|
+
| Sync Engine | libp2p transport, protocol handlers |
|
|
51
|
+
| Storage Backend | OPFS/IndexedDB/memory abstraction |
|
|
52
|
+
| Eviction Manager | Two-tier eviction with LRU |
|
|
53
|
+
| Query/Index Engine | Orama full-text + graphology graph |
|
|
54
|
+
| Observability | Structured logging (pino), event channels |
|
|
55
|
+
|
|
56
|
+
| Server Module | Purpose |
|
|
57
|
+
|---|---|
|
|
58
|
+
| Sync Service | Yjs relay with selective replication |
|
|
59
|
+
| ACL Authority | Sign/validate ACL reference nodes |
|
|
60
|
+
| Auth Service | Email-based device linking |
|
|
61
|
+
| Query Bridge | Yjs → PostgreSQL/AGE/pgvector pipeline |
|
|
62
|
+
|
|
63
|
+
## Project Status
|
|
64
|
+
|
|
65
|
+
Phase 5 (Implementation) complete. 14 source modules, 276 tests (272 passing + 4 browser-only skipped), 83/83 requirements covered.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
[Your license here]
|
package/dist/acl.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACL Module — per-subtree access control via server-signed reference nodes.
|
|
3
|
+
*
|
|
4
|
+
* Architecture: ADR-007 (Per-subtree ACL via reference nodes)
|
|
5
|
+
*
|
|
6
|
+
* Three tiers:
|
|
7
|
+
* 1. No ACL (open): no reference node → anyone can read/write
|
|
8
|
+
* 2. ACL unencrypted: reference node with encrypted:false → policy enforcement
|
|
9
|
+
* 3. ACL encrypted: reference node with encrypted:true → cryptographic enforcement (CEK)
|
|
10
|
+
*/
|
|
11
|
+
import type { AclMap, AclPermissions, CrdtValue, Result } from './types.js';
|
|
12
|
+
import type { ObservabilityContext } from './observability.js';
|
|
13
|
+
import { type HybridPublicKey, type HybridSignature, type HybridKemKeypair } from './crypto.js';
|
|
14
|
+
import { InvalidAclSignatureError, AclError } from './errors.js';
|
|
15
|
+
/** Reference node structure for a subtree ACL. */
|
|
16
|
+
export interface AclReferenceNode {
|
|
17
|
+
subtreeId: string;
|
|
18
|
+
owner: string;
|
|
19
|
+
encrypted: boolean;
|
|
20
|
+
acl: AclMap;
|
|
21
|
+
/** Per-user wrapped CEKs (only for encrypted subtrees). */
|
|
22
|
+
ceks?: Record<string, Uint8Array>;
|
|
23
|
+
/** Server signature over the ACL data. */
|
|
24
|
+
signature: HybridSignature;
|
|
25
|
+
}
|
|
26
|
+
/** Serialized form of ACL reference node (for hashing/signing). */
|
|
27
|
+
export interface AclPayload {
|
|
28
|
+
subtreeId: string;
|
|
29
|
+
owner: string;
|
|
30
|
+
encrypted: boolean;
|
|
31
|
+
acl: AclMap;
|
|
32
|
+
}
|
|
33
|
+
/** Result of an ACL check. */
|
|
34
|
+
export interface AclCheckResult {
|
|
35
|
+
allowed: boolean;
|
|
36
|
+
tier: 'open' | 'policy' | 'crypto';
|
|
37
|
+
/** Decrypted CEK (only for encrypted subtrees, only if user has access). */
|
|
38
|
+
cek?: Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
export declare class AclManager {
|
|
41
|
+
private readonly obs;
|
|
42
|
+
/** In-memory cache of ACL reference nodes. */
|
|
43
|
+
private readonly refNodes;
|
|
44
|
+
private serverPublicKey;
|
|
45
|
+
constructor(obs: ObservabilityContext);
|
|
46
|
+
/**
|
|
47
|
+
* Set the trusted server's public key for signature verification.
|
|
48
|
+
*/
|
|
49
|
+
setServerPublicKey(pk: HybridPublicKey): void;
|
|
50
|
+
/**
|
|
51
|
+
* Apply an ACL reference node update (from server or sync).
|
|
52
|
+
* Verifies server signature before accepting.
|
|
53
|
+
*/
|
|
54
|
+
applyReferenceNode(node: AclReferenceNode): Result<void, InvalidAclSignatureError>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if a user has read permission on a subtree.
|
|
57
|
+
*/
|
|
58
|
+
checkRead(subtreeId: string, userId: string, ownKem?: HybridKemKeypair): AclCheckResult;
|
|
59
|
+
/**
|
|
60
|
+
* Check if a user has write permission on a subtree.
|
|
61
|
+
*/
|
|
62
|
+
checkWrite(subtreeId: string, userId: string): AclCheckResult;
|
|
63
|
+
/**
|
|
64
|
+
* Check if a user has admin permission on a subtree.
|
|
65
|
+
*/
|
|
66
|
+
checkAdmin(subtreeId: string, userId: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Validate a permission grant request.
|
|
69
|
+
* Only users with a:true can grant permissions.
|
|
70
|
+
*/
|
|
71
|
+
validateGrant(subtreeId: string, granterId: string, targetPerms: AclPermissions): Result<void, AclError>;
|
|
72
|
+
/**
|
|
73
|
+
* Validate a permission revoke request.
|
|
74
|
+
* Cannot revoke the owner.
|
|
75
|
+
*/
|
|
76
|
+
validateRevoke(subtreeId: string, revokerId: string, targetId: string): Result<void, AclError>;
|
|
77
|
+
/**
|
|
78
|
+
* Create a new ACL reference node for a subtree.
|
|
79
|
+
* The node must be signed by the server before distribution.
|
|
80
|
+
*/
|
|
81
|
+
createAclPayload(subtreeId: string, ownerId: string, acl: AclMap, encrypted: boolean): AclPayload;
|
|
82
|
+
/**
|
|
83
|
+
* Generate wrapped CEKs for all users in an encrypted subtree.
|
|
84
|
+
*/
|
|
85
|
+
generateCeks(acl: AclMap, userPublicKeys: Record<string, HybridPublicKey>): {
|
|
86
|
+
cek: Uint8Array;
|
|
87
|
+
wrappedCeks: Record<string, Uint8Array>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Rotate CEK after user revocation.
|
|
91
|
+
* Generates new CEK and re-wraps for remaining users.
|
|
92
|
+
*/
|
|
93
|
+
rotateCek(acl: AclMap, userPublicKeys: Record<string, HybridPublicKey>): {
|
|
94
|
+
cek: Uint8Array;
|
|
95
|
+
wrappedCeks: Record<string, Uint8Array>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Get the ACL for a subtree.
|
|
99
|
+
*/
|
|
100
|
+
getAcl(subtreeId: string): AclMap | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Get reference node for a subtree.
|
|
103
|
+
*/
|
|
104
|
+
getReferenceNode(subtreeId: string): AclReferenceNode | undefined;
|
|
105
|
+
/**
|
|
106
|
+
* Check if a subtree is encrypted.
|
|
107
|
+
*/
|
|
108
|
+
isEncrypted(subtreeId: string): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Check if a subtree has an ACL.
|
|
111
|
+
*/
|
|
112
|
+
hasAcl(subtreeId: string): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Get the full reference node for a subtree.
|
|
115
|
+
*/
|
|
116
|
+
getRefNode(subtreeId: string): AclReferenceNode | undefined;
|
|
117
|
+
}
|
|
118
|
+
/** Serialize ACL payload to bytes for signing/verification. */
|
|
119
|
+
declare function serializeAclPayload(payload: AclPayload): Uint8Array;
|
|
120
|
+
/** Exported for server-side signing. */
|
|
121
|
+
export { serializeAclPayload };
|
|
122
|
+
/**
|
|
123
|
+
* Serialize an AclReferenceNode to a Yjs-storable flat object.
|
|
124
|
+
*
|
|
125
|
+
* Flattens the HybridSignature to top-level `sig_ed25519` / `sig_mlDsa44`
|
|
126
|
+
* keys so that Yjs Y.Map roundtrip preserves the Uint8Array values.
|
|
127
|
+
*/
|
|
128
|
+
export declare function serializeRefNode(node: AclReferenceNode): Record<string, CrdtValue>;
|
|
129
|
+
/**
|
|
130
|
+
* Deserialize a Yjs-stored object back into an AclReferenceNode.
|
|
131
|
+
*
|
|
132
|
+
* Throws if the data doesn't contain the required fields.
|
|
133
|
+
*/
|
|
134
|
+
export declare function deserializeRefNode(data: Record<string, CrdtValue>): AclReferenceNode;
|
package/dist/acl.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACL Module — per-subtree access control via server-signed reference nodes.
|
|
3
|
+
*
|
|
4
|
+
* Architecture: ADR-007 (Per-subtree ACL via reference nodes)
|
|
5
|
+
*
|
|
6
|
+
* Three tiers:
|
|
7
|
+
* 1. No ACL (open): no reference node → anyone can read/write
|
|
8
|
+
* 2. ACL unencrypted: reference node with encrypted:false → policy enforcement
|
|
9
|
+
* 3. ACL encrypted: reference node with encrypted:true → cryptographic enforcement (CEK)
|
|
10
|
+
*/
|
|
11
|
+
import { verify, wrapCek, unwrapCek, generateCek, } from './crypto.js';
|
|
12
|
+
import { PermissionDeniedError, InvalidAclSignatureError, AclError, } from './errors.js';
|
|
13
|
+
// ── ACL Manager ────────────────────────────────────────────
|
|
14
|
+
// @req FR-28
|
|
15
|
+
export class AclManager {
|
|
16
|
+
obs;
|
|
17
|
+
/** In-memory cache of ACL reference nodes. */
|
|
18
|
+
refNodes = new Map();
|
|
19
|
+
serverPublicKey = null;
|
|
20
|
+
constructor(obs) {
|
|
21
|
+
this.obs = obs;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set the trusted server's public key for signature verification.
|
|
25
|
+
*/
|
|
26
|
+
setServerPublicKey(pk) {
|
|
27
|
+
this.serverPublicKey = pk;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Apply an ACL reference node update (from server or sync).
|
|
31
|
+
* Verifies server signature before accepting.
|
|
32
|
+
*/
|
|
33
|
+
// @req FR-28
|
|
34
|
+
applyReferenceNode(node) {
|
|
35
|
+
if (!this.serverPublicKey) {
|
|
36
|
+
// No server key configured — accept in open mode
|
|
37
|
+
this.refNodes.set(node.subtreeId, node);
|
|
38
|
+
return { ok: true, value: undefined };
|
|
39
|
+
}
|
|
40
|
+
// Verify server signature
|
|
41
|
+
const payload = serializeAclPayload({
|
|
42
|
+
subtreeId: node.subtreeId,
|
|
43
|
+
owner: node.owner,
|
|
44
|
+
encrypted: node.encrypted,
|
|
45
|
+
acl: node.acl,
|
|
46
|
+
});
|
|
47
|
+
if (!verify(payload, node.signature, this.serverPublicKey)) {
|
|
48
|
+
this.obs.logger.warn({ subtreeId: node.subtreeId }, 'invalid ACL signature rejected');
|
|
49
|
+
return { ok: false, error: new InvalidAclSignatureError() };
|
|
50
|
+
}
|
|
51
|
+
this.refNodes.set(node.subtreeId, node);
|
|
52
|
+
this.obs.logger.debug({ subtreeId: node.subtreeId }, 'ACL reference node applied');
|
|
53
|
+
return { ok: true, value: undefined };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if a user has read permission on a subtree.
|
|
57
|
+
*/
|
|
58
|
+
checkRead(subtreeId, userId, ownKem) {
|
|
59
|
+
const node = this.refNodes.get(subtreeId);
|
|
60
|
+
// No ACL → open access
|
|
61
|
+
if (!node) {
|
|
62
|
+
return { allowed: true, tier: 'open' };
|
|
63
|
+
}
|
|
64
|
+
const perms = node.acl[userId];
|
|
65
|
+
if (!perms?.r) {
|
|
66
|
+
return { allowed: false, tier: node.encrypted ? 'crypto' : 'policy' };
|
|
67
|
+
}
|
|
68
|
+
// Encrypted subtree → need to decrypt CEK
|
|
69
|
+
if (node.encrypted && ownKem) {
|
|
70
|
+
const wrappedCek = node.ceks?.[userId];
|
|
71
|
+
if (!wrappedCek) {
|
|
72
|
+
return { allowed: false, tier: 'crypto' };
|
|
73
|
+
}
|
|
74
|
+
const result = unwrapCek(wrappedCek, ownKem);
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
return { allowed: false, tier: 'crypto' };
|
|
77
|
+
}
|
|
78
|
+
return { allowed: true, tier: 'crypto', cek: result.value };
|
|
79
|
+
}
|
|
80
|
+
return { allowed: true, tier: node.encrypted ? 'crypto' : 'policy' };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a user has write permission on a subtree.
|
|
84
|
+
*/
|
|
85
|
+
checkWrite(subtreeId, userId) {
|
|
86
|
+
const node = this.refNodes.get(subtreeId);
|
|
87
|
+
if (!node) {
|
|
88
|
+
return { allowed: true, tier: 'open' };
|
|
89
|
+
}
|
|
90
|
+
const perms = node.acl[userId];
|
|
91
|
+
if (!perms?.w) {
|
|
92
|
+
return { allowed: false, tier: node.encrypted ? 'crypto' : 'policy' };
|
|
93
|
+
}
|
|
94
|
+
return { allowed: true, tier: node.encrypted ? 'crypto' : 'policy' };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if a user has admin permission on a subtree.
|
|
98
|
+
*/
|
|
99
|
+
checkAdmin(subtreeId, userId) {
|
|
100
|
+
const node = this.refNodes.get(subtreeId);
|
|
101
|
+
if (!node)
|
|
102
|
+
return true; // No ACL = everyone is admin
|
|
103
|
+
return node.acl[userId]?.a ?? false;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate a permission grant request.
|
|
107
|
+
* Only users with a:true can grant permissions.
|
|
108
|
+
*/
|
|
109
|
+
validateGrant(subtreeId, granterId, targetPerms) {
|
|
110
|
+
// Validate permission combinations
|
|
111
|
+
if (targetPerms.w && !targetPerms.r) {
|
|
112
|
+
return { ok: false, error: new AclError('Write without read is not allowed', 'INVALID_PERMS') };
|
|
113
|
+
}
|
|
114
|
+
if (targetPerms.a && !targetPerms.w) {
|
|
115
|
+
return { ok: false, error: new AclError('Admin without write is not allowed', 'INVALID_PERMS') };
|
|
116
|
+
}
|
|
117
|
+
if (!this.checkAdmin(subtreeId, granterId)) {
|
|
118
|
+
return { ok: false, error: new PermissionDeniedError('Only admins can grant access') };
|
|
119
|
+
}
|
|
120
|
+
return { ok: true, value: undefined };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Validate a permission revoke request.
|
|
124
|
+
* Cannot revoke the owner.
|
|
125
|
+
*/
|
|
126
|
+
validateRevoke(subtreeId, revokerId, targetId) {
|
|
127
|
+
const node = this.refNodes.get(subtreeId);
|
|
128
|
+
if (!node) {
|
|
129
|
+
return { ok: false, error: new AclError('No ACL on this subtree', 'NO_ACL') };
|
|
130
|
+
}
|
|
131
|
+
if (targetId === node.owner) {
|
|
132
|
+
return { ok: false, error: new AclError('Cannot revoke owner access', 'OWNER_PROTECTED') };
|
|
133
|
+
}
|
|
134
|
+
if (!this.checkAdmin(subtreeId, revokerId)) {
|
|
135
|
+
return { ok: false, error: new PermissionDeniedError('Only admins can revoke access') };
|
|
136
|
+
}
|
|
137
|
+
return { ok: true, value: undefined };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Create a new ACL reference node for a subtree.
|
|
141
|
+
* The node must be signed by the server before distribution.
|
|
142
|
+
*/
|
|
143
|
+
createAclPayload(subtreeId, ownerId, acl, encrypted) {
|
|
144
|
+
return { subtreeId, owner: ownerId, encrypted, acl };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate wrapped CEKs for all users in an encrypted subtree.
|
|
148
|
+
*/
|
|
149
|
+
generateCeks(acl, userPublicKeys) {
|
|
150
|
+
const cek = generateCek();
|
|
151
|
+
const wrappedCeks = {};
|
|
152
|
+
for (const userId of Object.keys(acl)) {
|
|
153
|
+
const pk = userPublicKeys[userId];
|
|
154
|
+
if (pk) {
|
|
155
|
+
wrappedCeks[userId] = wrapCek(cek, pk);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { cek, wrappedCeks };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Rotate CEK after user revocation.
|
|
162
|
+
* Generates new CEK and re-wraps for remaining users.
|
|
163
|
+
*/
|
|
164
|
+
rotateCek(acl, userPublicKeys) {
|
|
165
|
+
return this.generateCeks(acl, userPublicKeys);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the ACL for a subtree.
|
|
169
|
+
*/
|
|
170
|
+
getAcl(subtreeId) {
|
|
171
|
+
return this.refNodes.get(subtreeId)?.acl;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get reference node for a subtree.
|
|
175
|
+
*/
|
|
176
|
+
getReferenceNode(subtreeId) {
|
|
177
|
+
return this.refNodes.get(subtreeId);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if a subtree is encrypted.
|
|
181
|
+
*/
|
|
182
|
+
isEncrypted(subtreeId) {
|
|
183
|
+
return this.refNodes.get(subtreeId)?.encrypted ?? false;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Check if a subtree has an ACL.
|
|
187
|
+
*/
|
|
188
|
+
hasAcl(subtreeId) {
|
|
189
|
+
return this.refNodes.has(subtreeId);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get the full reference node for a subtree.
|
|
193
|
+
*/
|
|
194
|
+
getRefNode(subtreeId) {
|
|
195
|
+
return this.refNodes.get(subtreeId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// ── Helpers ────────────────────────────────────────────────
|
|
199
|
+
/** Serialize ACL payload to bytes for signing/verification. */
|
|
200
|
+
function serializeAclPayload(payload) {
|
|
201
|
+
const json = JSON.stringify(payload, Object.keys(payload).sort());
|
|
202
|
+
return new TextEncoder().encode(json);
|
|
203
|
+
}
|
|
204
|
+
/** Exported for server-side signing. */
|
|
205
|
+
export { serializeAclPayload };
|
|
206
|
+
// ── Reference Node Serialization ──────────────────────────
|
|
207
|
+
/**
|
|
208
|
+
* Serialize an AclReferenceNode to a Yjs-storable flat object.
|
|
209
|
+
*
|
|
210
|
+
* Flattens the HybridSignature to top-level `sig_ed25519` / `sig_mlDsa44`
|
|
211
|
+
* keys so that Yjs Y.Map roundtrip preserves the Uint8Array values.
|
|
212
|
+
*/
|
|
213
|
+
// @req FR-28
|
|
214
|
+
export function serializeRefNode(node) {
|
|
215
|
+
const result = {
|
|
216
|
+
subtreeId: node.subtreeId,
|
|
217
|
+
owner: node.owner,
|
|
218
|
+
encrypted: node.encrypted,
|
|
219
|
+
acl: node.acl,
|
|
220
|
+
sig_ed25519: node.signature.ed25519,
|
|
221
|
+
sig_mlDsa44: node.signature.mlDsa44,
|
|
222
|
+
};
|
|
223
|
+
if (node.ceks) {
|
|
224
|
+
result.ceks = node.ceks;
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Deserialize a Yjs-stored object back into an AclReferenceNode.
|
|
230
|
+
*
|
|
231
|
+
* Throws if the data doesn't contain the required fields.
|
|
232
|
+
*/
|
|
233
|
+
// @req FR-28
|
|
234
|
+
export function deserializeRefNode(data) {
|
|
235
|
+
if (typeof data.subtreeId !== 'string' ||
|
|
236
|
+
typeof data.owner !== 'string' ||
|
|
237
|
+
typeof data.encrypted !== 'boolean' ||
|
|
238
|
+
typeof data.acl !== 'object' || data.acl === null ||
|
|
239
|
+
!(data.sig_ed25519 instanceof Uint8Array) ||
|
|
240
|
+
!(data.sig_mlDsa44 instanceof Uint8Array)) {
|
|
241
|
+
throw new AclError('Invalid ACL reference node data', 'ACL_INVALID_DATA');
|
|
242
|
+
}
|
|
243
|
+
const node = {
|
|
244
|
+
subtreeId: data.subtreeId,
|
|
245
|
+
owner: data.owner,
|
|
246
|
+
encrypted: data.encrypted,
|
|
247
|
+
acl: data.acl,
|
|
248
|
+
signature: {
|
|
249
|
+
ed25519: data.sig_ed25519,
|
|
250
|
+
mlDsa44: data.sig_mlDsa44,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
if (data.ceks && typeof data.ceks === 'object' && !ArrayBuffer.isView(data.ceks) && !Array.isArray(data.ceks)) {
|
|
254
|
+
node.ceks = data.ceks;
|
|
255
|
+
}
|
|
256
|
+
return node;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=acl.js.map
|
package/dist/acl.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acl.js","sourceRoot":"","sources":["../src/acl.ts"],"names":[],"mappings":"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"}
|
package/dist/crypto.d.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Module — PQ hybrid cryptography for Sietch.
|
|
3
|
+
*
|
|
4
|
+
* Provides hybrid Ed25519+ML-DSA-44 signing, X25519+ML-KEM-768 key exchange,
|
|
5
|
+
* AES-256-GCM symmetric encryption, and Merkle-batched signatures.
|
|
6
|
+
*
|
|
7
|
+
* Internal module — never exposed directly to the developer API.
|
|
8
|
+
*/
|
|
9
|
+
import type { Result } from './types.js';
|
|
10
|
+
import { CryptoError, KeyExchangeError } from './errors.js';
|
|
11
|
+
/** Classical Ed25519 keypair. */
|
|
12
|
+
export interface Ed25519Keypair {
|
|
13
|
+
publicKey: Uint8Array;
|
|
14
|
+
privateKey: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
/** Post-quantum ML-DSA-44 keypair. */
|
|
17
|
+
export interface MlDsa44Keypair {
|
|
18
|
+
publicKey: Uint8Array;
|
|
19
|
+
secretKey: Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
/** Classical X25519 keypair. */
|
|
22
|
+
export interface X25519Keypair {
|
|
23
|
+
publicKey: Uint8Array;
|
|
24
|
+
privateKey: Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
/** Post-quantum ML-KEM-768 keypair. */
|
|
27
|
+
export interface MlKem768Keypair {
|
|
28
|
+
publicKey: Uint8Array;
|
|
29
|
+
secretKey: Uint8Array;
|
|
30
|
+
}
|
|
31
|
+
/** Hybrid signing keypair (Ed25519 + ML-DSA-44). */
|
|
32
|
+
export interface HybridSigningKeypair {
|
|
33
|
+
ed25519: Ed25519Keypair;
|
|
34
|
+
mlDsa44: MlDsa44Keypair;
|
|
35
|
+
}
|
|
36
|
+
/** Hybrid KEM keypair (X25519 + ML-KEM-768). */
|
|
37
|
+
export interface HybridKemKeypair {
|
|
38
|
+
x25519: X25519Keypair;
|
|
39
|
+
mlKem768: MlKem768Keypair;
|
|
40
|
+
}
|
|
41
|
+
/** Complete hybrid keypair (signing + key exchange). */
|
|
42
|
+
export interface HybridKeypair {
|
|
43
|
+
signing: HybridSigningKeypair;
|
|
44
|
+
kem: HybridKemKeypair;
|
|
45
|
+
}
|
|
46
|
+
/** Public-only half of a hybrid keypair. */
|
|
47
|
+
export interface HybridPublicKey {
|
|
48
|
+
signing: {
|
|
49
|
+
ed25519: Uint8Array;
|
|
50
|
+
mlDsa44: Uint8Array;
|
|
51
|
+
};
|
|
52
|
+
kem: {
|
|
53
|
+
x25519: Uint8Array;
|
|
54
|
+
mlKem768: Uint8Array;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Hybrid signature (Ed25519 + ML-DSA-44 concatenated). */
|
|
58
|
+
export interface HybridSignature {
|
|
59
|
+
ed25519: Uint8Array;
|
|
60
|
+
mlDsa44: Uint8Array;
|
|
61
|
+
}
|
|
62
|
+
/** Merkle proof for a single record in a batch. */
|
|
63
|
+
export interface MerkleProof {
|
|
64
|
+
index: number;
|
|
65
|
+
siblings: Uint8Array[];
|
|
66
|
+
}
|
|
67
|
+
/** Batch signature with Merkle root signed by hybrid scheme. */
|
|
68
|
+
export interface BatchSignature {
|
|
69
|
+
root: Uint8Array;
|
|
70
|
+
signature: HybridSignature;
|
|
71
|
+
leaves: Uint8Array[];
|
|
72
|
+
}
|
|
73
|
+
/** Shared secret derived from hybrid key exchange. */
|
|
74
|
+
export interface SharedSecret {
|
|
75
|
+
secret: Uint8Array;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Generate a complete hybrid keypair for signing and key exchange.
|
|
79
|
+
*
|
|
80
|
+
* @returns Hybrid keypair with Ed25519+ML-DSA-44 signing and X25519+ML-KEM-768 KEM
|
|
81
|
+
*/
|
|
82
|
+
export declare function generateKeypair(): HybridKeypair;
|
|
83
|
+
/**
|
|
84
|
+
* Extract public key portion from a full keypair.
|
|
85
|
+
*/
|
|
86
|
+
export declare function extractPublicKey(keypair: HybridKeypair): HybridPublicKey;
|
|
87
|
+
/**
|
|
88
|
+
* Sign a message with hybrid Ed25519 + ML-DSA-44 signature.
|
|
89
|
+
*/
|
|
90
|
+
export declare function sign(message: Uint8Array, keypair: HybridSigningKeypair): HybridSignature;
|
|
91
|
+
/**
|
|
92
|
+
* Verify a hybrid signature against a message and public key.
|
|
93
|
+
*
|
|
94
|
+
* Both classical and post-quantum signatures must be valid.
|
|
95
|
+
*/
|
|
96
|
+
export declare function verify(message: Uint8Array, signature: HybridSignature, publicKey: HybridPublicKey): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Sign a batch of records using Merkle-batched signatures (ADR-004).
|
|
99
|
+
*
|
|
100
|
+
* Up to 64 records share a single hybrid signature via a Merkle tree.
|
|
101
|
+
* Each record gets a per-record Merkle proof for independent verification.
|
|
102
|
+
*
|
|
103
|
+
* @param records - Array of records to sign (max BATCH_SIZE)
|
|
104
|
+
* @param keypair - Signing keypair
|
|
105
|
+
* @returns Batch signature with root and per-record leaf hashes
|
|
106
|
+
*/
|
|
107
|
+
export declare function signBatch(records: Uint8Array[], keypair: HybridSigningKeypair): BatchSignature;
|
|
108
|
+
/**
|
|
109
|
+
* Get a Merkle proof for a specific record in a batch.
|
|
110
|
+
*/
|
|
111
|
+
export declare function getBatchProof(records: Uint8Array[], index: number): MerkleProof;
|
|
112
|
+
/**
|
|
113
|
+
* Verify a single record against a batch signature using its Merkle proof.
|
|
114
|
+
*/
|
|
115
|
+
export declare function verifyRecord(record: Uint8Array, batchSig: BatchSignature, proof: MerkleProof, publicKey: HybridPublicKey): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Perform hybrid key exchange: X25519 + ML-KEM-768.
|
|
118
|
+
*
|
|
119
|
+
* Initiator side: encapsulates a shared secret using the responder's public keys.
|
|
120
|
+
*
|
|
121
|
+
* @returns Shared secret and encapsulated data to send to responder
|
|
122
|
+
*/
|
|
123
|
+
export declare function keyExchangeInitiate(localKeypair: HybridKemKeypair, remotePublicKey: HybridPublicKey): {
|
|
124
|
+
shared: SharedSecret;
|
|
125
|
+
encapsulated: {
|
|
126
|
+
classical: Uint8Array;
|
|
127
|
+
pq: Uint8Array;
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Respond to a hybrid key exchange.
|
|
132
|
+
*
|
|
133
|
+
* Responder side: decapsulates the shared secret using own private keys.
|
|
134
|
+
*/
|
|
135
|
+
export declare function keyExchangeRespond(localKeypair: HybridKemKeypair, encapsulated: {
|
|
136
|
+
classical: Uint8Array;
|
|
137
|
+
pq: Uint8Array;
|
|
138
|
+
}): Result<SharedSecret, KeyExchangeError>;
|
|
139
|
+
/**
|
|
140
|
+
* Encrypt plaintext with AES-256-GCM.
|
|
141
|
+
*
|
|
142
|
+
* Generates a random 12-byte nonce per invocation (prepended to ciphertext).
|
|
143
|
+
*
|
|
144
|
+
* @param plaintext - Data to encrypt
|
|
145
|
+
* @param key - 32-byte AES key
|
|
146
|
+
* @returns Nonce (12 bytes) || ciphertext || tag (16 bytes)
|
|
147
|
+
*/
|
|
148
|
+
export declare function encrypt(plaintext: Uint8Array, key: Uint8Array): Uint8Array;
|
|
149
|
+
/**
|
|
150
|
+
* Decrypt AES-256-GCM ciphertext.
|
|
151
|
+
*
|
|
152
|
+
* @param data - Nonce (12 bytes) || ciphertext || tag (16 bytes)
|
|
153
|
+
* @param key - 32-byte AES key
|
|
154
|
+
* @returns Decrypted plaintext
|
|
155
|
+
*/
|
|
156
|
+
export declare function decrypt(data: Uint8Array, key: Uint8Array): Result<Uint8Array, CryptoError>;
|
|
157
|
+
/**
|
|
158
|
+
* Wrap a Content Encryption Key (CEK) for a recipient using hybrid KEM.
|
|
159
|
+
*
|
|
160
|
+
* Uses ML-KEM-768 encapsulation to derive a wrapping key, then AES-256-GCM wraps the CEK.
|
|
161
|
+
*
|
|
162
|
+
* @returns Wrapped CEK: KEM ciphertext || AES-encrypted CEK
|
|
163
|
+
*/
|
|
164
|
+
export declare function wrapCek(cek: Uint8Array, recipientPk: HybridPublicKey): Uint8Array;
|
|
165
|
+
/**
|
|
166
|
+
* Unwrap a CEK using own private keys.
|
|
167
|
+
*/
|
|
168
|
+
export declare function unwrapCek(wrapped: Uint8Array, ownKeypair: HybridKemKeypair): Result<Uint8Array, CryptoError>;
|
|
169
|
+
/**
|
|
170
|
+
* Encrypt private key material for storage using a password-derived key.
|
|
171
|
+
*
|
|
172
|
+
* @param keypair - Full hybrid keypair to protect
|
|
173
|
+
* @param password - User password or browser-derived key
|
|
174
|
+
* @returns Encrypted keypair bytes
|
|
175
|
+
*/
|
|
176
|
+
export declare function encryptKeypair(keypair: HybridKeypair, password: Uint8Array): Uint8Array;
|
|
177
|
+
/**
|
|
178
|
+
* Decrypt private key material from storage.
|
|
179
|
+
*/
|
|
180
|
+
export declare function decryptKeypair(encrypted: Uint8Array, password: Uint8Array): Result<HybridKeypair, CryptoError>;
|
|
181
|
+
/**
|
|
182
|
+
* Generate a random Content Encryption Key (CEK) for subtree encryption.
|
|
183
|
+
*/
|
|
184
|
+
export declare function generateCek(): Uint8Array;
|