@totemsdk/wots-lease 1.0.1 → 1.0.2

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 (55) hide show
  1. package/LICENSE +202 -21
  2. package/NOTICE +60 -0
  3. package/dist/axia.js +21 -25
  4. package/dist/certificate.js +7 -12
  5. package/dist/cjs/axia.d.ts +33 -0
  6. package/dist/cjs/axia.js +145 -0
  7. package/dist/cjs/certificate.d.ts +37 -0
  8. package/dist/cjs/certificate.js +74 -0
  9. package/dist/cjs/device.d.ts +18 -0
  10. package/dist/cjs/device.js +34 -0
  11. package/dist/cjs/errors.d.ts +71 -0
  12. package/dist/cjs/errors.js +135 -0
  13. package/dist/cjs/hybrid.d.ts +37 -0
  14. package/dist/cjs/hybrid.js +145 -0
  15. package/dist/cjs/index.d.ts +17 -0
  16. package/dist/cjs/index.js +45 -0
  17. package/dist/cjs/journal.d.ts +30 -0
  18. package/dist/cjs/journal.js +369 -0
  19. package/dist/cjs/local.d.ts +83 -0
  20. package/dist/cjs/local.js +440 -0
  21. package/dist/cjs/onchain.d.ts +54 -0
  22. package/dist/cjs/onchain.js +314 -0
  23. package/dist/cjs/package.json +1 -0
  24. package/dist/cjs/quorum.d.ts +75 -0
  25. package/dist/cjs/quorum.js +307 -0
  26. package/dist/cjs/replicated-backup.d.ts +30 -0
  27. package/dist/cjs/replicated-backup.js +52 -0
  28. package/dist/cjs/seed.d.ts +41 -0
  29. package/dist/cjs/seed.js +84 -0
  30. package/dist/cjs/stubs.d.ts +50 -0
  31. package/dist/cjs/stubs.js +125 -0
  32. package/dist/cjs/types.d.ts +272 -0
  33. package/dist/cjs/types.js +5 -0
  34. package/dist/cjs/watermark.d.ts +51 -0
  35. package/dist/cjs/watermark.js +328 -0
  36. package/dist/device.js +2 -6
  37. package/dist/errors.js +14 -31
  38. package/dist/hybrid.js +4 -8
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +12 -43
  41. package/dist/journal.js +35 -38
  42. package/dist/local.js +29 -33
  43. package/dist/onchain.js +23 -27
  44. package/dist/quorum.js +9 -13
  45. package/dist/replicated-backup.d.ts +30 -0
  46. package/dist/replicated-backup.js +48 -0
  47. package/dist/seed.js +7 -11
  48. package/dist/stubs.js +5 -11
  49. package/dist/types.js +1 -2
  50. package/dist/watermark.js +11 -18
  51. package/package.json +22 -13
  52. package/rust/pkg/package.json +2 -2
  53. package/rust/pkg/wots_lease_wasm_bg.wasm +0 -0
  54. package/rust/pkg-node/package.json +2 -2
  55. package/rust/pkg-node/wots_lease_wasm_bg.wasm +0 -0
package/dist/journal.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * LeaseJournal — physically append-only audit log for WOTS key-use events.
4
3
  *
@@ -6,11 +5,10 @@
6
5
  * key is retained as migration input/forensic history but is never rewritten.
7
6
  * The SHA-256 chain format is intentionally unchanged (RFC-007 OQ3).
8
7
  */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.LeaseJournal = void 0;
11
- const crypto_1 = require("crypto");
12
- const core_1 = require("@totemsdk/core");
13
- const errors_1 = require("@totemsdk/storage/errors");
8
+ import { sha256 } from '@noble/hashes/sha256.js';
9
+ import { bytesToHex } from '@noble/hashes/utils.js';
10
+ import { NoopLogger } from '@totemsdk/core';
11
+ import { StorageError } from '@totemsdk/storage/errors';
14
12
  const LEGACY_STORAGE_KEY = 'totem_wots_journal';
15
13
  const ENTRY_PREFIX = 'totem_wots_journal:v1:entry:';
16
14
  const RECORD_VERSION = 1;
@@ -45,7 +43,7 @@ function hashEntry(entry) {
45
43
  d: entry.deviceId,
46
44
  };
47
45
  const bytes = encoder.encode(JSON.stringify(data));
48
- return (0, crypto_1.createHash)('sha256').update(bytes).digest('hex');
46
+ return bytesToHex(sha256(bytes));
49
47
  }
50
48
  function cloneEntry(entry) {
51
49
  return {
@@ -65,14 +63,14 @@ function cloneEntry(entry) {
65
63
  }
66
64
  function asObject(value, key) {
67
65
  if (value === null || typeof value !== 'object' || Array.isArray(value)) {
68
- throw new errors_1.StorageError(`LeaseJournal: invalid object at ${key}`, 'corrupt', { key });
66
+ throw new StorageError(`LeaseJournal: invalid object at ${key}`, 'corrupt', { key });
69
67
  }
70
68
  return value;
71
69
  }
72
70
  function requiredString(object, field, key) {
73
71
  const value = object[field];
74
72
  if (typeof value !== 'string' || value.length === 0) {
75
- throw new errors_1.StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
73
+ throw new StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
76
74
  }
77
75
  return value;
78
76
  }
@@ -81,14 +79,14 @@ function optionalString(object, field, key) {
81
79
  if (value === undefined)
82
80
  return undefined;
83
81
  if (typeof value !== 'string') {
84
- throw new errors_1.StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
82
+ throw new StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
85
83
  }
86
84
  return value;
87
85
  }
88
86
  function requiredInteger(object, field, key) {
89
87
  const value = object[field];
90
88
  if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
91
- throw new errors_1.StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
89
+ throw new StorageError(`LeaseJournal: invalid ${field} at ${key}`, 'corrupt', { key });
92
90
  }
93
91
  return value;
94
92
  }
@@ -97,11 +95,11 @@ function validateEntry(value, key) {
97
95
  const indices = asObject(object.indices, `${key}.indices`);
98
96
  const status = requiredString(object, 'status', key);
99
97
  if (!STATUSES.has(status)) {
100
- throw new errors_1.StorageError(`LeaseJournal: invalid status at ${key}`, 'corrupt', { key });
98
+ throw new StorageError(`LeaseJournal: invalid status at ${key}`, 'corrupt', { key });
101
99
  }
102
100
  const timestamp = object.timestamp;
103
101
  if (typeof timestamp !== 'number' || !Number.isFinite(timestamp) || timestamp < 0) {
104
- throw new errors_1.StorageError(`LeaseJournal: invalid timestamp at ${key}`, 'corrupt', { key });
102
+ throw new StorageError(`LeaseJournal: invalid timestamp at ${key}`, 'corrupt', { key });
105
103
  }
106
104
  const validated = cloneEntry({
107
105
  treeId: requiredString(object, 'treeId', key),
@@ -123,11 +121,11 @@ function validateEntry(value, key) {
123
121
  });
124
122
  const { addressIndex, l1, l2 } = validated.indices;
125
123
  if (addressIndex > MAX_INDEX_COMPONENT || l1 > MAX_INDEX_COMPONENT || l2 > MAX_INDEX_COMPONENT) {
126
- throw new errors_1.StorageError(`LeaseJournal: indices out of range at ${key}`, 'corrupt', { key });
124
+ throw new StorageError(`LeaseJournal: indices out of range at ${key}`, 'corrupt', { key });
127
125
  }
128
126
  const expectedIndex = addressIndex * 64 * 64 + l1 * 64 + l2;
129
127
  if (validated.wotsIndex !== expectedIndex) {
130
- throw new errors_1.StorageError(`LeaseJournal: wotsIndex/indices mismatch at ${key}`, 'corrupt', { key });
128
+ throw new StorageError(`LeaseJournal: wotsIndex/indices mismatch at ${key}`, 'corrupt', { key });
131
129
  }
132
130
  return validated;
133
131
  }
@@ -135,19 +133,19 @@ function verifyChain(entries, source) {
135
133
  for (let i = 0; i < entries.length; i += 1) {
136
134
  const entry = entries[i];
137
135
  if (entry.hash === undefined || !HASH_PATTERN.test(entry.hash)) {
138
- throw new errors_1.StorageError(`LeaseJournal: invalid hash at ${source}[${i}]`, 'corrupt');
136
+ throw new StorageError(`LeaseJournal: invalid hash at ${source}[${i}]`, 'corrupt');
139
137
  }
140
138
  const expectedHash = hashEntry(entry);
141
139
  if (entry.hash !== expectedHash) {
142
- throw new errors_1.StorageError(`LeaseJournal: hash mismatch at ${source}[${i}]`, 'corrupt');
140
+ throw new StorageError(`LeaseJournal: hash mismatch at ${source}[${i}]`, 'corrupt');
143
141
  }
144
142
  if (i === 0) {
145
143
  if (entry.previousHash !== undefined) {
146
- throw new errors_1.StorageError(`LeaseJournal: first entry has previousHash at ${source}[0]`, 'corrupt');
144
+ throw new StorageError(`LeaseJournal: first entry has previousHash at ${source}[0]`, 'corrupt');
147
145
  }
148
146
  }
149
147
  else if (entry.previousHash !== entries[i - 1].hash) {
150
- throw new errors_1.StorageError(`LeaseJournal: previousHash mismatch at ${source}[${i}]`, 'corrupt');
148
+ throw new StorageError(`LeaseJournal: previousHash mismatch at ${source}[${i}]`, 'corrupt');
151
149
  }
152
150
  }
153
151
  }
@@ -177,16 +175,16 @@ function parseRecordKey(key) {
177
175
  const relative = key.slice(ENTRY_PREFIX.length);
178
176
  const match = KEY_PATTERN.exec(relative);
179
177
  if (!match) {
180
- throw new errors_1.StorageError(`LeaseJournal: malformed record key ${key}`, 'corrupt', { key });
178
+ throw new StorageError(`LeaseJournal: malformed record key ${key}`, 'corrupt', { key });
181
179
  }
182
180
  const sequence = Number(match[1]);
183
181
  if (!Number.isSafeInteger(sequence)) {
184
- throw new errors_1.StorageError(`LeaseJournal: invalid sequence in ${key}`, 'corrupt', { key });
182
+ throw new StorageError(`LeaseJournal: invalid sequence in ${key}`, 'corrupt', { key });
185
183
  }
186
184
  return { key, sequence, hash: match[2] };
187
185
  }
188
- class LeaseJournal {
189
- constructor(storage, logger = new core_1.NoopLogger()) {
186
+ export class LeaseJournal {
187
+ constructor(storage, logger = new NoopLogger()) {
190
188
  this.storage = storage;
191
189
  this.logger = logger;
192
190
  this.entries = [];
@@ -212,7 +210,7 @@ class LeaseJournal {
212
210
  const commonLength = Math.min(physical.length, legacy.length);
213
211
  for (let i = 0; i < commonLength; i += 1) {
214
212
  if (!entriesEqual(physical[i], legacy[i])) {
215
- throw new errors_1.StorageError(`LeaseJournal: legacy/v1 divergence at sequence ${i}`, 'corrupt');
213
+ throw new StorageError(`LeaseJournal: legacy/v1 divergence at sequence ${i}`, 'corrupt');
216
214
  }
217
215
  }
218
216
  for (let i = physical.length; i < legacy.length; i += 1) {
@@ -230,7 +228,7 @@ class LeaseJournal {
230
228
  if (raw === null)
231
229
  return null;
232
230
  if (!Array.isArray(raw)) {
233
- throw new errors_1.StorageError('LeaseJournal: legacy journal is not an array', 'corrupt', { key: LEGACY_STORAGE_KEY });
231
+ throw new StorageError('LeaseJournal: legacy journal is not an array', 'corrupt', { key: LEGACY_STORAGE_KEY });
234
232
  }
235
233
  const entries = raw.map((entry, index) => validateEntry(entry, `${LEGACY_STORAGE_KEY}[${index}]`));
236
234
  verifyChain(entries, LEGACY_STORAGE_KEY);
@@ -243,33 +241,33 @@ class LeaseJournal {
243
241
  .sort((a, b) => a.sequence - b.sequence || a.key.localeCompare(b.key));
244
242
  for (let i = 0; i < parsed.length; i += 1) {
245
243
  if (i > 0 && parsed[i - 1].sequence === parsed[i].sequence) {
246
- throw new errors_1.StorageError(`LeaseJournal: fork at sequence ${parsed[i].sequence}`, 'corrupt');
244
+ throw new StorageError(`LeaseJournal: fork at sequence ${parsed[i].sequence}`, 'corrupt');
247
245
  }
248
246
  if (parsed[i].sequence !== i) {
249
- throw new errors_1.StorageError(`LeaseJournal: sequence gap at ${i}`, 'corrupt');
247
+ throw new StorageError(`LeaseJournal: sequence gap at ${i}`, 'corrupt');
250
248
  }
251
249
  }
252
250
  const entries = [];
253
251
  for (const descriptor of parsed) {
254
252
  const raw = await this.storage.get(descriptor.key);
255
253
  if (raw === null) {
256
- throw new errors_1.StorageError(`LeaseJournal: listed record is missing`, 'corrupt', { key: descriptor.key });
254
+ throw new StorageError(`LeaseJournal: listed record is missing`, 'corrupt', { key: descriptor.key });
257
255
  }
258
256
  const record = asObject(raw, descriptor.key);
259
257
  const formatVersion = record.formatVersion;
260
258
  if (formatVersion !== RECORD_VERSION) {
261
- throw new errors_1.StorageError('LeaseJournal: unsupported record version', 'corrupt', {
259
+ throw new StorageError('LeaseJournal: unsupported record version', 'corrupt', {
262
260
  key: descriptor.key,
263
261
  detectedVersion: typeof formatVersion === 'number' ? formatVersion : undefined,
264
262
  unsupportedVersion: typeof formatVersion === 'number' ? formatVersion : undefined,
265
263
  });
266
264
  }
267
265
  if (record.sequence !== descriptor.sequence) {
268
- throw new errors_1.StorageError('LeaseJournal: sequence/key mismatch', 'corrupt', { key: descriptor.key });
266
+ throw new StorageError('LeaseJournal: sequence/key mismatch', 'corrupt', { key: descriptor.key });
269
267
  }
270
268
  const entry = validateEntry(record.entry, `${descriptor.key}.entry`);
271
269
  if (entry.hash !== descriptor.hash) {
272
- throw new errors_1.StorageError('LeaseJournal: hash/key mismatch', 'corrupt', { key: descriptor.key });
270
+ throw new StorageError('LeaseJournal: hash/key mismatch', 'corrupt', { key: descriptor.key });
273
271
  }
274
272
  entries.push(entry);
275
273
  }
@@ -278,7 +276,7 @@ class LeaseJournal {
278
276
  }
279
277
  async writeRecord(sequence, entry) {
280
278
  if (entry.hash === undefined) {
281
- throw new errors_1.StorageError(`LeaseJournal: entry ${sequence} has no hash`, 'corrupt');
279
+ throw new StorageError(`LeaseJournal: entry ${sequence} has no hash`, 'corrupt');
282
280
  }
283
281
  const key = recordKey(sequence, entry.hash);
284
282
  const record = {
@@ -301,14 +299,14 @@ class LeaseJournal {
301
299
  this.entries = (await this.readPhysicalRecords()).map(cloneEntry);
302
300
  const entry = validateEntry(cloneEntry(input), 'append');
303
301
  if (entry.status === 'reserved' && this.entries.some((existing) => existing.treeId === entry.treeId && existing.wotsIndex === entry.wotsIndex)) {
304
- throw new errors_1.StorageError(`LeaseJournal: WOTS slot ${entry.treeId}/${entry.wotsIndex} is already recorded`, 'write-failed');
302
+ throw new StorageError(`LeaseJournal: WOTS slot ${entry.treeId}/${entry.wotsIndex} is already recorded`, 'write-failed');
305
303
  }
306
304
  if (entry.status !== 'reserved' && entry.reservationId) {
307
305
  const latest = [...this.entries].reverse().find((existing) => existing.reservationId === entry.reservationId);
308
306
  if (latest?.status === entry.status) {
309
307
  if (latest.treeId !== entry.treeId || latest.wotsIndex !== entry.wotsIndex
310
308
  || (entry.status === 'committed' && latest.txId !== entry.txId)) {
311
- throw new errors_1.StorageError(`LeaseJournal: reservation ${entry.reservationId} is already ${latest.status} for a different use`, 'write-failed');
309
+ throw new StorageError(`LeaseJournal: reservation ${entry.reservationId} is already ${latest.status} for a different use`, 'write-failed');
312
310
  }
313
311
  if (latest.previousHash === undefined)
314
312
  delete input.previousHash;
@@ -318,7 +316,7 @@ class LeaseJournal {
318
316
  return;
319
317
  }
320
318
  if (latest && latest.status !== 'reserved') {
321
- throw new errors_1.StorageError(`LeaseJournal: reservation ${entry.reservationId} is already ${latest.status}`, 'write-failed');
319
+ throw new StorageError(`LeaseJournal: reservation ${entry.reservationId} is already ${latest.status}`, 'write-failed');
322
320
  }
323
321
  }
324
322
  const previous = this.entries[this.entries.length - 1];
@@ -330,7 +328,7 @@ class LeaseJournal {
330
328
  const sequence = this.entries.length;
331
329
  const sequencePrefix = `${ENTRY_PREFIX}${sequenceText(sequence)}:`;
332
330
  if ((await this.storage.keys()).some((key) => key.startsWith(sequencePrefix))) {
333
- throw new errors_1.StorageError(`LeaseJournal: sequence ${sequence} already exists`, 'write-failed');
331
+ throw new StorageError(`LeaseJournal: sequence ${sequence} already exists`, 'write-failed');
334
332
  }
335
333
  await this.writeRecord(sequence, entry);
336
334
  this.entries.push(entry);
@@ -362,7 +360,6 @@ class LeaseJournal {
362
360
  return entry ? cloneEntry(entry) : undefined;
363
361
  }
364
362
  async clear() {
365
- throw new errors_1.StorageError('LeaseJournal is append-only and cannot be cleared', 'write-failed');
363
+ throw new StorageError('LeaseJournal is append-only and cannot be cleared', 'write-failed');
366
364
  }
367
365
  }
368
- exports.LeaseJournal = LeaseJournal;
package/dist/local.js CHANGED
@@ -1,32 +1,29 @@
1
- "use strict";
2
1
  /**
3
2
  * LocalLeaseProvider — Layer 1: local durable ledger.
4
3
  *
5
4
  * Single-device, no network coordination.
6
5
  * All WOTS safety guaranteed by the local WotsWatermarkStore + LeaseJournal.
7
6
  */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.LocalLeaseProvider = void 0;
10
- const core_1 = require("@totemsdk/core");
11
- const core_2 = require("@totemsdk/core");
12
- const watermark_js_1 = require("./watermark.js");
13
- const journal_js_1 = require("./journal.js");
14
- const errors_js_1 = require("./errors.js");
7
+ import { NoopLogger } from '@totemsdk/core';
8
+ import { LeaseStore } from '@totemsdk/core';
9
+ import { WotsWatermarkStore, flatIndex } from './watermark.js';
10
+ import { LeaseJournal } from './journal.js';
11
+ import { LeaseNotFoundError, InvalidLeaseTransitionError, DeviceRangeViolationError, IndicesUnavailableError, } from './errors.js';
15
12
  function randomId() {
16
13
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
17
14
  return crypto.randomUUID();
18
15
  }
19
16
  return Math.random().toString(36).slice(2) + Date.now().toString(36);
20
17
  }
21
- class LocalLeaseProvider {
22
- constructor(storage, logger = new core_1.NoopLogger(), deviceId = 'local') {
18
+ export class LocalLeaseProvider {
19
+ constructor(storage, logger = new NoopLogger(), deviceId = 'local') {
23
20
  this.logger = logger;
24
21
  this._initialized = false;
25
22
  this.initializePromise = null;
26
23
  this.mutationTail = Promise.resolve();
27
- this.watermark = new watermark_js_1.WotsWatermarkStore(storage, logger);
28
- this.leaseStore = new core_2.LeaseStore(storage, logger);
29
- this.journal = new journal_js_1.LeaseJournal(storage, logger);
24
+ this.watermark = new WotsWatermarkStore(storage, logger);
25
+ this.leaseStore = new LeaseStore(storage, logger);
26
+ this.journal = new LeaseJournal(storage, logger);
30
27
  this.deviceId = deviceId;
31
28
  this.cas = LocalLeaseProvider.detectCas(storage);
32
29
  }
@@ -96,7 +93,7 @@ class LocalLeaseProvider {
96
93
  await this.journal.append({
97
94
  treeId,
98
95
  branchId: 'default',
99
- wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
96
+ wotsIndex: flatIndex(lease.indices),
100
97
  indices: lease.indices,
101
98
  status,
102
99
  reservationId: lease.leaseId,
@@ -120,7 +117,7 @@ class LocalLeaseProvider {
120
117
  await this.journal.append({
121
118
  treeId,
122
119
  branchId: 'default',
123
- wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
120
+ wotsIndex: flatIndex(lease.indices),
124
121
  indices: lease.indices,
125
122
  status: 'committed',
126
123
  reservationId: lease.leaseId,
@@ -134,7 +131,7 @@ class LocalLeaseProvider {
134
131
  await this.journal.append({
135
132
  treeId,
136
133
  branchId: 'default',
137
- wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
134
+ wotsIndex: flatIndex(lease.indices),
138
135
  indices: lease.indices,
139
136
  status: 'burned',
140
137
  reservationId: lease.leaseId,
@@ -157,7 +154,7 @@ class LocalLeaseProvider {
157
154
  await this.journal.append({
158
155
  treeId: 'default',
159
156
  branchId: 'default',
160
- wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
157
+ wotsIndex: flatIndex(lease.indices),
161
158
  indices: lease.indices,
162
159
  status: 'burned',
163
160
  reservationId: lease.leaseId,
@@ -179,7 +176,7 @@ class LocalLeaseProvider {
179
176
  await this.journal.append({
180
177
  treeId,
181
178
  branchId: 'default',
182
- wotsIndex: (0, watermark_js_1.flatIndex)(lease.indices),
179
+ wotsIndex: flatIndex(lease.indices),
183
180
  indices: lease.indices,
184
181
  status: 'reserved-expired',
185
182
  reservationId: lease.leaseId,
@@ -223,13 +220,13 @@ class LocalLeaseProvider {
223
220
  const treeId = params.treeId;
224
221
  const ttlMs = params.ttlMs ?? 120000;
225
222
  if (this.watermark.isUnavailable(treeId, indices)) {
226
- throw new errors_js_1.IndicesUnavailableError(treeId, indices);
223
+ throw new IndicesUnavailableError(treeId, indices);
227
224
  }
228
225
  // Validate device range BEFORE marking unavailable — prevents permanent index loss
229
226
  if (params.deviceId) {
230
227
  const range = this.getDeviceRange(params.deviceId);
231
228
  if (range && (indices.addressIndex < range.start || indices.addressIndex > range.end)) {
232
- throw new errors_js_1.DeviceRangeViolationError(indices.addressIndex, range.start, range.end);
229
+ throw new DeviceRangeViolationError(indices.addressIndex, range.start, range.end);
233
230
  }
234
231
  }
235
232
  const reservationId = randomId();
@@ -239,7 +236,7 @@ class LocalLeaseProvider {
239
236
  // race and both pick the same slot. Claim it atomically first; a lost race
240
237
  // means another instance owns it, so the slot is not re-allocated.
241
238
  if (this.cas) {
242
- const claimKey = `totem_wots_claim:${treeId}:${(0, watermark_js_1.flatIndex)(indices)}`;
239
+ const claimKey = `totem_wots_claim:${treeId}:${flatIndex(indices)}`;
243
240
  const claim = await this.cas.conditionalUpdate(claimKey, (current) => current
244
241
  ? { abort: 'slot already claimed' }
245
242
  : {
@@ -250,7 +247,7 @@ class LocalLeaseProvider {
250
247
  },
251
248
  });
252
249
  if (!claim.applied) {
253
- throw new errors_js_1.IndicesUnavailableError(treeId, indices);
250
+ throw new IndicesUnavailableError(treeId, indices);
254
251
  }
255
252
  }
256
253
  // Write-ahead safety record: once this succeeds, recovery will never
@@ -258,7 +255,7 @@ class LocalLeaseProvider {
258
255
  await this.journal.append({
259
256
  treeId,
260
257
  branchId: params.branchId ?? 'default',
261
- wotsIndex: (0, watermark_js_1.flatIndex)(indices),
258
+ wotsIndex: flatIndex(indices),
262
259
  indices,
263
260
  status: 'reserved',
264
261
  reservationId,
@@ -294,13 +291,13 @@ class LocalLeaseProvider {
294
291
  await this.watermark.refresh();
295
292
  const lease = this.leaseStore.get(reservationId);
296
293
  if (!lease)
297
- throw new errors_js_1.LeaseNotFoundError(reservationId);
294
+ throw new LeaseNotFoundError(reservationId);
298
295
  const treeId = lease.treeId ?? 'default';
299
296
  const indices = lease.indices;
300
297
  const latest = this.journal.getByReservation(reservationId);
301
298
  if (latest?.status === 'committed') {
302
299
  if (latest.txId !== txId) {
303
- throw new errors_js_1.InvalidLeaseTransitionError(reservationId, 'committed', 'committed');
300
+ throw new InvalidLeaseTransitionError(reservationId, 'committed', 'committed');
304
301
  }
305
302
  await this.watermark.markUnavailable(treeId, indices, 'committed');
306
303
  if (lease.status !== 'finalized')
@@ -309,12 +306,12 @@ class LocalLeaseProvider {
309
306
  }
310
307
  if (latest?.status === 'burned' || latest?.status === 'reserved-expired'
311
308
  || lease.status === 'cancelled' || lease.status === 'expired') {
312
- throw new errors_js_1.InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'committed');
309
+ throw new InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'committed');
313
310
  }
314
311
  await this.journal.append({
315
312
  treeId,
316
313
  branchId: 'default',
317
- wotsIndex: (0, watermark_js_1.flatIndex)(indices),
314
+ wotsIndex: flatIndex(indices),
318
315
  indices,
319
316
  status: 'committed',
320
317
  reservationId,
@@ -334,7 +331,7 @@ class LocalLeaseProvider {
334
331
  await this.watermark.refresh();
335
332
  const lease = this.leaseStore.get(reservationId);
336
333
  if (!lease)
337
- throw new errors_js_1.LeaseNotFoundError(reservationId);
334
+ throw new LeaseNotFoundError(reservationId);
338
335
  const treeId = lease.treeId ?? 'default';
339
336
  const indices = lease.indices;
340
337
  const latest = this.journal.getByReservation(reservationId);
@@ -346,12 +343,12 @@ class LocalLeaseProvider {
346
343
  }
347
344
  if (latest?.status === 'committed' || latest?.status === 'reserved-expired'
348
345
  || lease.status === 'finalized' || lease.status === 'expired') {
349
- throw new errors_js_1.InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'burned');
346
+ throw new InvalidLeaseTransitionError(reservationId, latest?.status ?? lease.status, 'burned');
350
347
  }
351
348
  await this.journal.append({
352
349
  treeId,
353
350
  branchId: 'default',
354
- wotsIndex: (0, watermark_js_1.flatIndex)(indices),
351
+ wotsIndex: flatIndex(indices),
355
352
  indices,
356
353
  status: 'burned',
357
354
  reservationId,
@@ -405,8 +402,8 @@ class LocalLeaseProvider {
405
402
  if (!this._initialized)
406
403
  await this.initialize();
407
404
  const local = this.watermark.getLocalWatermark(treeId);
408
- const localFlat = (0, watermark_js_1.flatIndex)({ addressIndex: local.addressCursor, l1: local.l1Cursor, l2: local.l2Cursor });
409
- const remoteFlat = (0, watermark_js_1.flatIndex)({ addressIndex: remote.addressCursor, l1: remote.l1Cursor, l2: remote.l2Cursor });
405
+ const localFlat = flatIndex({ addressIndex: local.addressCursor, l1: local.l1Cursor, l2: local.l2Cursor });
406
+ const remoteFlat = flatIndex({ addressIndex: remote.addressCursor, l1: remote.l1Cursor, l2: remote.l2Cursor });
410
407
  if (remoteFlat <= localFlat)
411
408
  return false;
412
409
  await this.watermark.save(treeId, {
@@ -437,4 +434,3 @@ class LocalLeaseProvider {
437
434
  return { start: slot * 8, end: slot * 8 + 7 };
438
435
  }
439
436
  }
440
- exports.LocalLeaseProvider = LocalLeaseProvider;
package/dist/onchain.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * OnchainWatermarkProvider — Layer 5: on-chain watermark anchoring.
4
3
  *
@@ -21,13 +20,11 @@
21
20
  * - syncLeaseJournal merges the on-chain cursor into the local watermark
22
21
  * monotonically (remote ahead → advance; remote behind → conflict record).
23
22
  */
24
- Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.OnchainWatermarkProvider = void 0;
26
- const watermark_js_1 = require("./watermark.js");
27
- const errors_js_1 = require("./errors.js");
28
- const core_1 = require("@totemsdk/core");
29
- const txpow_1 = require("@totemsdk/txpow");
30
- const certificate_js_1 = require("./certificate.js");
23
+ import { flatIndex } from './watermark.js';
24
+ import { OnchainWatermarkError } from './errors.js';
25
+ import { sha3_256, bytesToHex, hexToBytes, parseMxAddress, serializeTransaction, computeTransactionDigest, rollCoinId, writeMiniNumber, } from '@totemsdk/core';
26
+ import { serializeTxPoW } from '@totemsdk/txpow';
27
+ import { signCertificate, certificateSignatureVerified } from './certificate.js';
31
28
  const MAX_L = 64;
32
29
  const WATERMARK_COIN_STORAGE_KEY = 'wots-lease:onchain:watermark-coin';
33
30
  function multiConcat(parts) {
@@ -48,7 +45,7 @@ function concatBytes(a, b) {
48
45
  function addressToHex(address) {
49
46
  const upper = address.startsWith('0x') || address.startsWith('0X') ? address.slice(2) : address;
50
47
  return upper.toUpperCase().startsWith('MX')
51
- ? (0, core_1.bytesToHex)((0, core_1.parseMxAddress)(address))
48
+ ? bytesToHex(parseMxAddress(address))
52
49
  : address.replace(/^0x/i, '');
53
50
  }
54
51
  function flatToHex(flat) {
@@ -79,14 +76,14 @@ function stateVarJson(port, hexValue) {
79
76
  return { port, svtype: 'hex', data: hexValue };
80
77
  }
81
78
  function buildWitnessBytes(sigs) {
82
- const parts = [(0, core_1.writeMiniNumber)(BigInt(sigs.length), 0)];
79
+ const parts = [writeMiniNumber(BigInt(sigs.length), 0)];
83
80
  for (const sig of sigs)
84
81
  parts.push(sig);
85
- parts.push((0, core_1.writeMiniNumber)(0n, 0));
86
- parts.push((0, core_1.writeMiniNumber)(0n, 0));
82
+ parts.push(writeMiniNumber(0n, 0));
83
+ parts.push(writeMiniNumber(0n, 0));
87
84
  return multiConcat(parts);
88
85
  }
89
- class OnchainWatermarkProvider {
86
+ export class OnchainWatermarkProvider {
90
87
  constructor(config) {
91
88
  this._initialized = false;
92
89
  this._lastPublishBlock = -1;
@@ -145,7 +142,7 @@ class OnchainWatermarkProvider {
145
142
  expiresAt: reservation.expiresAt,
146
143
  signature: '',
147
144
  };
148
- certificate.signature = await (0, certificate_js_1.signCertificate)(this.signer, certificate);
145
+ certificate.signature = await signCertificate(this.signer, certificate);
149
146
  return { ...reservation, certificate };
150
147
  }
151
148
  async commitKeyUse(reservationId, txId) {
@@ -172,7 +169,7 @@ class OnchainWatermarkProvider {
172
169
  async publishWatermark(treeId) {
173
170
  await this.ensureInit();
174
171
  const wm = await this.local.getLocalWatermark(treeId);
175
- const newFlat = (0, watermark_js_1.flatIndex)({
172
+ const newFlat = flatIndex({
176
173
  addressIndex: wm.addressCursor,
177
174
  l1: wm.l1Cursor,
178
175
  l2: wm.l2Cursor,
@@ -193,7 +190,7 @@ class OnchainWatermarkProvider {
193
190
  // anchored by the CURRENT watermark coin (which advances on every spend).
194
191
  const coin = await this.chain.getCoin(this.currentCoinId);
195
192
  if (!coin) {
196
- throw new errors_js_1.OnchainWatermarkError(`Watermark coin not found: ${this.currentCoinId}`);
193
+ throw new OnchainWatermarkError(`Watermark coin not found: ${this.currentCoinId}`);
197
194
  }
198
195
  const currentState = coin.state ?? [];
199
196
  const currentFlat = readStateCursor(currentState, this.statePort);
@@ -216,28 +213,28 @@ class OnchainWatermarkProvider {
216
213
  storestate: true,
217
214
  state: [stateVarJson(this.statePort, flatToHex(newFlat))],
218
215
  };
216
+ const rolledCoinId = rollCoinId(hexToBytes(this.currentCoinId), 0);
219
217
  const tx = {
220
218
  linkhash: '0x00',
221
219
  inputs: [inputCoin],
222
- outputs: [outputCoin],
220
+ outputs: [{ ...outputCoin, coinid: '0x' + bytesToHex(rolledCoinId) }],
223
221
  state: [],
224
222
  };
225
- const txBytes = (0, core_1.serializeTransaction)(JSON.stringify(tx));
226
- (0, core_1.precomputeTransactionCoinID)(txBytes, 0);
227
- const digest = (0, core_1.computeTransactionDigest)(txBytes);
223
+ const txBytes = serializeTransaction(JSON.stringify(tx));
224
+ const digest = computeTransactionDigest(txBytes);
228
225
  const sig = await this.signer.sign(digest);
229
226
  const witnessBytes = buildWitnessBytes([sig]);
230
- const prng = (0, core_1.sha3_256)(new TextEncoder().encode(`watermark:${treeId}:${this.currentCoinId}:${newFlat}`));
231
- const txHex = Buffer.from((0, txpow_1.serializeTxPoW)(txBytes, witnessBytes, { prng })).toString('hex');
227
+ const prng = sha3_256(new TextEncoder().encode(`watermark:${treeId}:${this.currentCoinId}:${newFlat}`));
228
+ const txHex = Buffer.from(serializeTxPoW(txBytes, witnessBytes, { prng })).toString('hex');
232
229
  const result = await this.chain.broadcastTxPoW(txHex);
233
230
  if (!result.success) {
234
- throw new errors_js_1.OnchainWatermarkError(`Watermark publish rejected: ${result.message ?? 'unknown error'}`);
231
+ throw new OnchainWatermarkError(`Watermark publish rejected: ${result.message ?? 'unknown error'}`);
235
232
  }
236
233
  // Rollover: the spent watermark coin is destroyed and replaced by a fresh
237
234
  // coin with a new coin ID (output 0 of this spend). Track and persist the
238
235
  // ADVANCED identity so the next publish spends the live coin, not the
239
236
  // consumed one.
240
- const newCoinId = '0x' + (0, core_1.bytesToHex)((0, core_1.precomputeTransactionCoinID)((0, core_1.hexToBytes)(this.currentCoinId), 0));
237
+ const newCoinId = '0x' + bytesToHex(rolledCoinId);
241
238
  this.currentCoinId = newCoinId;
242
239
  if (this.storage) {
243
240
  try {
@@ -260,7 +257,7 @@ class OnchainWatermarkProvider {
260
257
  let advancedTo;
261
258
  for (const treeId of this.local.listTrees()) {
262
259
  const localWm = await this.local.getLocalWatermark(treeId);
263
- const localFlat = (0, watermark_js_1.flatIndex)({
260
+ const localFlat = flatIndex({
264
261
  addressIndex: localWm.addressCursor,
265
262
  l1: localWm.l1Cursor,
266
263
  l2: localWm.l2Cursor,
@@ -306,9 +303,8 @@ class OnchainWatermarkProvider {
306
303
  return false;
307
304
  if (cert.expiresAt <= Date.now())
308
305
  return false;
309
- if (!(await (0, certificate_js_1.certificateSignatureVerified)(cert, this.signer)))
306
+ if (!(await certificateSignatureVerified(cert, this.signer)))
310
307
  return false;
311
308
  return cert.issuedBy === 'onchain-watermark';
312
309
  }
313
310
  }
314
- exports.OnchainWatermarkProvider = OnchainWatermarkProvider;
package/dist/quorum.js CHANGED
@@ -1,4 +1,3 @@
1
- "use strict";
2
1
  /**
3
2
  * P2PQuorumLeaseProvider — Layer 4: multi-device quorum lease coordination.
4
3
  *
@@ -28,14 +27,12 @@
28
27
  * - If a peer reports the slot as taken, the local reservation is burned
29
28
  * and QuorumConflictError is thrown.
30
29
  */
31
- Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.P2PQuorumLeaseProvider = void 0;
33
- const errors_js_1 = require("./errors.js");
34
- const certificate_js_1 = require("./certificate.js");
30
+ import { QuorumUnavailableError, QuorumConflictError, } from './errors.js';
31
+ import { signCertificate, certificateSignatureVerified, } from './certificate.js';
35
32
  function sameIndices(a, b) {
36
33
  return a.addressIndex === b.addressIndex && a.l1 === b.l1 && a.l2 === b.l2;
37
34
  }
38
- class P2PQuorumLeaseProvider {
35
+ export class P2PQuorumLeaseProvider {
39
36
  constructor(config) {
40
37
  /**
41
38
  * localReservationId -> (peerId -> the reservation id that peer issued).
@@ -75,7 +72,7 @@ class P2PQuorumLeaseProvider {
75
72
  attestations = await this.attestKeyUse(params, reservation.indices);
76
73
  }
77
74
  catch (err) {
78
- const reason = err instanceof errors_js_1.QuorumConflictError
75
+ const reason = err instanceof QuorumConflictError
79
76
  ? `quorum conflict: ${err.message}`
80
77
  : 'quorum not reached';
81
78
  await this.local.burnReservation(reservation.reservationId, reason);
@@ -96,7 +93,7 @@ class P2PQuorumLeaseProvider {
96
93
  signature: '',
97
94
  attestations,
98
95
  };
99
- certificate.signature = this.certificateSigner ? await (0, certificate_js_1.signCertificate)(this.certificateSigner, certificate) : '';
96
+ certificate.signature = this.certificateSigner ? await signCertificate(this.certificateSigner, certificate) : '';
100
97
  return { ...reservation, certificate };
101
98
  }
102
99
  /**
@@ -148,10 +145,10 @@ class P2PQuorumLeaseProvider {
148
145
  }
149
146
  }));
150
147
  if (conflicts.length > 0) {
151
- throw new errors_js_1.QuorumConflictError(params.treeId, indices);
148
+ throw new QuorumConflictError(params.treeId, indices);
152
149
  }
153
150
  if (attestations.length < this.minAttestations) {
154
- throw new errors_js_1.QuorumUnavailableError(this.minAttestations, attestations.length);
151
+ throw new QuorumUnavailableError(this.minAttestations, attestations.length);
155
152
  }
156
153
  return attestations;
157
154
  }
@@ -166,7 +163,7 @@ class P2PQuorumLeaseProvider {
166
163
  txId,
167
164
  }));
168
165
  if (this.requireQuorumOnCommit && acks < this.minAttestations) {
169
- throw new errors_js_1.QuorumUnavailableError(this.minAttestations, acks);
166
+ throw new QuorumUnavailableError(this.minAttestations, acks);
170
167
  }
171
168
  }
172
169
  async burnReservation(reservationId, reason) {
@@ -294,7 +291,7 @@ class P2PQuorumLeaseProvider {
294
291
  if (cert.expiresAt <= Date.now())
295
292
  return false;
296
293
  // An unsigned certificate carries no authenticity evidence.
297
- if (!(await (0, certificate_js_1.certificateSignatureVerified)(cert, this.certificateSigner))) {
294
+ if (!(await certificateSignatureVerified(cert, this.certificateSigner))) {
298
295
  return false;
299
296
  }
300
297
  const attestations = cert.attestations ?? [];
@@ -304,4 +301,3 @@ class P2PQuorumLeaseProvider {
304
301
  a.expiresAt > Date.now());
305
302
  }
306
303
  }
307
- exports.P2PQuorumLeaseProvider = P2PQuorumLeaseProvider;