@orbinum/sdk 0.25.0 → 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.
@@ -0,0 +1,292 @@
1
+ /**
2
+ * The shape a note takes at rest.
3
+ *
4
+ * Any backend — IndexedDB, SQLite, remote — produces and consumes exactly this,
5
+ * so `encryptNote` / `decryptNoteRecord` work against all of them unmodified.
6
+ */
7
+ /**
8
+ * One encrypted note as stored.
9
+ *
10
+ * Identifiers are BLINDED: stored as HMAC tags under the vault blind key, never
11
+ * the raw on-chain hex. A storage dump therefore reveals no commitment,
12
+ * nullifier or asset that could be linked to chain activity, while equality
13
+ * lookups still work by comparing tags.
14
+ *
15
+ * `spent` / `spentAt` stay plaintext deliberately — they are local flags with
16
+ * no on-chain linkage, and keeping them readable lets a backend filter spent
17
+ * notes without unlocking the vault.
18
+ */
19
+ interface EncryptedNoteRecord {
20
+ /** Primary key — blinded commitment tag: HMAC(blindKey, commitmentHex). */
21
+ commitmentTag: string;
22
+ /** AES-GCM IV for this record — base64 */
23
+ iv: string;
24
+ /** AES-GCM ciphertext of the full ZkNote JSON — base64 */
25
+ ciphertext: string;
26
+ /** Blinded nullifier tag: HMAC(blindKey, nullifierHex). Quick spent-check. */
27
+ nullifierTag: string;
28
+ /** Blinded asset tag: HMAC(blindKey, assetId). Filter by asset without unlock. */
29
+ assetTag: string;
30
+ /** Whether the note has already been spent/nullified on-chain. */
31
+ spent?: boolean;
32
+ /** When the app marked the note as spent locally, if known. */
33
+ spentAt?: number | null;
34
+ updatedAt: number;
35
+ }
36
+ /** Partial update applied to a note's spent status without re-encrypting the full payload. */
37
+ interface NoteStatusUpdate {
38
+ spent?: boolean;
39
+ spentAt?: number | null;
40
+ }
41
+
42
+ /**
43
+ * The persistence contract a wallet's vault runs on.
44
+ *
45
+ * Split by responsibility rather than exposed as one interface: a consumer that
46
+ * only decrypts notes never has to supply a nullifier cache, and an
47
+ * implementation can grow one concern at a time. `VaultStorage` composes the
48
+ * three for the common case.
49
+ *
50
+ * Every method is async because the reference backend is IndexedDB. Records
51
+ * cross this boundary already encrypted and with identifiers blinded (see
52
+ * `EncryptedNoteRecord`), so a backend never sees a commitment, a nullifier, or
53
+ * an amount — which is what makes an untrusted or remote backend viable.
54
+ */
55
+
56
+ /** Wallet-level state that is not a note: cursors and ephemeral-key counters. */
57
+ interface VaultConfigRecord {
58
+ id: 'main';
59
+ /** Schema version of this record's shape. */
60
+ v: number;
61
+ /** Genesis hash the vault was last reconciled against. */
62
+ chainFingerprint?: string;
63
+ /** Highest leafIndex a completed scan processed; the next one resumes after it. */
64
+ lastScannedLeafIndex?: number;
65
+ /**
66
+ * Next self-note ephemeral index.
67
+ *
68
+ * Monotonic, and that is a protocol requirement rather than bookkeeping:
69
+ * reusing an index republishes the same ephPk and publicly links the two
70
+ * notes as sharing a creator.
71
+ */
72
+ selfEphCounter?: number;
73
+ /**
74
+ * Counterparties whose payments can be recognised without a trial ECDH,
75
+ * keyed by their packed viewing public key (lowercase hex). Each carries its
76
+ * own counter, monotonic for the same reason as `selfEphCounter`.
77
+ */
78
+ pairwiseCounterparties?: Record<string, {
79
+ nextIndex: number;
80
+ addedAt: number;
81
+ }>;
82
+ createdAt: number;
83
+ updatedAt: number;
84
+ }
85
+ /** An encrypted entry in the local transaction history. */
86
+ interface EncryptedTxRecord {
87
+ /** Primary key — the transaction hash, the one field left in plaintext. */
88
+ id: string;
89
+ /** AES-GCM IV, base64. */
90
+ iv: string;
91
+ /** AES-GCM ciphertext of the record body, base64. */
92
+ ciphertext: string;
93
+ updatedAt: number;
94
+ }
95
+ /**
96
+ * What is known locally about a spend, once a nullifier turns up in the set.
97
+ *
98
+ * Both fields are public chain data recovered from a set that always transfers
99
+ * whole — neither implies a per-nullifier query.
100
+ */
101
+ interface SpendDetails {
102
+ /** Spend block timestamp (ms); null when the source carried none. */
103
+ spentAt: number | null;
104
+ /** Hash of the spending transaction; null when the extrinsic was unresolvable. */
105
+ txHash: string | null;
106
+ }
107
+ /**
108
+ * One entry of the spent-nullifier set, stored as it arrives.
109
+ *
110
+ * Field names are terse because there is one record per spent note on the
111
+ * chain: at millions of notes the key strings are a material share of the
112
+ * cache's size on disk.
113
+ */
114
+ interface CachedNullifier {
115
+ /** The nullifier hex, as the source publishes it. Primary key. */
116
+ h: string;
117
+ /** Spend block timestamp (ms); null when the source carried none. */
118
+ ts: number | null;
119
+ /** Hash of the spending transaction; null when unresolvable. */
120
+ tx?: string | null;
121
+ }
122
+ /** Progress of the nullifier-set download, so a sync resumes instead of restarting. */
123
+ interface NullifierSyncMeta {
124
+ id: 'main';
125
+ /** Sealed chunks already stored. */
126
+ chunksDone: number;
127
+ /** Generation of the set the stored chunks belong to; a change invalidates them. */
128
+ generation: string;
129
+ totalStored: number;
130
+ updatedAt: number;
131
+ }
132
+ /** Config and note persistence — the minimum a wallet needs to hold funds. */
133
+ interface NoteStorage {
134
+ getConfig(): Promise<VaultConfigRecord | null>;
135
+ putConfig(config: VaultConfigRecord): Promise<void>;
136
+ /**
137
+ * Applies `mutate` to the stored config as ONE atomic read-modify-write.
138
+ *
139
+ * Part of the contract, not sugar over get + put. Two concurrent callers
140
+ * doing that separately both read the same `selfEphCounter`, so both derive
141
+ * the same ephemeral index and publish the same ephPk — a privacy leak, not
142
+ * a lost update. A backend that cannot make this atomic cannot host a vault.
143
+ *
144
+ * Resolves to null when no config exists; `mutate` is not called.
145
+ */
146
+ updateConfig(mutate: (config: VaultConfigRecord) => VaultConfigRecord): Promise<VaultConfigRecord | null>;
147
+ getAllNoteRecords(): Promise<EncryptedNoteRecord[]>;
148
+ putNote(record: EncryptedNoteRecord): Promise<void>;
149
+ putNotes(records: EncryptedNoteRecord[]): Promise<void>;
150
+ deleteNote(commitmentTag: string): Promise<void>;
151
+ deleteNotes(commitmentTags: string[]): Promise<void>;
152
+ clearNotes(): Promise<void>;
153
+ }
154
+ /**
155
+ * Local mirror of the spent-nullifier set.
156
+ *
157
+ * The whole set is downloaded and intersected locally, deliberately: asking a
158
+ * server whether one specific nullifier is spent would tell it which notes the
159
+ * wallet holds. That is why this is a cache to fill, not a lookup to call.
160
+ */
161
+ interface NullifierCache {
162
+ /**
163
+ * Stores one sealed chunk together with the sync progress it produced.
164
+ *
165
+ * Both must land in the same transaction: progress ahead of the data would
166
+ * make the next sync resume past chunks the cache never stored, leaving
167
+ * spent notes looking unspent.
168
+ */
169
+ putNullifierChunk(entries: CachedNullifier[], meta: NullifierSyncMeta): Promise<void>;
170
+ getNullifierSyncMeta(): Promise<NullifierSyncMeta | null>;
171
+ /**
172
+ * Which of `hexes` the cache holds. Absent keys are unspent as far as it
173
+ * knows.
174
+ *
175
+ * Matching is EXACT: a backend must not lowercase, trim or otherwise repair
176
+ * a key. Normalising happens once at ingestion, where the feed's rows are
177
+ * lowercased on the way into `putNullifierChunk` — so everything stored is
178
+ * already in one form, and a backend that "helpfully" normalised again
179
+ * would only hide a caller passing the wrong case.
180
+ *
181
+ * Both sides must agree, and the cost of disagreeing is not a missing row:
182
+ * a lookup that misses reports a SPENT note as unspent, the wallet offers
183
+ * it, and the spend dies on a duplicate nullifier.
184
+ */
185
+ getSpentNullifiers(hexes: string[]): Promise<Map<string, SpendDetails>>;
186
+ countNullifiers(): Promise<number>;
187
+ clearNullifierCache(): Promise<void>;
188
+ }
189
+ /** Encrypted local transaction history — outgoing sends the chain cannot reveal. */
190
+ interface TxHistoryStore {
191
+ addTxRecord(record: EncryptedTxRecord): Promise<void>;
192
+ getAllTxRecords(): Promise<EncryptedTxRecord[]>;
193
+ }
194
+ /** Everything a full wallet needs from persistence. */
195
+ interface VaultStorage extends NoteStorage, NullifierCache, TxHistoryStore {
196
+ /** Whether a vault already exists for this identity. */
197
+ hasVault(): Promise<boolean>;
198
+ }
199
+
200
+ /**
201
+ * The key that protects secrets at rest on one device.
202
+ *
203
+ * A single AES-GCM-256 key, generated once and kept for the life of the install.
204
+ * Where the platform allows it the key must be NON-EXTRACTABLE — created with
205
+ * `extractable: false` and persisted as a key handle rather than as bytes — so
206
+ * a storage dump, disk image or synced profile yields nothing usable.
207
+ *
208
+ * ## Why not derive it from the wallet signature
209
+ *
210
+ * Circular. The thing being protected is the cached identity, and the reason it
211
+ * is cached is to avoid asking for a signature again. A key derived from that
212
+ * signature would require the signature to read the cache that exists to avoid
213
+ * the signature.
214
+ *
215
+ * ## Platform notes
216
+ *
217
+ * Browsers and extensions: `@orbinum/sdk/storage/indexeddb` exports
218
+ * `getOrCreateIndexedDbDeviceKey`, which stores a non-extractable `CryptoKey`
219
+ * via structured clone — the key material never becomes visible to JavaScript.
220
+ *
221
+ * React Native and Node: no non-extractable handle survives a restart, so store
222
+ * raw key bytes in the platform's secure enclave (Keychain, Keystore) and import
223
+ * them with `importDeviceKey`. The isolation then comes from the enclave rather
224
+ * than from the key being unexportable.
225
+ */
226
+ /**
227
+ * A fresh AES-GCM-256 device key.
228
+ *
229
+ * `extractable` defaults to false and should stay that way. Passing true is for
230
+ * the platforms described above — the ones that cannot persist a key handle and
231
+ * must export raw bytes into a secure enclave. Anywhere a handle survives a
232
+ * restart, an extractable key is strictly worse: the material becomes readable
233
+ * by any code in the context.
234
+ */
235
+ declare function generateDeviceKey(extractable?: boolean): Promise<CryptoKey>;
236
+ /**
237
+ * Imports raw device-key bytes, for platforms that persist bytes in a secure
238
+ * enclave rather than a key handle. The imported key is non-extractable, so the
239
+ * bytes cannot be read back out through WebCrypto.
240
+ */
241
+ declare function importDeviceKey(raw: Uint8Array): Promise<CryptoKey>;
242
+ /**
243
+ * Where a device key is persisted between launches.
244
+ *
245
+ * `load` returning null means "no key yet", not "failed" — the provider takes
246
+ * that as permission to generate one. An adapter that cannot reach its backend
247
+ * should throw instead, or the first transient failure silently mints a second
248
+ * key and orphans every secret encrypted under the first.
249
+ */
250
+ interface DeviceKeyStore {
251
+ load(): Promise<CryptoKey | null>;
252
+ save(key: CryptoKey): Promise<void>;
253
+ }
254
+ /**
255
+ * The device key, generating and persisting it on first use.
256
+ *
257
+ * Cached in memory per process: generating a second key would silently orphan
258
+ * every secret encrypted under the first.
259
+ */
260
+ declare function createDeviceKeyProvider(store: DeviceKeyStore): () => Promise<CryptoKey>;
261
+
262
+ /**
263
+ * Where a host keeps small secrets between sessions.
264
+ *
265
+ * Three methods, because that is all the identity cache needs and every platform
266
+ * has them: `localStorage` in a page, `chrome.storage.local` in an extension,
267
+ * Keychain or SharedPreferences on mobile, a file on a server. Keeping the
268
+ * surface this small is what makes those adapters a few lines each.
269
+ *
270
+ * Values handed to `set` are already ENCRYPTED — see `sessionCache`. A store is
271
+ * not trusted with plaintext secrets, so a backend with weak isolation is still
272
+ * a safe place to put one.
273
+ */
274
+ interface SecretStore {
275
+ get(key: string): Promise<string | null>;
276
+ set(key: string, value: string): Promise<void>;
277
+ remove(key: string): Promise<void>;
278
+ /**
279
+ * Keys currently held. Needed to clear every network's cache for one account
280
+ * on disconnect, which cannot be done by constructing keys — the caller does
281
+ * not know which chains the user has visited.
282
+ */
283
+ keys(): Promise<string[]>;
284
+ }
285
+ /**
286
+ * An in-memory `SecretStore`. Loses everything on restart, which is the whole
287
+ * point of the real ones — useful for tests and for a host that deliberately
288
+ * wants re-signing on every launch.
289
+ */
290
+ declare function createMemorySecretStore(): SecretStore;
291
+
292
+ export { type CachedNullifier as C, type DeviceKeyStore as D, type EncryptedNoteRecord as E, type NullifierSyncMeta as N, type SpendDetails as S, type TxHistoryStore as T, type VaultStorage as V, type VaultConfigRecord as a, type EncryptedTxRecord as b, type SecretStore as c, type NoteStorage as d, type NoteStatusUpdate as e, type NullifierCache as f, createDeviceKeyProvider as g, createMemorySecretStore as h, generateDeviceKey as i, importDeviceKey as j };
@@ -0,0 +1,292 @@
1
+ /**
2
+ * The shape a note takes at rest.
3
+ *
4
+ * Any backend — IndexedDB, SQLite, remote — produces and consumes exactly this,
5
+ * so `encryptNote` / `decryptNoteRecord` work against all of them unmodified.
6
+ */
7
+ /**
8
+ * One encrypted note as stored.
9
+ *
10
+ * Identifiers are BLINDED: stored as HMAC tags under the vault blind key, never
11
+ * the raw on-chain hex. A storage dump therefore reveals no commitment,
12
+ * nullifier or asset that could be linked to chain activity, while equality
13
+ * lookups still work by comparing tags.
14
+ *
15
+ * `spent` / `spentAt` stay plaintext deliberately — they are local flags with
16
+ * no on-chain linkage, and keeping them readable lets a backend filter spent
17
+ * notes without unlocking the vault.
18
+ */
19
+ interface EncryptedNoteRecord {
20
+ /** Primary key — blinded commitment tag: HMAC(blindKey, commitmentHex). */
21
+ commitmentTag: string;
22
+ /** AES-GCM IV for this record — base64 */
23
+ iv: string;
24
+ /** AES-GCM ciphertext of the full ZkNote JSON — base64 */
25
+ ciphertext: string;
26
+ /** Blinded nullifier tag: HMAC(blindKey, nullifierHex). Quick spent-check. */
27
+ nullifierTag: string;
28
+ /** Blinded asset tag: HMAC(blindKey, assetId). Filter by asset without unlock. */
29
+ assetTag: string;
30
+ /** Whether the note has already been spent/nullified on-chain. */
31
+ spent?: boolean;
32
+ /** When the app marked the note as spent locally, if known. */
33
+ spentAt?: number | null;
34
+ updatedAt: number;
35
+ }
36
+ /** Partial update applied to a note's spent status without re-encrypting the full payload. */
37
+ interface NoteStatusUpdate {
38
+ spent?: boolean;
39
+ spentAt?: number | null;
40
+ }
41
+
42
+ /**
43
+ * The persistence contract a wallet's vault runs on.
44
+ *
45
+ * Split by responsibility rather than exposed as one interface: a consumer that
46
+ * only decrypts notes never has to supply a nullifier cache, and an
47
+ * implementation can grow one concern at a time. `VaultStorage` composes the
48
+ * three for the common case.
49
+ *
50
+ * Every method is async because the reference backend is IndexedDB. Records
51
+ * cross this boundary already encrypted and with identifiers blinded (see
52
+ * `EncryptedNoteRecord`), so a backend never sees a commitment, a nullifier, or
53
+ * an amount — which is what makes an untrusted or remote backend viable.
54
+ */
55
+
56
+ /** Wallet-level state that is not a note: cursors and ephemeral-key counters. */
57
+ interface VaultConfigRecord {
58
+ id: 'main';
59
+ /** Schema version of this record's shape. */
60
+ v: number;
61
+ /** Genesis hash the vault was last reconciled against. */
62
+ chainFingerprint?: string;
63
+ /** Highest leafIndex a completed scan processed; the next one resumes after it. */
64
+ lastScannedLeafIndex?: number;
65
+ /**
66
+ * Next self-note ephemeral index.
67
+ *
68
+ * Monotonic, and that is a protocol requirement rather than bookkeeping:
69
+ * reusing an index republishes the same ephPk and publicly links the two
70
+ * notes as sharing a creator.
71
+ */
72
+ selfEphCounter?: number;
73
+ /**
74
+ * Counterparties whose payments can be recognised without a trial ECDH,
75
+ * keyed by their packed viewing public key (lowercase hex). Each carries its
76
+ * own counter, monotonic for the same reason as `selfEphCounter`.
77
+ */
78
+ pairwiseCounterparties?: Record<string, {
79
+ nextIndex: number;
80
+ addedAt: number;
81
+ }>;
82
+ createdAt: number;
83
+ updatedAt: number;
84
+ }
85
+ /** An encrypted entry in the local transaction history. */
86
+ interface EncryptedTxRecord {
87
+ /** Primary key — the transaction hash, the one field left in plaintext. */
88
+ id: string;
89
+ /** AES-GCM IV, base64. */
90
+ iv: string;
91
+ /** AES-GCM ciphertext of the record body, base64. */
92
+ ciphertext: string;
93
+ updatedAt: number;
94
+ }
95
+ /**
96
+ * What is known locally about a spend, once a nullifier turns up in the set.
97
+ *
98
+ * Both fields are public chain data recovered from a set that always transfers
99
+ * whole — neither implies a per-nullifier query.
100
+ */
101
+ interface SpendDetails {
102
+ /** Spend block timestamp (ms); null when the source carried none. */
103
+ spentAt: number | null;
104
+ /** Hash of the spending transaction; null when the extrinsic was unresolvable. */
105
+ txHash: string | null;
106
+ }
107
+ /**
108
+ * One entry of the spent-nullifier set, stored as it arrives.
109
+ *
110
+ * Field names are terse because there is one record per spent note on the
111
+ * chain: at millions of notes the key strings are a material share of the
112
+ * cache's size on disk.
113
+ */
114
+ interface CachedNullifier {
115
+ /** The nullifier hex, as the source publishes it. Primary key. */
116
+ h: string;
117
+ /** Spend block timestamp (ms); null when the source carried none. */
118
+ ts: number | null;
119
+ /** Hash of the spending transaction; null when unresolvable. */
120
+ tx?: string | null;
121
+ }
122
+ /** Progress of the nullifier-set download, so a sync resumes instead of restarting. */
123
+ interface NullifierSyncMeta {
124
+ id: 'main';
125
+ /** Sealed chunks already stored. */
126
+ chunksDone: number;
127
+ /** Generation of the set the stored chunks belong to; a change invalidates them. */
128
+ generation: string;
129
+ totalStored: number;
130
+ updatedAt: number;
131
+ }
132
+ /** Config and note persistence — the minimum a wallet needs to hold funds. */
133
+ interface NoteStorage {
134
+ getConfig(): Promise<VaultConfigRecord | null>;
135
+ putConfig(config: VaultConfigRecord): Promise<void>;
136
+ /**
137
+ * Applies `mutate` to the stored config as ONE atomic read-modify-write.
138
+ *
139
+ * Part of the contract, not sugar over get + put. Two concurrent callers
140
+ * doing that separately both read the same `selfEphCounter`, so both derive
141
+ * the same ephemeral index and publish the same ephPk — a privacy leak, not
142
+ * a lost update. A backend that cannot make this atomic cannot host a vault.
143
+ *
144
+ * Resolves to null when no config exists; `mutate` is not called.
145
+ */
146
+ updateConfig(mutate: (config: VaultConfigRecord) => VaultConfigRecord): Promise<VaultConfigRecord | null>;
147
+ getAllNoteRecords(): Promise<EncryptedNoteRecord[]>;
148
+ putNote(record: EncryptedNoteRecord): Promise<void>;
149
+ putNotes(records: EncryptedNoteRecord[]): Promise<void>;
150
+ deleteNote(commitmentTag: string): Promise<void>;
151
+ deleteNotes(commitmentTags: string[]): Promise<void>;
152
+ clearNotes(): Promise<void>;
153
+ }
154
+ /**
155
+ * Local mirror of the spent-nullifier set.
156
+ *
157
+ * The whole set is downloaded and intersected locally, deliberately: asking a
158
+ * server whether one specific nullifier is spent would tell it which notes the
159
+ * wallet holds. That is why this is a cache to fill, not a lookup to call.
160
+ */
161
+ interface NullifierCache {
162
+ /**
163
+ * Stores one sealed chunk together with the sync progress it produced.
164
+ *
165
+ * Both must land in the same transaction: progress ahead of the data would
166
+ * make the next sync resume past chunks the cache never stored, leaving
167
+ * spent notes looking unspent.
168
+ */
169
+ putNullifierChunk(entries: CachedNullifier[], meta: NullifierSyncMeta): Promise<void>;
170
+ getNullifierSyncMeta(): Promise<NullifierSyncMeta | null>;
171
+ /**
172
+ * Which of `hexes` the cache holds. Absent keys are unspent as far as it
173
+ * knows.
174
+ *
175
+ * Matching is EXACT: a backend must not lowercase, trim or otherwise repair
176
+ * a key. Normalising happens once at ingestion, where the feed's rows are
177
+ * lowercased on the way into `putNullifierChunk` — so everything stored is
178
+ * already in one form, and a backend that "helpfully" normalised again
179
+ * would only hide a caller passing the wrong case.
180
+ *
181
+ * Both sides must agree, and the cost of disagreeing is not a missing row:
182
+ * a lookup that misses reports a SPENT note as unspent, the wallet offers
183
+ * it, and the spend dies on a duplicate nullifier.
184
+ */
185
+ getSpentNullifiers(hexes: string[]): Promise<Map<string, SpendDetails>>;
186
+ countNullifiers(): Promise<number>;
187
+ clearNullifierCache(): Promise<void>;
188
+ }
189
+ /** Encrypted local transaction history — outgoing sends the chain cannot reveal. */
190
+ interface TxHistoryStore {
191
+ addTxRecord(record: EncryptedTxRecord): Promise<void>;
192
+ getAllTxRecords(): Promise<EncryptedTxRecord[]>;
193
+ }
194
+ /** Everything a full wallet needs from persistence. */
195
+ interface VaultStorage extends NoteStorage, NullifierCache, TxHistoryStore {
196
+ /** Whether a vault already exists for this identity. */
197
+ hasVault(): Promise<boolean>;
198
+ }
199
+
200
+ /**
201
+ * The key that protects secrets at rest on one device.
202
+ *
203
+ * A single AES-GCM-256 key, generated once and kept for the life of the install.
204
+ * Where the platform allows it the key must be NON-EXTRACTABLE — created with
205
+ * `extractable: false` and persisted as a key handle rather than as bytes — so
206
+ * a storage dump, disk image or synced profile yields nothing usable.
207
+ *
208
+ * ## Why not derive it from the wallet signature
209
+ *
210
+ * Circular. The thing being protected is the cached identity, and the reason it
211
+ * is cached is to avoid asking for a signature again. A key derived from that
212
+ * signature would require the signature to read the cache that exists to avoid
213
+ * the signature.
214
+ *
215
+ * ## Platform notes
216
+ *
217
+ * Browsers and extensions: `@orbinum/sdk/storage/indexeddb` exports
218
+ * `getOrCreateIndexedDbDeviceKey`, which stores a non-extractable `CryptoKey`
219
+ * via structured clone — the key material never becomes visible to JavaScript.
220
+ *
221
+ * React Native and Node: no non-extractable handle survives a restart, so store
222
+ * raw key bytes in the platform's secure enclave (Keychain, Keystore) and import
223
+ * them with `importDeviceKey`. The isolation then comes from the enclave rather
224
+ * than from the key being unexportable.
225
+ */
226
+ /**
227
+ * A fresh AES-GCM-256 device key.
228
+ *
229
+ * `extractable` defaults to false and should stay that way. Passing true is for
230
+ * the platforms described above — the ones that cannot persist a key handle and
231
+ * must export raw bytes into a secure enclave. Anywhere a handle survives a
232
+ * restart, an extractable key is strictly worse: the material becomes readable
233
+ * by any code in the context.
234
+ */
235
+ declare function generateDeviceKey(extractable?: boolean): Promise<CryptoKey>;
236
+ /**
237
+ * Imports raw device-key bytes, for platforms that persist bytes in a secure
238
+ * enclave rather than a key handle. The imported key is non-extractable, so the
239
+ * bytes cannot be read back out through WebCrypto.
240
+ */
241
+ declare function importDeviceKey(raw: Uint8Array): Promise<CryptoKey>;
242
+ /**
243
+ * Where a device key is persisted between launches.
244
+ *
245
+ * `load` returning null means "no key yet", not "failed" — the provider takes
246
+ * that as permission to generate one. An adapter that cannot reach its backend
247
+ * should throw instead, or the first transient failure silently mints a second
248
+ * key and orphans every secret encrypted under the first.
249
+ */
250
+ interface DeviceKeyStore {
251
+ load(): Promise<CryptoKey | null>;
252
+ save(key: CryptoKey): Promise<void>;
253
+ }
254
+ /**
255
+ * The device key, generating and persisting it on first use.
256
+ *
257
+ * Cached in memory per process: generating a second key would silently orphan
258
+ * every secret encrypted under the first.
259
+ */
260
+ declare function createDeviceKeyProvider(store: DeviceKeyStore): () => Promise<CryptoKey>;
261
+
262
+ /**
263
+ * Where a host keeps small secrets between sessions.
264
+ *
265
+ * Three methods, because that is all the identity cache needs and every platform
266
+ * has them: `localStorage` in a page, `chrome.storage.local` in an extension,
267
+ * Keychain or SharedPreferences on mobile, a file on a server. Keeping the
268
+ * surface this small is what makes those adapters a few lines each.
269
+ *
270
+ * Values handed to `set` are already ENCRYPTED — see `sessionCache`. A store is
271
+ * not trusted with plaintext secrets, so a backend with weak isolation is still
272
+ * a safe place to put one.
273
+ */
274
+ interface SecretStore {
275
+ get(key: string): Promise<string | null>;
276
+ set(key: string, value: string): Promise<void>;
277
+ remove(key: string): Promise<void>;
278
+ /**
279
+ * Keys currently held. Needed to clear every network's cache for one account
280
+ * on disconnect, which cannot be done by constructing keys — the caller does
281
+ * not know which chains the user has visited.
282
+ */
283
+ keys(): Promise<string[]>;
284
+ }
285
+ /**
286
+ * An in-memory `SecretStore`. Loses everything on restart, which is the whole
287
+ * point of the real ones — useful for tests and for a host that deliberately
288
+ * wants re-signing on every launch.
289
+ */
290
+ declare function createMemorySecretStore(): SecretStore;
291
+
292
+ export { type CachedNullifier as C, type DeviceKeyStore as D, type EncryptedNoteRecord as E, type NullifierSyncMeta as N, type SpendDetails as S, type TxHistoryStore as T, type VaultStorage as V, type VaultConfigRecord as a, type EncryptedTxRecord as b, type SecretStore as c, type NoteStorage as d, type NoteStatusUpdate as e, type NullifierCache as f, createDeviceKeyProvider as g, createMemorySecretStore as h, generateDeviceKey as i, importDeviceKey as j };
@@ -0,0 +1 @@
1
+ export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-JYVjYJtf.mjs';
@@ -0,0 +1 @@
1
+ export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-JYVjYJtf.js';