@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
@@ -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
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Device keyspace partitioning — Layer 2.
3
+ *
4
+ * Deterministic address-index range assignment per device slot.
5
+ * Does NOT accept a raw seed; the caller derives the TreeKey in @totemsdk/core.
6
+ *
7
+ * Layout (8 device slots × 8 addresses each = 64 total):
8
+ * device 0 → addressIndex 0–7
9
+ * device 1 → addressIndex 8–15
10
+ * device 2 → addressIndex 16–23
11
+ * ...
12
+ */
13
+ import type { DeviceKeyRange } from './types.js';
14
+ export declare function allocateDeviceRange(params: {
15
+ deviceSlot: number;
16
+ deviceId: string;
17
+ }): DeviceKeyRange;
18
+ export declare function deviceSlotForAddressIndex(addressIndex: number): number;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Device keyspace partitioning — Layer 2.
4
+ *
5
+ * Deterministic address-index range assignment per device slot.
6
+ * Does NOT accept a raw seed; the caller derives the TreeKey in @totemsdk/core.
7
+ *
8
+ * Layout (8 device slots × 8 addresses each = 64 total):
9
+ * device 0 → addressIndex 0–7
10
+ * device 1 → addressIndex 8–15
11
+ * device 2 → addressIndex 16–23
12
+ * ...
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.allocateDeviceRange = allocateDeviceRange;
16
+ exports.deviceSlotForAddressIndex = deviceSlotForAddressIndex;
17
+ const ADDRESSES_PER_DEVICE = 8;
18
+ const MAX_DEVICE_SLOTS = 8;
19
+ function allocateDeviceRange(params) {
20
+ if (params.deviceSlot < 0 || params.deviceSlot >= MAX_DEVICE_SLOTS) {
21
+ throw new RangeError(`Device slot must be 0–${MAX_DEVICE_SLOTS - 1}, got ${params.deviceSlot}`);
22
+ }
23
+ const startAddressIndex = params.deviceSlot * ADDRESSES_PER_DEVICE;
24
+ const endAddressIndex = startAddressIndex + ADDRESSES_PER_DEVICE - 1;
25
+ return {
26
+ deviceId: params.deviceId,
27
+ startAddressIndex,
28
+ endAddressIndex,
29
+ addressCount: ADDRESSES_PER_DEVICE,
30
+ };
31
+ }
32
+ function deviceSlotForAddressIndex(addressIndex) {
33
+ return Math.floor(addressIndex / ADDRESSES_PER_DEVICE);
34
+ }
@@ -0,0 +1,71 @@
1
+ import type { SigningIndices } from './types.js';
2
+ export declare class WatermarkMonotonicityError extends Error {
3
+ constructor(message: string);
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
+ }
25
+ export declare class WatermarkExhaustedError extends Error {
26
+ readonly treeId: string;
27
+ constructor(treeId: string);
28
+ }
29
+ export declare class LeaseNotFoundError extends Error {
30
+ readonly reservationId: string;
31
+ constructor(reservationId: string);
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
+ }
39
+ export declare class IndicesUnavailableError extends Error {
40
+ readonly treeId: string;
41
+ readonly indices: SigningIndices;
42
+ constructor(treeId: string, indices: SigningIndices);
43
+ }
44
+ export declare class PersonalLeaseNodeNotConfiguredError extends Error {
45
+ constructor();
46
+ }
47
+ export declare class P2PQuorumNotImplementedError extends Error {
48
+ constructor();
49
+ }
50
+ export declare class OnchainWatermarkNotImplementedError extends Error {
51
+ constructor();
52
+ }
53
+ export declare class QuorumUnavailableError extends Error {
54
+ readonly required: number;
55
+ readonly available: number;
56
+ constructor(required: number, available: number);
57
+ }
58
+ export declare class QuorumConflictError extends Error {
59
+ readonly treeId: string;
60
+ readonly indices: SigningIndices;
61
+ constructor(treeId: string, indices: SigningIndices);
62
+ }
63
+ export declare class OnchainWatermarkError extends Error {
64
+ constructor(message: string);
65
+ }
66
+ export declare class DeviceRangeViolationError extends Error {
67
+ readonly addressIndex: number;
68
+ readonly allowedStart: number;
69
+ readonly allowedEnd: number;
70
+ constructor(addressIndex: number, allowedStart: number, allowedEnd: number);
71
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
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.InvalidLeaseTransitionError = exports.LeaseNotFoundError = exports.WatermarkExhaustedError = exports.WatermarkSeedRequiredError = exports.WatermarkSeedRegressionError = exports.WatermarkMonotonicityError = void 0;
4
+ class WatermarkMonotonicityError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'WatermarkMonotonicityError';
8
+ }
9
+ }
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;
42
+ class WatermarkExhaustedError extends Error {
43
+ constructor(treeId) {
44
+ super(`WOTS keyspace exhausted for tree: ${treeId}`);
45
+ this.treeId = treeId;
46
+ this.name = 'WatermarkExhaustedError';
47
+ }
48
+ }
49
+ exports.WatermarkExhaustedError = WatermarkExhaustedError;
50
+ class LeaseNotFoundError extends Error {
51
+ constructor(reservationId) {
52
+ super(`Lease reservation not found: ${reservationId}`);
53
+ this.reservationId = reservationId;
54
+ this.name = 'LeaseNotFoundError';
55
+ }
56
+ }
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;
68
+ class IndicesUnavailableError extends Error {
69
+ constructor(treeId, indices) {
70
+ super(`Indices (${indices.addressIndex}, ${indices.l1}, ${indices.l2}) are already unavailable for tree: ${treeId}`);
71
+ this.treeId = treeId;
72
+ this.indices = indices;
73
+ this.name = 'IndicesUnavailableError';
74
+ }
75
+ }
76
+ exports.IndicesUnavailableError = IndicesUnavailableError;
77
+ class PersonalLeaseNodeNotConfiguredError extends Error {
78
+ constructor() {
79
+ super('PersonalLeaseNodeProvider requires a running lookup node. ' +
80
+ 'Deploy @totemsdk/lookup-node and configure { nodeUrl, nodePubkey }.');
81
+ this.name = 'PersonalLeaseNodeNotConfiguredError';
82
+ }
83
+ }
84
+ exports.PersonalLeaseNodeNotConfiguredError = PersonalLeaseNodeNotConfiguredError;
85
+ class P2PQuorumNotImplementedError extends Error {
86
+ constructor() {
87
+ super('P2PQuorumLeaseProvider is not yet implemented.');
88
+ this.name = 'P2PQuorumNotImplementedError';
89
+ }
90
+ }
91
+ exports.P2PQuorumNotImplementedError = P2PQuorumNotImplementedError;
92
+ class OnchainWatermarkNotImplementedError extends Error {
93
+ constructor() {
94
+ super('OnchainWatermarkProvider is not yet implemented.');
95
+ this.name = 'OnchainWatermarkNotImplementedError';
96
+ }
97
+ }
98
+ exports.OnchainWatermarkNotImplementedError = OnchainWatermarkNotImplementedError;
99
+ class QuorumUnavailableError extends Error {
100
+ constructor(required, available) {
101
+ super(`Quorum not reached: ${available}/${required} peers attested. ` +
102
+ 'The reservation was burned locally — retry when more peers are reachable.');
103
+ this.required = required;
104
+ this.available = available;
105
+ this.name = 'QuorumUnavailableError';
106
+ }
107
+ }
108
+ exports.QuorumUnavailableError = QuorumUnavailableError;
109
+ class QuorumConflictError extends Error {
110
+ constructor(treeId, indices) {
111
+ super(`Quorum conflict for tree ${treeId} at (${indices.addressIndex}, ${indices.l1}, ${indices.l2}): ` +
112
+ 'a peer already holds a reservation for these indices.');
113
+ this.treeId = treeId;
114
+ this.indices = indices;
115
+ this.name = 'QuorumConflictError';
116
+ }
117
+ }
118
+ exports.QuorumConflictError = QuorumConflictError;
119
+ class OnchainWatermarkError extends Error {
120
+ constructor(message) {
121
+ super(message);
122
+ this.name = 'OnchainWatermarkError';
123
+ }
124
+ }
125
+ exports.OnchainWatermarkError = OnchainWatermarkError;
126
+ class DeviceRangeViolationError extends Error {
127
+ constructor(addressIndex, allowedStart, allowedEnd) {
128
+ super(`Address index ${addressIndex} is outside device range [${allowedStart}, ${allowedEnd}]`);
129
+ this.addressIndex = addressIndex;
130
+ this.allowedStart = allowedStart;
131
+ this.allowedEnd = allowedEnd;
132
+ this.name = 'DeviceRangeViolationError';
133
+ }
134
+ }
135
+ exports.DeviceRangeViolationError = DeviceRangeViolationError;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * HybridLeaseProvider — recommended production default.
3
+ *
4
+ * Always uses local first, then gates on node cert and/or quorum attestation
5
+ * for transactions above a configurable value threshold.
6
+ */
7
+ import type { WotsLeaseProvider, ReserveParams, LeaseReservation, LeaseCertificate, LocalWatermark, SyncResult, CertificateSigner } from './types.js';
8
+ import type { LocalLeaseProvider } from './local.js';
9
+ import type { PersonalLeaseNodeProvider } from './stubs.js';
10
+ import type { P2PQuorumLeaseProvider } from './stubs.js';
11
+ import type { OnchainWatermarkProvider } from './stubs.js';
12
+ export interface HybridLeaseProviderConfig {
13
+ local: LocalLeaseProvider;
14
+ node?: PersonalLeaseNodeProvider;
15
+ quorum?: P2PQuorumLeaseProvider;
16
+ onchain?: OnchainWatermarkProvider;
17
+ threshold?: number;
18
+ /** Identity that authenticates locally-assembled quorum certificates. */
19
+ certificateSigner?: CertificateSigner;
20
+ }
21
+ export declare class HybridLeaseProvider implements WotsLeaseProvider {
22
+ private readonly local;
23
+ private readonly node?;
24
+ private readonly quorum?;
25
+ private readonly onchain?;
26
+ private readonly threshold;
27
+ private readonly certificateSigner?;
28
+ constructor(config: HybridLeaseProviderConfig);
29
+ private isHighValue;
30
+ reserveKeyUse(params: ReserveParams): Promise<LeaseReservation>;
31
+ commitKeyUse(reservationId: string, txId: string): Promise<void>;
32
+ burnReservation(reservationId: string, reason: string): Promise<void>;
33
+ getLocalWatermark(treeId: string): Promise<LocalWatermark>;
34
+ publishWatermark(treeId: string): Promise<void>;
35
+ syncLeaseJournal(): Promise<SyncResult>;
36
+ verifyLeaseCertificate(cert?: LeaseCertificate): Promise<boolean>;
37
+ }
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * HybridLeaseProvider — recommended production default.
4
+ *
5
+ * Always uses local first, then gates on node cert and/or quorum attestation
6
+ * for transactions above a configurable value threshold.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.HybridLeaseProvider = void 0;
10
+ const certificate_js_1 = require("./certificate.js");
11
+ class HybridLeaseProvider {
12
+ constructor(config) {
13
+ this.local = config.local;
14
+ this.node = config.node;
15
+ this.quorum = config.quorum;
16
+ this.onchain = config.onchain;
17
+ this.threshold = config.threshold ?? Infinity;
18
+ this.certificateSigner = config.certificateSigner;
19
+ }
20
+ isHighValue(params) {
21
+ if (!params.valueHint)
22
+ return false;
23
+ const val = parseFloat(params.valueHint);
24
+ return !isNaN(val) && val >= this.threshold;
25
+ }
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
+ }
38
+ const reservation = await this.local.reserveKeyUse(params);
39
+ if (this.isHighValue(params)) {
40
+ // Layer 4 — quorum attestation over the SAME local slot.
41
+ if (this.quorum) {
42
+ try {
43
+ const attestations = await this.quorum.attestKeyUse(params, reservation.indices);
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,
57
+ };
58
+ certificate.signature = this.certificateSigner
59
+ ? await (0, certificate_js_1.signCertificate)(this.certificateSigner, certificate)
60
+ : '';
61
+ return { ...reservation, certificate };
62
+ }
63
+ catch {
64
+ // Quorum unavailable — fall through to node / local only.
65
+ }
66
+ }
67
+ // Layer 3 — personal lookup node certificate.
68
+ if (this.node) {
69
+ try {
70
+ const nodeReservation = await this.node.reserveKeyUse(params);
71
+ return { ...reservation, certificate: nodeReservation.certificate };
72
+ }
73
+ catch {
74
+ // Node unavailable — continue with local only
75
+ }
76
+ }
77
+ }
78
+ return reservation;
79
+ }
80
+ commitKeyUse(reservationId, txId) {
81
+ return this.local.commitKeyUse(reservationId, txId);
82
+ }
83
+ burnReservation(reservationId, reason) {
84
+ return this.local.burnReservation(reservationId, reason);
85
+ }
86
+ getLocalWatermark(treeId) {
87
+ return this.local.getLocalWatermark(treeId);
88
+ }
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
+ }
101
+ if (this.node) {
102
+ try {
103
+ await this.node.publishWatermark(treeId);
104
+ return;
105
+ }
106
+ catch {
107
+ // fall through
108
+ }
109
+ }
110
+ await this.local.publishWatermark(treeId);
111
+ }
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
+ }
123
+ if (this.node) {
124
+ try {
125
+ return await this.node.syncLeaseJournal();
126
+ }
127
+ catch {
128
+ // fall through
129
+ }
130
+ }
131
+ return this.local.syncLeaseJournal();
132
+ }
133
+ async verifyLeaseCertificate(cert) {
134
+ if (!cert)
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
+ }
140
+ if (this.node)
141
+ return this.node.verifyLeaseCertificate(cert);
142
+ return false;
143
+ }
144
+ }
145
+ exports.HybridLeaseProvider = HybridLeaseProvider;
@@ -0,0 +1,17 @@
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
+ export { WotsWatermarkStore, flatIndex, fromFlatIndex, } from './watermark.js';
3
+ export { LeaseJournal } from './journal.js';
4
+ export { LocalLeaseProvider } from './local.js';
5
+ export { seedLeaseWatermark, exportLeaseWatermark } from './seed.js';
6
+ export type { SeedLeaseWatermarkParams } from './seed.js';
7
+ export { AxiaLeaseProvider } from './axia.js';
8
+ export type { AxiaLeaseProviderConfig } from './axia.js';
9
+ export { HybridLeaseProvider } from './hybrid.js';
10
+ export type { HybridLeaseProviderConfig } from './hybrid.js';
11
+ export { PersonalLeaseNodeProvider, P2PQuorumLeaseProvider, OnchainWatermarkProvider, } from './stubs.js';
12
+ export { P2PQuorumLeaseProvider as QuorumLeaseProvider } from './quorum.js';
13
+ export { OnchainWatermarkProvider as ChainWatermarkProvider } from './onchain.js';
14
+ export { allocateDeviceRange, deviceSlotForAddressIndex } from './device.js';
15
+ export { WatermarkMonotonicityError, WatermarkExhaustedError, WatermarkSeedRegressionError, WatermarkSeedRequiredError, LeaseNotFoundError, IndicesUnavailableError, PersonalLeaseNodeNotConfiguredError, P2PQuorumNotImplementedError, OnchainWatermarkNotImplementedError, QuorumUnavailableError, QuorumConflictError, OnchainWatermarkError, DeviceRangeViolationError, } from './errors.js';
16
+ export { ReplicatedLeaseJournalBackup } from './replicated-backup.js';
17
+ export type { LeaseJournalRestoreTarget } from './replicated-backup.js';
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReplicatedLeaseJournalBackup = 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
+ var watermark_js_1 = require("./watermark.js");
5
+ Object.defineProperty(exports, "WotsWatermarkStore", { enumerable: true, get: function () { return watermark_js_1.WotsWatermarkStore; } });
6
+ Object.defineProperty(exports, "flatIndex", { enumerable: true, get: function () { return watermark_js_1.flatIndex; } });
7
+ Object.defineProperty(exports, "fromFlatIndex", { enumerable: true, get: function () { return watermark_js_1.fromFlatIndex; } });
8
+ var journal_js_1 = require("./journal.js");
9
+ Object.defineProperty(exports, "LeaseJournal", { enumerable: true, get: function () { return journal_js_1.LeaseJournal; } });
10
+ var local_js_1 = require("./local.js");
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; } });
15
+ var axia_js_1 = require("./axia.js");
16
+ Object.defineProperty(exports, "AxiaLeaseProvider", { enumerable: true, get: function () { return axia_js_1.AxiaLeaseProvider; } });
17
+ var hybrid_js_1 = require("./hybrid.js");
18
+ Object.defineProperty(exports, "HybridLeaseProvider", { enumerable: true, get: function () { return hybrid_js_1.HybridLeaseProvider; } });
19
+ var stubs_js_1 = require("./stubs.js");
20
+ Object.defineProperty(exports, "PersonalLeaseNodeProvider", { enumerable: true, get: function () { return stubs_js_1.PersonalLeaseNodeProvider; } });
21
+ Object.defineProperty(exports, "P2PQuorumLeaseProvider", { enumerable: true, get: function () { return stubs_js_1.P2PQuorumLeaseProvider; } });
22
+ Object.defineProperty(exports, "OnchainWatermarkProvider", { enumerable: true, get: function () { return stubs_js_1.OnchainWatermarkProvider; } });
23
+ var quorum_js_1 = require("./quorum.js");
24
+ Object.defineProperty(exports, "QuorumLeaseProvider", { enumerable: true, get: function () { return quorum_js_1.P2PQuorumLeaseProvider; } });
25
+ var onchain_js_1 = require("./onchain.js");
26
+ Object.defineProperty(exports, "ChainWatermarkProvider", { enumerable: true, get: function () { return onchain_js_1.OnchainWatermarkProvider; } });
27
+ var device_js_1 = require("./device.js");
28
+ Object.defineProperty(exports, "allocateDeviceRange", { enumerable: true, get: function () { return device_js_1.allocateDeviceRange; } });
29
+ Object.defineProperty(exports, "deviceSlotForAddressIndex", { enumerable: true, get: function () { return device_js_1.deviceSlotForAddressIndex; } });
30
+ var errors_js_1 = require("./errors.js");
31
+ Object.defineProperty(exports, "WatermarkMonotonicityError", { enumerable: true, get: function () { return errors_js_1.WatermarkMonotonicityError; } });
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; } });
35
+ Object.defineProperty(exports, "LeaseNotFoundError", { enumerable: true, get: function () { return errors_js_1.LeaseNotFoundError; } });
36
+ Object.defineProperty(exports, "IndicesUnavailableError", { enumerable: true, get: function () { return errors_js_1.IndicesUnavailableError; } });
37
+ Object.defineProperty(exports, "PersonalLeaseNodeNotConfiguredError", { enumerable: true, get: function () { return errors_js_1.PersonalLeaseNodeNotConfiguredError; } });
38
+ Object.defineProperty(exports, "P2PQuorumNotImplementedError", { enumerable: true, get: function () { return errors_js_1.P2PQuorumNotImplementedError; } });
39
+ Object.defineProperty(exports, "OnchainWatermarkNotImplementedError", { enumerable: true, get: function () { return errors_js_1.OnchainWatermarkNotImplementedError; } });
40
+ Object.defineProperty(exports, "QuorumUnavailableError", { enumerable: true, get: function () { return errors_js_1.QuorumUnavailableError; } });
41
+ Object.defineProperty(exports, "QuorumConflictError", { enumerable: true, get: function () { return errors_js_1.QuorumConflictError; } });
42
+ Object.defineProperty(exports, "OnchainWatermarkError", { enumerable: true, get: function () { return errors_js_1.OnchainWatermarkError; } });
43
+ Object.defineProperty(exports, "DeviceRangeViolationError", { enumerable: true, get: function () { return errors_js_1.DeviceRangeViolationError; } });
44
+ var replicated_backup_js_1 = require("./replicated-backup.js");
45
+ Object.defineProperty(exports, "ReplicatedLeaseJournalBackup", { enumerable: true, get: function () { return replicated_backup_js_1.ReplicatedLeaseJournalBackup; } });
@@ -0,0 +1,30 @@
1
+ /**
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).
7
+ */
8
+ import type { StorageAdapter, LoggerAdapter } from '@totemsdk/core';
9
+ import type { JournalEntry } from './types.js';
10
+ export declare class LeaseJournal {
11
+ private readonly storage;
12
+ private readonly logger;
13
+ private entries;
14
+ private initialized;
15
+ private initializePromise;
16
+ private appendTail;
17
+ constructor(storage: StorageAdapter, logger?: LoggerAdapter);
18
+ initialize(): Promise<void>;
19
+ private initializeOnce;
20
+ private readLegacyEntries;
21
+ private readPhysicalRecords;
22
+ private writeRecord;
23
+ append(entry: JournalEntry): Promise<void>;
24
+ private appendOne;
25
+ private ensureInitialized;
26
+ getAll(): JournalEntry[];
27
+ getByTree(treeId: string): JournalEntry[];
28
+ getByReservation(reservationId: string): JournalEntry | undefined;
29
+ clear(): Promise<void>;
30
+ }