@aztec/kv-store 0.0.1-commit.b2a5d0dd1 → 0.0.1-commit.b3d3157a

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 (71) hide show
  1. package/README.md +8 -1
  2. package/dest/indexeddb/index.js +1 -1
  3. package/dest/interfaces/array_test_suite.d.ts +1 -1
  4. package/dest/interfaces/array_test_suite.d.ts.map +1 -1
  5. package/dest/interfaces/array_test_suite.js +33 -34
  6. package/dest/interfaces/index.d.ts +2 -2
  7. package/dest/interfaces/index.d.ts.map +1 -1
  8. package/dest/interfaces/map_test_suite.d.ts +1 -1
  9. package/dest/interfaces/map_test_suite.d.ts.map +1 -1
  10. package/dest/interfaces/map_test_suite.js +32 -33
  11. package/dest/interfaces/multi_map_test_suite.d.ts +1 -1
  12. package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -1
  13. package/dest/interfaces/multi_map_test_suite.js +68 -69
  14. package/dest/interfaces/set_test_suite.d.ts +1 -1
  15. package/dest/interfaces/set_test_suite.d.ts.map +1 -1
  16. package/dest/interfaces/set_test_suite.js +13 -14
  17. package/dest/interfaces/singleton_test_suite.d.ts +1 -1
  18. package/dest/interfaces/singleton_test_suite.d.ts.map +1 -1
  19. package/dest/interfaces/singleton_test_suite.js +6 -7
  20. package/dest/lmdb/index.js +2 -2
  21. package/dest/lmdb-v2/factory.d.ts +28 -3
  22. package/dest/lmdb-v2/factory.d.ts.map +1 -1
  23. package/dest/lmdb-v2/factory.js +61 -10
  24. package/dest/lmdb-v2/store.d.ts +2 -2
  25. package/dest/lmdb-v2/store.d.ts.map +1 -1
  26. package/dest/lmdb-v2/store.js +4 -4
  27. package/dest/sqlite-opfs/array.js +2 -2
  28. package/dest/sqlite-opfs/errors.d.ts +27 -0
  29. package/dest/sqlite-opfs/errors.d.ts.map +1 -0
  30. package/dest/sqlite-opfs/errors.js +34 -0
  31. package/dest/sqlite-opfs/index.d.ts +10 -1
  32. package/dest/sqlite-opfs/index.d.ts.map +1 -1
  33. package/dest/sqlite-opfs/index.js +10 -1
  34. package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts +32 -0
  35. package/dest/sqlite-opfs/internal/ordered-binary-browser.d.ts.map +1 -0
  36. package/dest/sqlite-opfs/internal/ordered-binary-browser.js +448 -0
  37. package/dest/sqlite-opfs/map.d.ts +2 -2
  38. package/dest/sqlite-opfs/map.d.ts.map +1 -1
  39. package/dest/sqlite-opfs/map.js +2 -2
  40. package/dest/sqlite-opfs/messages.d.ts +9 -1
  41. package/dest/sqlite-opfs/messages.d.ts.map +1 -1
  42. package/dest/sqlite-opfs/messages.js +1 -1
  43. package/dest/sqlite-opfs/singleton.js +2 -2
  44. package/dest/sqlite-opfs/store.d.ts +11 -2
  45. package/dest/sqlite-opfs/store.d.ts.map +1 -1
  46. package/dest/sqlite-opfs/store.js +51 -12
  47. package/dest/sqlite-opfs/worker.js +63 -8
  48. package/dest/stores/l2_tips_store.d.ts +3 -3
  49. package/dest/stores/l2_tips_store.d.ts.map +1 -1
  50. package/dest/stores/l2_tips_store.js +2 -2
  51. package/package.json +21 -21
  52. package/src/indexeddb/index.ts +1 -1
  53. package/src/interfaces/array_test_suite.ts +33 -35
  54. package/src/interfaces/index.ts +1 -1
  55. package/src/interfaces/map_test_suite.ts +32 -34
  56. package/src/interfaces/multi_map_test_suite.ts +65 -67
  57. package/src/interfaces/set_test_suite.ts +13 -15
  58. package/src/interfaces/singleton_test_suite.ts +6 -8
  59. package/src/lmdb/index.ts +2 -2
  60. package/src/lmdb-v2/factory.ts +79 -9
  61. package/src/lmdb-v2/store.ts +4 -2
  62. package/src/sqlite-opfs/array.ts +2 -2
  63. package/src/sqlite-opfs/errors.ts +44 -0
  64. package/src/sqlite-opfs/index.ts +13 -1
  65. package/src/sqlite-opfs/internal/ordered-binary-browser.js +465 -0
  66. package/src/sqlite-opfs/map.ts +2 -2
  67. package/src/sqlite-opfs/messages.ts +13 -2
  68. package/src/sqlite-opfs/singleton.ts +2 -2
  69. package/src/sqlite-opfs/store.ts +53 -5
  70. package/src/sqlite-opfs/worker.ts +78 -9
  71. package/src/stores/l2_tips_store.ts +3 -2
@@ -1,6 +1,7 @@
1
1
  /// <reference lib="webworker" />
2
- import sqlite3InitModule, { type Database, type SAHPoolUtil, type Sqlite3Static } from '@sqlite.org/sqlite-wasm';
2
+ import sqlite3InitModule, { type Database, type SAHPoolUtil, type Sqlite3Static } from '@aztec/sqlite3mc-wasm';
3
3
 
4
+ import { SqliteEncryptionError, type SqliteEncryptionErrorCode, isDecryptFailureMessage } from './errors.js';
4
5
  import type { ResultRow, SqlValue, WorkerRequest, WorkerResponse } from './messages.js';
5
6
 
6
7
  const SCHEMA_SQL = `
@@ -20,6 +21,7 @@ const SCHEMA_SQL = `
20
21
 
21
22
  const DEFAULT_SAH_POOL_DIRECTORY = '.aztec-kv';
22
23
  const SAH_POOL_VFS_NAME = 'aztec-kv-opfs';
24
+ const MC_SAH_POOL_VFS_NAME = `multipleciphers-${SAH_POOL_VFS_NAME}`;
23
25
 
24
26
  let sqlite3: Sqlite3Static | undefined;
25
27
  let pool: SAHPoolUtil | undefined;
@@ -28,24 +30,63 @@ let dbPath: string | undefined;
28
30
 
29
31
  async function ensurePool(directory: string): Promise<SAHPoolUtil> {
30
32
  sqlite3 ??= await sqlite3InitModule();
33
+ const s = sqlite3;
31
34
  if (!pool) {
32
- pool = await sqlite3.installOpfsSAHPoolVfs({
35
+ pool = await s.installOpfsSAHPoolVfs({
33
36
  name: SAH_POOL_VFS_NAME,
34
37
  directory,
35
38
  initialCapacity: 8,
36
39
  });
40
+ // Register a sqlite3mc-wrapped VFS pointing at our SAH Pool VFS.
41
+ // Encrypted DBs must be opened through this wrapper so sqlite3mc can
42
+ // intercept file I/O; plain DBs continue using the SAH Pool VFS directly.
43
+ // The wrapper name is `multipleciphers-<underlying>`.
44
+ (s.capi as unknown as { sqlite3mc_vfs_create(name: string, makeDefault: number): number }).sqlite3mc_vfs_create(
45
+ SAH_POOL_VFS_NAME,
46
+ 0,
47
+ );
37
48
  }
38
- return pool;
49
+ return pool!;
39
50
  }
40
51
 
41
- async function handleInit(dbName: string, ephemeral: boolean, directory?: string): Promise<void> {
52
+ /**
53
+ * Applies sqlite3mc's ChaCha20 page cipher using a pre-derived 32-byte key.
54
+ * The PRAGMAs must run before any schema DDL so sqlite3mc can decrypt existing
55
+ * pages and encrypt new ones. Zeroes the caller-held key array after the PRAGMA
56
+ * completes to minimize residency of the raw key bytes outside sqlite3mc's heap.
57
+ */
58
+ function applyEncryptionKey(conn: Database, key: Uint8Array): void {
59
+ const hex = Array.from(key, b => b.toString(16).padStart(2, '0')).join('');
60
+ conn.exec(`PRAGMA cipher = 'chacha20'`);
61
+ conn.exec(`PRAGMA key = "x'${hex}'"`);
62
+ key.fill(0);
63
+ }
64
+
65
+ async function handleInit(
66
+ dbName: string,
67
+ ephemeral: boolean,
68
+ directory?: string,
69
+ encryptionKey?: Uint8Array,
70
+ ): Promise<void> {
42
71
  sqlite3 ??= await sqlite3InitModule();
72
+ const s = sqlite3;
73
+ if (encryptionKey !== undefined && ephemeral) {
74
+ throw new SqliteEncryptionError(
75
+ 'encryption_not_supported_for_ephemeral',
76
+ 'encryptionKey is not supported for ephemeral (:memory:) stores',
77
+ );
78
+ }
43
79
  if (ephemeral) {
44
- db = new sqlite3.oo1.DB(':memory:', 'c');
80
+ db = new s.oo1.DB(':memory:', 'c');
45
81
  } else {
46
- const p = await ensurePool(directory ?? DEFAULT_SAH_POOL_DIRECTORY);
82
+ await ensurePool(directory ?? DEFAULT_SAH_POOL_DIRECTORY);
47
83
  dbPath = normalizeDbPath(dbName);
48
- db = new p.OpfsSAHPoolDb(dbPath);
84
+ if (encryptionKey !== undefined) {
85
+ db = new s.oo1.DB({ filename: dbPath, flags: 'c', vfs: MC_SAH_POOL_VFS_NAME });
86
+ applyEncryptionKey(db, encryptionKey);
87
+ } else {
88
+ db = new pool!.OpfsSAHPoolDb(dbPath);
89
+ }
49
90
  }
50
91
  runSql(SCHEMA_SQL);
51
92
  }
@@ -121,7 +162,7 @@ function respond(msg: WorkerResponse): void {
121
162
  try {
122
163
  switch (req.type) {
123
164
  case 'init':
124
- await handleInit(req.dbName, req.ephemeral, req.poolDirectory);
165
+ await handleInit(req.dbName, req.ephemeral, req.poolDirectory, req.encryptionKey);
125
166
  return respond({ type: 'ok', id: req.id });
126
167
  case 'close':
127
168
  handleClose();
@@ -157,6 +198,34 @@ function respond(msg: WorkerResponse): void {
157
198
  }
158
199
  } catch (err) {
159
200
  const message = err instanceof Error ? err.message : String(err);
160
- respond({ type: 'err', id: req.id, message });
201
+ respond({ type: 'err', id: req.id, message, encryptionCode: detectEncryptionCode(req, err, message) });
161
202
  }
162
203
  };
204
+
205
+ /**
206
+ * Maps a thrown error during request handling to a typed encryption code, so the
207
+ * main thread can re-hydrate it as a {@link SqliteEncryptionError}. Returns
208
+ * `undefined` for non-encryption errors (preserves the existing untyped path).
209
+ *
210
+ * Two sources:
211
+ * - The error was already a `SqliteEncryptionError` (pre-flight throws inside
212
+ * this worker — e.g. ephemeral + encryptionKey). Forward its code as-is.
213
+ * - The error came from SQLite/sqlite3mc with a known decrypt-failure message
214
+ * during an `init` request. Both "wrong key supplied" and "no key supplied
215
+ * to an encrypted DB" surface as SQLITE_NOTADB ("file is not a database") —
216
+ * we don't constrain on `req.encryptionKey` because the no-key-on-encrypted-DB
217
+ * case is exactly when the caller most needs the typed signal.
218
+ */
219
+ function detectEncryptionCode(
220
+ req: WorkerRequest,
221
+ err: unknown,
222
+ message: string,
223
+ ): SqliteEncryptionErrorCode | undefined {
224
+ if (err instanceof SqliteEncryptionError) {
225
+ return err.code;
226
+ }
227
+ if (req.type === 'init' && isDecryptFailureMessage(message)) {
228
+ return 'decrypt_failed';
229
+ }
230
+ return undefined;
231
+ }
@@ -1,5 +1,5 @@
1
1
  import { BlockNumber, CheckpointNumber } from '@aztec/foundation/branded-types';
2
- import { type L2BlockTag, L2TipsStoreBase } from '@aztec/stdlib/block';
2
+ import { type BlockHash, type L2BlockTag, L2TipsStoreBase } from '@aztec/stdlib/block';
3
3
  import { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
4
4
 
5
5
  import type { AztecAsyncMap } from '../interfaces/map.js';
@@ -18,8 +18,9 @@ export class L2TipsKVStore extends L2TipsStoreBase {
18
18
  constructor(
19
19
  private store: AztecAsyncKVStore,
20
20
  namespace: string,
21
+ initialBlockHash: BlockHash,
21
22
  ) {
22
- super();
23
+ super(initialBlockHash);
23
24
  this.l2TipsStore = store.openMap([namespace, 'l2_tips'].join('_'));
24
25
  this.l2BlockHashesStore = store.openMap([namespace, 'l2_block_hashes'].join('_'));
25
26
  this.l2BlockNumberToCheckpointNumberStore = store.openMap(