@orbinum/sdk 1.3.1 → 1.4.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/dist/index.d.mts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +49 -19
- package/dist/index.mjs +49 -19
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -240,8 +240,27 @@ declare function reserveSelfEphIndex(storage: NoteStorage): Promise<number>;
|
|
|
240
240
|
* viewing public key. Registering the counterparty is a side effect worth
|
|
241
241
|
* having: it makes the REVERSE direction cheap too, since their future payments
|
|
242
242
|
* to this wallet become hash lookups instead of one trial ECDH per note.
|
|
243
|
+
*
|
|
244
|
+
* Returns `null` when this vault holds no history for that counterparty, and
|
|
245
|
+
* the caller must then use a random ephemeral. The reason is that "no history"
|
|
246
|
+
* has two causes this function cannot tell apart:
|
|
247
|
+
*
|
|
248
|
+
* - a genuine first payment, where index 0 is correct;
|
|
249
|
+
* - a counter that was LOST — a restored seed, a cleared IndexedDB, a wipe
|
|
250
|
+
* and rescan — where index 0 was already published and re-deriving it
|
|
251
|
+
* republishes that ephPk, linking the two notes in public.
|
|
252
|
+
*
|
|
253
|
+
* Unlike `selfEphCounter`, this one cannot be recovered: the index was
|
|
254
|
+
* published on a note encrypted toward someone else, so it never appears in
|
|
255
|
+
* this wallet's own scan, and asking a server whether a given ephPk exists
|
|
256
|
+
* would reveal which notes are ours. So the ambiguity is resolved the safe way
|
|
257
|
+
* — the caller degrades to random, costing the recipient one trial scan, and
|
|
258
|
+
* every later payment to the same counterparty takes the fast path again.
|
|
259
|
+
*
|
|
260
|
+
* The entry is still created: the NEXT payment has a counter, and registering
|
|
261
|
+
* the counterparty is what makes the reverse direction cheap.
|
|
243
262
|
*/
|
|
244
|
-
declare function reservePairwiseIndex(storage: NoteStorage, ivkHex: string): Promise<number>;
|
|
263
|
+
declare function reservePairwiseIndex(storage: NoteStorage, ivkHex: string): Promise<number | null>;
|
|
245
264
|
|
|
246
265
|
/**
|
|
247
266
|
* Turning a note into a stored record and back.
|
|
@@ -4495,7 +4514,13 @@ declare function resolveSelfEphCeiling(params: {
|
|
|
4495
4514
|
/**
|
|
4496
4515
|
* Discovery window size for the next scan, given the persisted counter: at least
|
|
4497
4516
|
* the default, rounded up so every index the wallet may already have used (plus
|
|
4498
|
-
* the gap margin) falls inside the fast path
|
|
4517
|
+
* the gap margin) falls inside the fast path, and never past `MAX_EPH_WINDOW`.
|
|
4518
|
+
*
|
|
4519
|
+
* The counter is read from a config that survives restores and hand-editing, so
|
|
4520
|
+
* a non-finite value is treated as "no history" rather than propagated: `NaN`
|
|
4521
|
+
* would make the builder's `i < from + count` false immediately and return an
|
|
4522
|
+
* EMPTY window, silently disabling the fast path, while `Infinity` would make
|
|
4523
|
+
* that same loop never terminate.
|
|
4499
4524
|
*/
|
|
4500
4525
|
declare function windowSizeForCounter(counter: number): number;
|
|
4501
4526
|
|
package/dist/index.d.ts
CHANGED
|
@@ -240,8 +240,27 @@ declare function reserveSelfEphIndex(storage: NoteStorage): Promise<number>;
|
|
|
240
240
|
* viewing public key. Registering the counterparty is a side effect worth
|
|
241
241
|
* having: it makes the REVERSE direction cheap too, since their future payments
|
|
242
242
|
* to this wallet become hash lookups instead of one trial ECDH per note.
|
|
243
|
+
*
|
|
244
|
+
* Returns `null` when this vault holds no history for that counterparty, and
|
|
245
|
+
* the caller must then use a random ephemeral. The reason is that "no history"
|
|
246
|
+
* has two causes this function cannot tell apart:
|
|
247
|
+
*
|
|
248
|
+
* - a genuine first payment, where index 0 is correct;
|
|
249
|
+
* - a counter that was LOST — a restored seed, a cleared IndexedDB, a wipe
|
|
250
|
+
* and rescan — where index 0 was already published and re-deriving it
|
|
251
|
+
* republishes that ephPk, linking the two notes in public.
|
|
252
|
+
*
|
|
253
|
+
* Unlike `selfEphCounter`, this one cannot be recovered: the index was
|
|
254
|
+
* published on a note encrypted toward someone else, so it never appears in
|
|
255
|
+
* this wallet's own scan, and asking a server whether a given ephPk exists
|
|
256
|
+
* would reveal which notes are ours. So the ambiguity is resolved the safe way
|
|
257
|
+
* — the caller degrades to random, costing the recipient one trial scan, and
|
|
258
|
+
* every later payment to the same counterparty takes the fast path again.
|
|
259
|
+
*
|
|
260
|
+
* The entry is still created: the NEXT payment has a counter, and registering
|
|
261
|
+
* the counterparty is what makes the reverse direction cheap.
|
|
243
262
|
*/
|
|
244
|
-
declare function reservePairwiseIndex(storage: NoteStorage, ivkHex: string): Promise<number>;
|
|
263
|
+
declare function reservePairwiseIndex(storage: NoteStorage, ivkHex: string): Promise<number | null>;
|
|
245
264
|
|
|
246
265
|
/**
|
|
247
266
|
* Turning a note into a stored record and back.
|
|
@@ -4495,7 +4514,13 @@ declare function resolveSelfEphCeiling(params: {
|
|
|
4495
4514
|
/**
|
|
4496
4515
|
* Discovery window size for the next scan, given the persisted counter: at least
|
|
4497
4516
|
* the default, rounded up so every index the wallet may already have used (plus
|
|
4498
|
-
* the gap margin) falls inside the fast path
|
|
4517
|
+
* the gap margin) falls inside the fast path, and never past `MAX_EPH_WINDOW`.
|
|
4518
|
+
*
|
|
4519
|
+
* The counter is read from a config that survives restores and hand-editing, so
|
|
4520
|
+
* a non-finite value is treated as "no history" rather than propagated: `NaN`
|
|
4521
|
+
* would make the builder's `i < from + count` false immediately and return an
|
|
4522
|
+
* EMPTY window, silently disabling the fast path, while `Infinity` would make
|
|
4523
|
+
* that same loop never terminate.
|
|
4499
4524
|
*/
|
|
4500
4525
|
declare function windowSizeForCounter(counter: number): number;
|
|
4501
4526
|
|
package/dist/index.js
CHANGED
|
@@ -5414,13 +5414,29 @@ function mergeCounters(current, fallback) {
|
|
|
5414
5414
|
...parties !== void 0 ? { pairwiseCounterparties: parties } : {}
|
|
5415
5415
|
};
|
|
5416
5416
|
}
|
|
5417
|
-
var
|
|
5417
|
+
var counterOrNone = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5418
|
+
var higher = (a, b) => {
|
|
5419
|
+
const x = counterOrNone(a);
|
|
5420
|
+
const y = counterOrNone(b);
|
|
5421
|
+
return x === void 0 ? y : y === void 0 ? x : Math.max(x, y);
|
|
5422
|
+
};
|
|
5423
|
+
function usableEntry(entry) {
|
|
5424
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
5425
|
+
const { nextIndex, addedAt } = entry;
|
|
5426
|
+
const index = counterOrNone(nextIndex);
|
|
5427
|
+
if (index === void 0) return void 0;
|
|
5428
|
+
return { nextIndex: index, addedAt: counterOrNone(addedAt) ?? Date.now() };
|
|
5429
|
+
}
|
|
5418
5430
|
function mergePairwise(current, fallback) {
|
|
5419
5431
|
if (!current && !fallback) return void 0;
|
|
5420
|
-
const merged = {
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5432
|
+
const merged = {};
|
|
5433
|
+
for (const [key, entry] of Object.entries(fallback ?? {})) {
|
|
5434
|
+
const usable = usableEntry(entry);
|
|
5435
|
+
if (usable) merged[key] = usable;
|
|
5436
|
+
}
|
|
5437
|
+
for (const [key, raw] of Object.entries(current ?? {})) {
|
|
5438
|
+
const entry = usableEntry(raw);
|
|
5439
|
+
if (!entry) continue;
|
|
5424
5440
|
const prev = merged[key];
|
|
5425
5441
|
merged[key] = prev ? {
|
|
5426
5442
|
nextIndex: Math.max(prev.nextIndex, entry.nextIndex),
|
|
@@ -5431,33 +5447,42 @@ function mergePairwise(current, fallback) {
|
|
|
5431
5447
|
}
|
|
5432
5448
|
|
|
5433
5449
|
// src/wallet/vault/storage/ephemeralIndex.ts
|
|
5450
|
+
var MAX_EPH_INDEX = 4294967294;
|
|
5451
|
+
function usableCounter(value) {
|
|
5452
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return null;
|
|
5453
|
+
if (value < 0 || value > MAX_EPH_INDEX) return null;
|
|
5454
|
+
return value;
|
|
5455
|
+
}
|
|
5434
5456
|
async function reserveSelfEphIndex(storage) {
|
|
5435
5457
|
const updated = await storage.updateConfig((config) => ({
|
|
5436
5458
|
...config,
|
|
5437
|
-
selfEphCounter: (config.selfEphCounter ?? 0) + 1,
|
|
5459
|
+
selfEphCounter: (usableCounter(config.selfEphCounter) ?? 0) + 1,
|
|
5438
5460
|
updatedAt: Date.now()
|
|
5439
5461
|
}));
|
|
5440
5462
|
if (!updated) throw new Error("vault not initialized");
|
|
5441
|
-
return (updated.selfEphCounter ?? 1) - 1;
|
|
5463
|
+
return (usableCounter(updated.selfEphCounter) ?? 1) - 1;
|
|
5442
5464
|
}
|
|
5443
5465
|
async function reservePairwiseIndex(storage, ivkHex) {
|
|
5444
5466
|
const key = ivkHex.toLowerCase();
|
|
5445
5467
|
const updated = await storage.updateConfig((config) => {
|
|
5446
|
-
const
|
|
5468
|
+
const stored = usableCounter(config.pairwiseCounterparties?.[key]?.nextIndex);
|
|
5469
|
+
const addedAt = config.pairwiseCounterparties?.[key]?.addedAt;
|
|
5447
5470
|
return {
|
|
5448
5471
|
...config,
|
|
5449
5472
|
pairwiseCounterparties: {
|
|
5450
5473
|
...config.pairwiseCounterparties,
|
|
5451
5474
|
[key]: {
|
|
5452
|
-
nextIndex: (
|
|
5453
|
-
addedAt:
|
|
5475
|
+
nextIndex: (stored ?? 0) + 1,
|
|
5476
|
+
addedAt: typeof addedAt === "number" ? addedAt : Date.now()
|
|
5454
5477
|
}
|
|
5455
5478
|
},
|
|
5456
5479
|
updatedAt: Date.now()
|
|
5457
5480
|
};
|
|
5458
5481
|
});
|
|
5459
5482
|
if (!updated) throw new Error("vault not initialized");
|
|
5460
|
-
|
|
5483
|
+
const nextIndex = usableCounter(updated.pairwiseCounterparties?.[key]?.nextIndex);
|
|
5484
|
+
if (nextIndex === null || nextIndex <= 1) return null;
|
|
5485
|
+
return nextIndex - 1;
|
|
5461
5486
|
}
|
|
5462
5487
|
|
|
5463
5488
|
// src/wallet/vault/notes/record.ts
|
|
@@ -6235,7 +6260,7 @@ function gapMargin(windowSize) {
|
|
|
6235
6260
|
}
|
|
6236
6261
|
function resolveSelfEphCeiling(params) {
|
|
6237
6262
|
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
6238
|
-
const windowSize = params.windowSize
|
|
6263
|
+
const windowSize = params.windowSize !== void 0 && params.windowSize > 0 ? params.windowSize : SELF_EPH_WINDOW;
|
|
6239
6264
|
if (scanMaxIndex === null) return null;
|
|
6240
6265
|
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
6241
6266
|
const ownEphPks = /* @__PURE__ */ new Set();
|
|
@@ -6260,9 +6285,12 @@ function resolveSelfEphCeiling(params) {
|
|
|
6260
6285
|
}
|
|
6261
6286
|
return max;
|
|
6262
6287
|
}
|
|
6288
|
+
var MAX_EPH_WINDOW = SELF_EPH_WINDOW * 64;
|
|
6263
6289
|
function windowSizeForCounter(counter) {
|
|
6264
|
-
const
|
|
6265
|
-
|
|
6290
|
+
const used = Number.isFinite(counter) ? counter : 0;
|
|
6291
|
+
const needed = used + gapMargin(SELF_EPH_WINDOW);
|
|
6292
|
+
const rounded = Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW;
|
|
6293
|
+
return Math.min(MAX_EPH_WINDOW, Math.max(SELF_EPH_WINDOW, rounded));
|
|
6266
6294
|
}
|
|
6267
6295
|
|
|
6268
6296
|
// src/wallet/scanner/phases/spentStatus.ts
|
|
@@ -6520,11 +6548,13 @@ async function buildZkNote(params, deps) {
|
|
|
6520
6548
|
} else if (params.viewingPublicKey && storage && keys.viewingSecretKey) {
|
|
6521
6549
|
try {
|
|
6522
6550
|
const index = await reservePairwiseIndex(storage, toHex(params.viewingPublicKey));
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6551
|
+
if (index !== null) {
|
|
6552
|
+
const pairSecret = derivePairwiseSharedSecret(
|
|
6553
|
+
keys.viewingSecretKey,
|
|
6554
|
+
params.viewingPublicKey
|
|
6555
|
+
);
|
|
6556
|
+
ephSkOverride = derivePairwiseEphSk(pairSecret, index);
|
|
6557
|
+
}
|
|
6528
6558
|
} catch {
|
|
6529
6559
|
}
|
|
6530
6560
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -4188,13 +4188,29 @@ function mergeCounters(current, fallback) {
|
|
|
4188
4188
|
...parties !== void 0 ? { pairwiseCounterparties: parties } : {}
|
|
4189
4189
|
};
|
|
4190
4190
|
}
|
|
4191
|
-
var
|
|
4191
|
+
var counterOrNone = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
4192
|
+
var higher = (a, b) => {
|
|
4193
|
+
const x = counterOrNone(a);
|
|
4194
|
+
const y = counterOrNone(b);
|
|
4195
|
+
return x === void 0 ? y : y === void 0 ? x : Math.max(x, y);
|
|
4196
|
+
};
|
|
4197
|
+
function usableEntry(entry) {
|
|
4198
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
4199
|
+
const { nextIndex, addedAt } = entry;
|
|
4200
|
+
const index = counterOrNone(nextIndex);
|
|
4201
|
+
if (index === void 0) return void 0;
|
|
4202
|
+
return { nextIndex: index, addedAt: counterOrNone(addedAt) ?? Date.now() };
|
|
4203
|
+
}
|
|
4192
4204
|
function mergePairwise(current, fallback) {
|
|
4193
4205
|
if (!current && !fallback) return void 0;
|
|
4194
|
-
const merged = {
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4206
|
+
const merged = {};
|
|
4207
|
+
for (const [key, entry] of Object.entries(fallback ?? {})) {
|
|
4208
|
+
const usable = usableEntry(entry);
|
|
4209
|
+
if (usable) merged[key] = usable;
|
|
4210
|
+
}
|
|
4211
|
+
for (const [key, raw] of Object.entries(current ?? {})) {
|
|
4212
|
+
const entry = usableEntry(raw);
|
|
4213
|
+
if (!entry) continue;
|
|
4198
4214
|
const prev = merged[key];
|
|
4199
4215
|
merged[key] = prev ? {
|
|
4200
4216
|
nextIndex: Math.max(prev.nextIndex, entry.nextIndex),
|
|
@@ -4205,33 +4221,42 @@ function mergePairwise(current, fallback) {
|
|
|
4205
4221
|
}
|
|
4206
4222
|
|
|
4207
4223
|
// src/wallet/vault/storage/ephemeralIndex.ts
|
|
4224
|
+
var MAX_EPH_INDEX = 4294967294;
|
|
4225
|
+
function usableCounter(value) {
|
|
4226
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return null;
|
|
4227
|
+
if (value < 0 || value > MAX_EPH_INDEX) return null;
|
|
4228
|
+
return value;
|
|
4229
|
+
}
|
|
4208
4230
|
async function reserveSelfEphIndex(storage) {
|
|
4209
4231
|
const updated = await storage.updateConfig((config) => ({
|
|
4210
4232
|
...config,
|
|
4211
|
-
selfEphCounter: (config.selfEphCounter ?? 0) + 1,
|
|
4233
|
+
selfEphCounter: (usableCounter(config.selfEphCounter) ?? 0) + 1,
|
|
4212
4234
|
updatedAt: Date.now()
|
|
4213
4235
|
}));
|
|
4214
4236
|
if (!updated) throw new Error("vault not initialized");
|
|
4215
|
-
return (updated.selfEphCounter ?? 1) - 1;
|
|
4237
|
+
return (usableCounter(updated.selfEphCounter) ?? 1) - 1;
|
|
4216
4238
|
}
|
|
4217
4239
|
async function reservePairwiseIndex(storage, ivkHex) {
|
|
4218
4240
|
const key = ivkHex.toLowerCase();
|
|
4219
4241
|
const updated = await storage.updateConfig((config) => {
|
|
4220
|
-
const
|
|
4242
|
+
const stored = usableCounter(config.pairwiseCounterparties?.[key]?.nextIndex);
|
|
4243
|
+
const addedAt = config.pairwiseCounterparties?.[key]?.addedAt;
|
|
4221
4244
|
return {
|
|
4222
4245
|
...config,
|
|
4223
4246
|
pairwiseCounterparties: {
|
|
4224
4247
|
...config.pairwiseCounterparties,
|
|
4225
4248
|
[key]: {
|
|
4226
|
-
nextIndex: (
|
|
4227
|
-
addedAt:
|
|
4249
|
+
nextIndex: (stored ?? 0) + 1,
|
|
4250
|
+
addedAt: typeof addedAt === "number" ? addedAt : Date.now()
|
|
4228
4251
|
}
|
|
4229
4252
|
},
|
|
4230
4253
|
updatedAt: Date.now()
|
|
4231
4254
|
};
|
|
4232
4255
|
});
|
|
4233
4256
|
if (!updated) throw new Error("vault not initialized");
|
|
4234
|
-
|
|
4257
|
+
const nextIndex = usableCounter(updated.pairwiseCounterparties?.[key]?.nextIndex);
|
|
4258
|
+
if (nextIndex === null || nextIndex <= 1) return null;
|
|
4259
|
+
return nextIndex - 1;
|
|
4235
4260
|
}
|
|
4236
4261
|
|
|
4237
4262
|
// src/wallet/vault/notes/record.ts
|
|
@@ -4998,7 +5023,7 @@ function gapMargin(windowSize) {
|
|
|
4998
5023
|
}
|
|
4999
5024
|
function resolveSelfEphCeiling(params) {
|
|
5000
5025
|
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
5001
|
-
const windowSize = params.windowSize
|
|
5026
|
+
const windowSize = params.windowSize !== void 0 && params.windowSize > 0 ? params.windowSize : SELF_EPH_WINDOW;
|
|
5002
5027
|
if (scanMaxIndex === null) return null;
|
|
5003
5028
|
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
5004
5029
|
const ownEphPks = /* @__PURE__ */ new Set();
|
|
@@ -5023,9 +5048,12 @@ function resolveSelfEphCeiling(params) {
|
|
|
5023
5048
|
}
|
|
5024
5049
|
return max;
|
|
5025
5050
|
}
|
|
5051
|
+
var MAX_EPH_WINDOW = SELF_EPH_WINDOW * 64;
|
|
5026
5052
|
function windowSizeForCounter(counter) {
|
|
5027
|
-
const
|
|
5028
|
-
|
|
5053
|
+
const used = Number.isFinite(counter) ? counter : 0;
|
|
5054
|
+
const needed = used + gapMargin(SELF_EPH_WINDOW);
|
|
5055
|
+
const rounded = Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW;
|
|
5056
|
+
return Math.min(MAX_EPH_WINDOW, Math.max(SELF_EPH_WINDOW, rounded));
|
|
5029
5057
|
}
|
|
5030
5058
|
|
|
5031
5059
|
// src/wallet/scanner/phases/spentStatus.ts
|
|
@@ -5283,11 +5311,13 @@ async function buildZkNote(params, deps) {
|
|
|
5283
5311
|
} else if (params.viewingPublicKey && storage && keys.viewingSecretKey) {
|
|
5284
5312
|
try {
|
|
5285
5313
|
const index = await reservePairwiseIndex(storage, toHex(params.viewingPublicKey));
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5314
|
+
if (index !== null) {
|
|
5315
|
+
const pairSecret = derivePairwiseSharedSecret(
|
|
5316
|
+
keys.viewingSecretKey,
|
|
5317
|
+
params.viewingPublicKey
|
|
5318
|
+
);
|
|
5319
|
+
ephSkOverride = derivePairwiseEphSk(pairSecret, index);
|
|
5320
|
+
}
|
|
5291
5321
|
} catch {
|
|
5292
5322
|
}
|
|
5293
5323
|
}
|