@totemsdk/wots-lease 1.0.0 → 1.0.1

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/axia.d.ts CHANGED
@@ -29,5 +29,5 @@ export declare class AxiaLeaseProvider implements WotsLeaseProvider {
29
29
  getLocalWatermark(treeId: string): Promise<LocalWatermark>;
30
30
  publishWatermark(_treeId: string): Promise<void>;
31
31
  syncLeaseJournal(): Promise<SyncResult>;
32
- verifyLeaseCertificate(cert?: LeaseCertificate): Promise<boolean>;
32
+ verifyLeaseCertificate(_cert?: LeaseCertificate): Promise<boolean>;
33
33
  }
package/dist/axia.js CHANGED
@@ -11,6 +11,7 @@ const core_2 = require("@totemsdk/core");
11
11
  const watermark_js_1 = require("./watermark.js");
12
12
  const journal_js_1 = require("./journal.js");
13
13
  const errors_js_1 = require("./errors.js");
14
+ const errors_1 = require("@totemsdk/storage/errors");
14
15
  class AxiaLeaseProvider {
15
16
  constructor(config) {
16
17
  this._initialized = false;
@@ -42,6 +43,12 @@ class AxiaLeaseProvider {
42
43
  ttlMs: params.ttlMs ?? 120000,
43
44
  });
44
45
  const indices = resp.lease;
46
+ // RFC-020 C2: never accept an index this tree has already consumed, even if
47
+ // a replaying/hostile lease server hands the same slot out again.
48
+ await this.watermark.refresh();
49
+ if (this.watermark.isUnavailable(params.treeId, indices)) {
50
+ throw new errors_js_1.IndicesUnavailableError(params.treeId, indices);
51
+ }
45
52
  const expiresAt = Date.now() + (params.ttlMs ?? 120000);
46
53
  const reservationId = resp.txId;
47
54
  const lease = {
@@ -75,10 +82,16 @@ class AxiaLeaseProvider {
75
82
  throw new errors_js_1.LeaseNotFoundError(reservationId);
76
83
  await (0, core_2.finalizeLease)(this.apiUrl, this.apiKey, lease.leaseToken, txId);
77
84
  const indices = lease.indices;
78
- await this.watermark.markUnavailable(lease.indices.addressIndex.toString(), indices, 'committed');
85
+ // RFC-020 C2: key the watermark/journal by the real treeId, never the
86
+ // reservation id.
87
+ const treeId = lease.treeId;
88
+ if (!treeId) {
89
+ throw new errors_1.StorageError(`AxiaLeaseProvider: lease ${reservationId} has no treeId`, 'corrupt');
90
+ }
91
+ await this.watermark.markUnavailable(treeId, indices, 'committed');
79
92
  await this.leaseStore.updateStatus(reservationId, 'finalized');
80
93
  await this.journal.append({
81
- treeId: reservationId,
94
+ treeId,
82
95
  branchId: 'default',
83
96
  wotsIndex: (0, watermark_js_1.flatIndex)(indices),
84
97
  indices,
@@ -95,11 +108,15 @@ class AxiaLeaseProvider {
95
108
  if (!lease)
96
109
  throw new errors_js_1.LeaseNotFoundError(reservationId);
97
110
  const indices = lease.indices;
98
- await this.watermark.markUnavailable(lease.indices.addressIndex.toString(), indices, 'burned');
111
+ const treeId = lease.treeId;
112
+ if (!treeId) {
113
+ throw new errors_1.StorageError(`AxiaLeaseProvider: lease ${reservationId} has no treeId`, 'corrupt');
114
+ }
115
+ await this.watermark.markUnavailable(treeId, indices, 'burned');
99
116
  await this.leaseStore.updateStatus(reservationId, 'cancelled');
100
117
  this.logger.warn(`[AxiaLeaseProvider] Burning reservation ${reservationId}: ${reason}`);
101
118
  await this.journal.append({
102
- treeId: reservationId,
119
+ treeId,
103
120
  branchId: 'default',
104
121
  wotsIndex: (0, watermark_js_1.flatIndex)(indices),
105
122
  indices,
@@ -119,9 +136,9 @@ class AxiaLeaseProvider {
119
136
  async syncLeaseJournal() {
120
137
  return { synced: true, conflicts: [] };
121
138
  }
122
- async verifyLeaseCertificate(cert) {
123
- if (cert === undefined)
124
- return true;
139
+ async verifyLeaseCertificate(_cert) {
140
+ // RFC-020 C2: the Axia API does not return a server-signed certificate we
141
+ // can verify, so fail closed rather than asserting a trust we cannot prove.
125
142
  return false;
126
143
  }
127
144
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared helpers for authenticating lease certificates.
3
+ *
4
+ * A certificate is authenticated by a real signature over a canonical,
5
+ * deterministic byte encoding of its content. Providers that hold an issuing
6
+ * identity (`certificateSigner`, or the watermark coin owner on Layer 5) sign
7
+ * the payload before returning the certificate. Verification FAILS CLOSED:
8
+ * a certificate is rejected unless the caller supplies a `verify()` function
9
+ * and the signature cryptographically verifies against it. An empty signature,
10
+ * an absent verifier, or an identity without a verifier are all rejections —
11
+ * `issuedBy` alone is never treated as authenticity evidence.
12
+ */
13
+ import type { CertificateSigner, LeaseCertificate } from './types.js';
14
+ /**
15
+ * Canonical byte encoding of a certificate's bindable content.
16
+ *
17
+ * Any change to a bound field changes the message, so a certificate cannot
18
+ * be re-targeted at a different reservation, device, purpose, or expiry.
19
+ */
20
+ export declare function canonicalCertificateMessage(cert: Pick<LeaseCertificate, 'treeId' | 'branchId' | 'deviceId' | 'indices' | 'purpose' | 'payloadHash' | 'reservationId' | 'issuedBy' | 'issuedAt' | 'expiresAt'>): Uint8Array;
21
+ /** Sign a certificate via the issuer identity, returning hex of the signature. */
22
+ export declare function signCertificate(signer: {
23
+ sign(message: Uint8Array): Promise<Uint8Array>;
24
+ }, cert: Parameters<typeof canonicalCertificateMessage>[0]): Promise<string>;
25
+ /**
26
+ * Verify a certificate's signature.
27
+ *
28
+ * Fails closed (AUD-016):
29
+ * - Empty/malformed signature -> false (an unsigned certificate is not
30
+ * authenticity evidence regardless of what issuedBy claims).
31
+ * - No `signer`, or a `signer` without a `verify()` function -> false. Knowing
32
+ * only an identity (e.g. a public key digest) cannot authenticate a
33
+ * signature; a verifier is required.
34
+ * - Otherwise the signature must cryptographically verify against the
35
+ * supplied verifier.
36
+ */
37
+ export declare function certificateSignatureVerified(cert: LeaseCertificate, signer?: CertificateSigner): Promise<boolean>;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Shared helpers for authenticating lease certificates.
4
+ *
5
+ * A certificate is authenticated by a real signature over a canonical,
6
+ * deterministic byte encoding of its content. Providers that hold an issuing
7
+ * identity (`certificateSigner`, or the watermark coin owner on Layer 5) sign
8
+ * the payload before returning the certificate. Verification FAILS CLOSED:
9
+ * a certificate is rejected unless the caller supplies a `verify()` function
10
+ * and the signature cryptographically verifies against it. An empty signature,
11
+ * an absent verifier, or an identity without a verifier are all rejections —
12
+ * `issuedBy` alone is never treated as authenticity evidence.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.canonicalCertificateMessage = canonicalCertificateMessage;
16
+ exports.signCertificate = signCertificate;
17
+ exports.certificateSignatureVerified = certificateSignatureVerified;
18
+ const core_1 = require("@totemsdk/core");
19
+ /**
20
+ * Canonical byte encoding of a certificate's bindable content.
21
+ *
22
+ * Any change to a bound field changes the message, so a certificate cannot
23
+ * be re-targeted at a different reservation, device, purpose, or expiry.
24
+ */
25
+ function canonicalCertificateMessage(cert) {
26
+ const encoded = new TextEncoder().encode([
27
+ 'TOTEM-LEASECERT',
28
+ 'v1',
29
+ cert.issuedBy ?? '',
30
+ cert.treeId ?? '',
31
+ cert.branchId ?? '',
32
+ cert.deviceId ?? '',
33
+ cert.reservationId ?? '',
34
+ String(cert.indices?.addressIndex ?? 0),
35
+ String(cert.indices?.l1 ?? 0),
36
+ String(cert.indices?.l2 ?? 0),
37
+ cert.purpose ?? '',
38
+ cert.payloadHash ?? '',
39
+ String(cert.issuedAt ?? 0),
40
+ String(cert.expiresAt ?? 0),
41
+ ].join('\x1f'));
42
+ return (0, core_1.sha3_256)(encoded);
43
+ }
44
+ /** Sign a certificate via the issuer identity, returning hex of the signature. */
45
+ async function signCertificate(signer, cert) {
46
+ return (0, core_1.bytesToHex)(await signer.sign(canonicalCertificateMessage(cert)));
47
+ }
48
+ /**
49
+ * Verify a certificate's signature.
50
+ *
51
+ * Fails closed (AUD-016):
52
+ * - Empty/malformed signature -> false (an unsigned certificate is not
53
+ * authenticity evidence regardless of what issuedBy claims).
54
+ * - No `signer`, or a `signer` without a `verify()` function -> false. Knowing
55
+ * only an identity (e.g. a public key digest) cannot authenticate a
56
+ * signature; a verifier is required.
57
+ * - Otherwise the signature must cryptographically verify against the
58
+ * supplied verifier.
59
+ */
60
+ async function certificateSignatureVerified(cert, signer) {
61
+ if (typeof cert.signature !== 'string' || cert.signature.length === 0) {
62
+ return false;
63
+ }
64
+ if (!signer?.verify)
65
+ return false;
66
+ let sig;
67
+ try {
68
+ sig = (0, core_1.hexToBytes)(cert.signature);
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ return signer.verify(canonicalCertificateMessage(cert), sig);
74
+ }
package/dist/errors.d.ts CHANGED
@@ -2,6 +2,26 @@ import type { SigningIndices } from './types.js';
2
2
  export declare class WatermarkMonotonicityError extends Error {
3
3
  constructor(message: string);
4
4
  }
5
+ /**
6
+ * Raised when seeding would move the local watermark cursor backwards. This is
7
+ * the fail-closed guard against restoring a wallet below its true high-water
8
+ * mark and reusing a WOTS leaf (RFC-013 §9).
9
+ */
10
+ export declare class WatermarkSeedRegressionError extends Error {
11
+ readonly treeId: string;
12
+ readonly currentCursor: number;
13
+ readonly seedCursor: number;
14
+ constructor(treeId: string, currentCursor: number, seedCursor: number);
15
+ }
16
+ /**
17
+ * Raised when a restore/import has no usable watermark source and the caller has
18
+ * not declared the keyspace fresh. Failing closed is required: starting at 0
19
+ * would reissue spent leaves (RFC-013 §9).
20
+ */
21
+ export declare class WatermarkSeedRequiredError extends Error {
22
+ readonly treeId: string;
23
+ constructor(treeId: string);
24
+ }
5
25
  export declare class WatermarkExhaustedError extends Error {
6
26
  readonly treeId: string;
7
27
  constructor(treeId: string);
@@ -10,6 +30,12 @@ export declare class LeaseNotFoundError extends Error {
10
30
  readonly reservationId: string;
11
31
  constructor(reservationId: string);
12
32
  }
33
+ export declare class InvalidLeaseTransitionError extends Error {
34
+ readonly reservationId: string;
35
+ readonly currentStatus: string;
36
+ readonly requestedStatus: string;
37
+ constructor(reservationId: string, currentStatus: string, requestedStatus: string);
38
+ }
13
39
  export declare class IndicesUnavailableError extends Error {
14
40
  readonly treeId: string;
15
41
  readonly indices: SigningIndices;
package/dist/errors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkMonotonicityError = void 0;
3
+ exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.InvalidLeaseTransitionError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkSeedRequiredError = exports.WatermarkSeedRegressionError = exports.WatermarkMonotonicityError = void 0;
4
4
  class WatermarkMonotonicityError extends Error {
5
5
  constructor(message) {
6
6
  super(message);
@@ -8,6 +8,37 @@ class WatermarkMonotonicityError extends Error {
8
8
  }
9
9
  }
10
10
  exports.WatermarkMonotonicityError = WatermarkMonotonicityError;
11
+ /**
12
+ * Raised when seeding would move the local watermark cursor backwards. This is
13
+ * the fail-closed guard against restoring a wallet below its true high-water
14
+ * mark and reusing a WOTS leaf (RFC-013 §9).
15
+ */
16
+ class WatermarkSeedRegressionError extends Error {
17
+ constructor(treeId, currentCursor, seedCursor) {
18
+ super(`Refusing to seed watermark for ${treeId} below the current cursor ` +
19
+ `(${seedCursor} < ${currentCursor}); this would re-expose used WOTS leaves.`);
20
+ this.treeId = treeId;
21
+ this.currentCursor = currentCursor;
22
+ this.seedCursor = seedCursor;
23
+ this.name = 'WatermarkSeedRegressionError';
24
+ }
25
+ }
26
+ exports.WatermarkSeedRegressionError = WatermarkSeedRegressionError;
27
+ /**
28
+ * Raised when a restore/import has no usable watermark source and the caller has
29
+ * not declared the keyspace fresh. Failing closed is required: starting at 0
30
+ * would reissue spent leaves (RFC-013 §9).
31
+ */
32
+ class WatermarkSeedRequiredError extends Error {
33
+ constructor(treeId) {
34
+ super(`No watermark source available for ${treeId}. Provide an on-chain cursor, an ` +
35
+ 'exported watermark, or an operator high-water mark — or declare the keyspace fresh. ' +
36
+ 'Refusing to start at 0.');
37
+ this.treeId = treeId;
38
+ this.name = 'WatermarkSeedRequiredError';
39
+ }
40
+ }
41
+ exports.WatermarkSeedRequiredError = WatermarkSeedRequiredError;
11
42
  class WatermarkExhaustedError extends Error {
12
43
  constructor(treeId) {
13
44
  super(`WOTS keyspace exhausted for tree: ${treeId}`);
@@ -24,6 +55,16 @@ class LeaseNotFoundError extends Error {
24
55
  }
25
56
  }
26
57
  exports.LeaseNotFoundError = LeaseNotFoundError;
58
+ class InvalidLeaseTransitionError extends Error {
59
+ constructor(reservationId, currentStatus, requestedStatus) {
60
+ super(`Cannot transition lease ${reservationId} from ${currentStatus} to ${requestedStatus}`);
61
+ this.reservationId = reservationId;
62
+ this.currentStatus = currentStatus;
63
+ this.requestedStatus = requestedStatus;
64
+ this.name = 'InvalidLeaseTransitionError';
65
+ }
66
+ }
67
+ exports.InvalidLeaseTransitionError = InvalidLeaseTransitionError;
27
68
  class IndicesUnavailableError extends Error {
28
69
  constructor(treeId, indices) {
29
70
  super(`Indices (${indices.addressIndex}, ${indices.l1}, ${indices.l2}) are already unavailable for tree: ${treeId}`);
package/dist/hybrid.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Always uses local first, then gates on node cert and/or quorum attestation
5
5
  * for transactions above a configurable value threshold.
6
6
  */
7
- import type { WotsLeaseProvider, ReserveParams, LeaseReservation, LeaseCertificate, LocalWatermark, SyncResult } from './types.js';
7
+ import type { WotsLeaseProvider, ReserveParams, LeaseReservation, LeaseCertificate, LocalWatermark, SyncResult, CertificateSigner } from './types.js';
8
8
  import type { LocalLeaseProvider } from './local.js';
9
9
  import type { PersonalLeaseNodeProvider } from './stubs.js';
10
10
  import type { P2PQuorumLeaseProvider } from './stubs.js';
@@ -15,6 +15,8 @@ export interface HybridLeaseProviderConfig {
15
15
  quorum?: P2PQuorumLeaseProvider;
16
16
  onchain?: OnchainWatermarkProvider;
17
17
  threshold?: number;
18
+ /** Identity that authenticates locally-assembled quorum certificates. */
19
+ certificateSigner?: CertificateSigner;
18
20
  }
19
21
  export declare class HybridLeaseProvider implements WotsLeaseProvider {
20
22
  private readonly local;
@@ -22,6 +24,7 @@ export declare class HybridLeaseProvider implements WotsLeaseProvider {
22
24
  private readonly quorum?;
23
25
  private readonly onchain?;
24
26
  private readonly threshold;
27
+ private readonly certificateSigner?;
25
28
  constructor(config: HybridLeaseProviderConfig);
26
29
  private isHighValue;
27
30
  reserveKeyUse(params: ReserveParams): Promise<LeaseReservation>;
package/dist/hybrid.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.HybridLeaseProvider = void 0;
10
+ const certificate_js_1 = require("./certificate.js");
10
11
  class HybridLeaseProvider {
11
12
  constructor(config) {
12
13
  this.local = config.local;
@@ -14,6 +15,7 @@ class HybridLeaseProvider {
14
15
  this.quorum = config.quorum;
15
16
  this.onchain = config.onchain;
16
17
  this.threshold = config.threshold ?? Infinity;
18
+ this.certificateSigner = config.certificateSigner;
17
19
  }
18
20
  isHighValue(params) {
19
21
  if (!params.valueHint)
@@ -22,29 +24,41 @@ class HybridLeaseProvider {
22
24
  return !isNaN(val) && val >= this.threshold;
23
25
  }
24
26
  async reserveKeyUse(params) {
27
+ // Cross-instance/restore safety (RFC-013 §8/§9): before a high-value
28
+ // reservation, anchor the local cursor to the on-chain watermark so a second
29
+ // instance of the same seed cannot be handed a leaf we already used.
30
+ if (this.isHighValue(params) && this.onchain) {
31
+ try {
32
+ await this.onchain.syncLeaseJournal();
33
+ }
34
+ catch {
35
+ // Chain unreachable — local monotonic watermark still guards reuse.
36
+ }
37
+ }
25
38
  const reservation = await this.local.reserveKeyUse(params);
26
39
  if (this.isHighValue(params)) {
27
40
  // Layer 4 — quorum attestation over the SAME local slot.
28
41
  if (this.quorum) {
29
42
  try {
30
43
  const attestations = await this.quorum.attestKeyUse(params, reservation.indices);
31
- return {
32
- ...reservation,
33
- certificate: {
34
- reservationId: reservation.reservationId,
35
- treeId: params.treeId,
36
- branchId: params.branchId,
37
- deviceId: params.deviceId,
38
- indices: reservation.indices,
39
- purpose: params.purpose,
40
- payloadHash: params.payloadHash,
41
- issuedBy: 'p2p-quorum',
42
- issuedAt: Date.now(),
43
- expiresAt: reservation.expiresAt,
44
- signature: '',
45
- attestations,
46
- },
44
+ const certificate = {
45
+ reservationId: reservation.reservationId,
46
+ treeId: params.treeId,
47
+ branchId: params.branchId,
48
+ deviceId: params.deviceId,
49
+ indices: reservation.indices,
50
+ purpose: params.purpose,
51
+ payloadHash: params.payloadHash,
52
+ issuedBy: this.certificateSigner?.name ?? 'p2p-quorum',
53
+ issuedAt: Date.now(),
54
+ expiresAt: reservation.expiresAt,
55
+ signature: '',
56
+ attestations,
47
57
  };
58
+ certificate.signature = this.certificateSigner
59
+ ? await (0, certificate_js_1.signCertificate)(this.certificateSigner, certificate)
60
+ : '';
61
+ return { ...reservation, certificate };
48
62
  }
49
63
  catch {
50
64
  // Quorum unavailable — fall through to node / local only.
@@ -72,21 +86,57 @@ class HybridLeaseProvider {
72
86
  getLocalWatermark(treeId) {
73
87
  return this.local.getLocalWatermark(treeId);
74
88
  }
75
- publishWatermark(treeId) {
89
+ async publishWatermark(treeId) {
90
+ // Anchor to the chain first (authoritative cross-instance cursor), then the
91
+ // personal node, then the local no-op.
92
+ if (this.onchain) {
93
+ try {
94
+ await this.onchain.publishWatermark(treeId);
95
+ return;
96
+ }
97
+ catch {
98
+ // fall through
99
+ }
100
+ }
76
101
  if (this.node) {
77
- return this.node.publishWatermark(treeId).catch(() => this.local.publishWatermark(treeId));
102
+ try {
103
+ await this.node.publishWatermark(treeId);
104
+ return;
105
+ }
106
+ catch {
107
+ // fall through
108
+ }
78
109
  }
79
- return this.local.publishWatermark(treeId);
110
+ await this.local.publishWatermark(treeId);
80
111
  }
81
- syncLeaseJournal() {
112
+ async syncLeaseJournal() {
113
+ // Read the authoritative on-chain cursor first so a restore/second instance
114
+ // advances past every leaf published by anyone holding this seed.
115
+ if (this.onchain) {
116
+ try {
117
+ return await this.onchain.syncLeaseJournal();
118
+ }
119
+ catch {
120
+ // fall through
121
+ }
122
+ }
82
123
  if (this.node) {
83
- return this.node.syncLeaseJournal().catch(() => this.local.syncLeaseJournal());
124
+ try {
125
+ return await this.node.syncLeaseJournal();
126
+ }
127
+ catch {
128
+ // fall through
129
+ }
84
130
  }
85
131
  return this.local.syncLeaseJournal();
86
132
  }
87
133
  async verifyLeaseCertificate(cert) {
88
134
  if (!cert)
89
135
  return this.local.verifyLeaseCertificate(cert);
136
+ // Locally-assembled quorum certificates authenticate against our issuer.
137
+ if (cert.issuedBy === (this.certificateSigner?.name ?? 'p2p-quorum')) {
138
+ return (0, certificate_js_1.certificateSignatureVerified)(cert, this.certificateSigner);
139
+ }
90
140
  if (this.node)
91
141
  return this.node.verifyLeaseCertificate(cert);
92
142
  return false;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- export type { UnavailableReason, SigningIndices, LeaseStatus, TreeWatermark, WotsWatermarkState, LeaseCertificate, LeaseReservation, ReserveParams, LocalWatermark, ConflictRecord, SyncResult, WotsLeaseProvider, JournalEntry, DeviceKeyRange, PersonalLeaseNodeConfig, QuorumAttestation, QuorumPeer, P2PQuorumLeaseProviderConfig, OnchainWatermarkProviderConfig, } from './types.js';
1
+ export type { UnavailableReason, SigningIndices, LeaseStatus, TreeWatermark, WotsWatermarkState, LeaseCertificate, LeaseReservation, ReserveParams, LocalWatermark, WatermarkSeed, SeedWatermarkOptions, ConflictRecord, SyncResult, WotsLeaseProvider, JournalEntry, DeviceKeyRange, PersonalLeaseNodeConfig, QuorumAttestation, QuorumPeer, CertificateSigner, P2PQuorumLeaseProviderConfig, OnchainWatermarkProviderConfig, } from './types.js';
2
2
  export { WotsWatermarkStore, flatIndex, fromFlatIndex, } from './watermark.js';
3
3
  export { LeaseJournal } from './journal.js';
4
4
  export { LocalLeaseProvider } from './local.js';
5
+ export { seedLeaseWatermark, exportLeaseWatermark } from './seed.js';
6
+ export type { SeedLeaseWatermarkParams } from './seed.js';
5
7
  export { AxiaLeaseProvider } from './axia.js';
6
8
  export type { AxiaLeaseProviderConfig } from './axia.js';
7
9
  export { HybridLeaseProvider } from './hybrid.js';
@@ -10,4 +12,4 @@ export { PersonalLeaseNodeProvider, P2PQuorumLeaseProvider, OnchainWatermarkProv
10
12
  export { P2PQuorumLeaseProvider as QuorumLeaseProvider } from './quorum.js';
11
13
  export { OnchainWatermarkProvider as ChainWatermarkProvider } from './onchain.js';
12
14
  export { allocateDeviceRange, deviceSlotForAddressIndex } from './device.js';
13
- export { WatermarkMonotonicityError, WatermarkExhaustedError, LeaseNotFoundError, IndicesUnavailableError, PersonalLeaseNodeNotConfiguredError, P2PQuorumNotImplementedError, OnchainWatermarkNotImplementedError, QuorumUnavailableError, QuorumConflictError, OnchainWatermarkError, DeviceRangeViolationError, } from './errors.js';
15
+ export { WatermarkMonotonicityError, WatermarkExhaustedError, WatermarkSeedRegressionError, WatermarkSeedRequiredError, LeaseNotFoundError, IndicesUnavailableError, PersonalLeaseNodeNotConfiguredError, P2PQuorumNotImplementedError, OnchainWatermarkNotImplementedError, QuorumUnavailableError, QuorumConflictError, OnchainWatermarkError, DeviceRangeViolationError, } from './errors.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkMonotonicityError = exports.deviceSlotForAddressIndex = exports.allocateDeviceRange = exports.ChainWatermarkProvider = exports.QuorumLeaseProvider = exports.OnchainWatermarkProvider = exports.P2PQuorumLeaseProvider = exports.PersonalLeaseNodeProvider = exports.HybridLeaseProvider = exports.AxiaLeaseProvider = exports.LocalLeaseProvider = exports.LeaseJournal = exports.fromFlatIndex = exports.flatIndex = exports.WotsWatermarkStore = void 0;
3
+ exports.DeviceRangeViolationError = exports.OnchainWatermarkError = exports.QuorumConflictError = exports.QuorumUnavailableError = exports.OnchainWatermarkNotImplementedError = exports.P2PQuorumNotImplementedError = exports.PersonalLeaseNodeNotConfiguredError = exports.IndicesUnavailableError = exports.LeaseNotFoundError = exports.WatermarkSeedRequiredError = exports.WatermarkSeedRegressionError = exports.WatermarkExhaustedError = exports.WatermarkMonotonicityError = exports.deviceSlotForAddressIndex = exports.allocateDeviceRange = exports.ChainWatermarkProvider = exports.QuorumLeaseProvider = exports.OnchainWatermarkProvider = exports.P2PQuorumLeaseProvider = exports.PersonalLeaseNodeProvider = exports.HybridLeaseProvider = exports.AxiaLeaseProvider = exports.exportLeaseWatermark = exports.seedLeaseWatermark = exports.LocalLeaseProvider = exports.LeaseJournal = exports.fromFlatIndex = exports.flatIndex = exports.WotsWatermarkStore = void 0;
4
4
  var watermark_js_1 = require("./watermark.js");
5
5
  Object.defineProperty(exports, "WotsWatermarkStore", { enumerable: true, get: function () { return watermark_js_1.WotsWatermarkStore; } });
6
6
  Object.defineProperty(exports, "flatIndex", { enumerable: true, get: function () { return watermark_js_1.flatIndex; } });
@@ -9,6 +9,9 @@ var journal_js_1 = require("./journal.js");
9
9
  Object.defineProperty(exports, "LeaseJournal", { enumerable: true, get: function () { return journal_js_1.LeaseJournal; } });
10
10
  var local_js_1 = require("./local.js");
11
11
  Object.defineProperty(exports, "LocalLeaseProvider", { enumerable: true, get: function () { return local_js_1.LocalLeaseProvider; } });
12
+ var seed_js_1 = require("./seed.js");
13
+ Object.defineProperty(exports, "seedLeaseWatermark", { enumerable: true, get: function () { return seed_js_1.seedLeaseWatermark; } });
14
+ Object.defineProperty(exports, "exportLeaseWatermark", { enumerable: true, get: function () { return seed_js_1.exportLeaseWatermark; } });
12
15
  var axia_js_1 = require("./axia.js");
13
16
  Object.defineProperty(exports, "AxiaLeaseProvider", { enumerable: true, get: function () { return axia_js_1.AxiaLeaseProvider; } });
14
17
  var hybrid_js_1 = require("./hybrid.js");
@@ -27,6 +30,8 @@ Object.defineProperty(exports, "deviceSlotForAddressIndex", { enumerable: true,
27
30
  var errors_js_1 = require("./errors.js");
28
31
  Object.defineProperty(exports, "WatermarkMonotonicityError", { enumerable: true, get: function () { return errors_js_1.WatermarkMonotonicityError; } });
29
32
  Object.defineProperty(exports, "WatermarkExhaustedError", { enumerable: true, get: function () { return errors_js_1.WatermarkExhaustedError; } });
33
+ Object.defineProperty(exports, "WatermarkSeedRegressionError", { enumerable: true, get: function () { return errors_js_1.WatermarkSeedRegressionError; } });
34
+ Object.defineProperty(exports, "WatermarkSeedRequiredError", { enumerable: true, get: function () { return errors_js_1.WatermarkSeedRequiredError; } });
30
35
  Object.defineProperty(exports, "LeaseNotFoundError", { enumerable: true, get: function () { return errors_js_1.LeaseNotFoundError; } });
31
36
  Object.defineProperty(exports, "IndicesUnavailableError", { enumerable: true, get: function () { return errors_js_1.IndicesUnavailableError; } });
32
37
  Object.defineProperty(exports, "PersonalLeaseNodeNotConfiguredError", { enumerable: true, get: function () { return errors_js_1.PersonalLeaseNodeNotConfiguredError; } });
package/dist/journal.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
- * LeaseJournal — append-only audit log for WOTS key-use events.
3
- * Preserves the full history: reserved → committed | burned | reserved-expired.
2
+ * LeaseJournal — physically append-only audit log for WOTS key-use events.
3
+ *
4
+ * Layout v1 stores one immutable record per sequence. The legacy whole-array
5
+ * key is retained as migration input/forensic history but is never rewritten.
6
+ * The SHA-256 chain format is intentionally unchanged (RFC-007 OQ3).
4
7
  */
5
8
  import type { StorageAdapter, LoggerAdapter } from '@totemsdk/core';
6
9
  import type { JournalEntry } from './types.js';
@@ -8,11 +11,18 @@ export declare class LeaseJournal {
8
11
  private readonly storage;
9
12
  private readonly logger;
10
13
  private entries;
11
- private _initialized;
14
+ private initialized;
15
+ private initializePromise;
16
+ private appendTail;
12
17
  constructor(storage: StorageAdapter, logger?: LoggerAdapter);
13
18
  initialize(): Promise<void>;
19
+ private initializeOnce;
20
+ private readLegacyEntries;
21
+ private readPhysicalRecords;
22
+ private writeRecord;
14
23
  append(entry: JournalEntry): Promise<void>;
15
- private _verifyChain;
24
+ private appendOne;
25
+ private ensureInitialized;
16
26
  getAll(): JournalEntry[];
17
27
  getByTree(treeId: string): JournalEntry[];
18
28
  getByReservation(reservationId: string): JournalEntry | undefined;