zklicensing 0.1.0-beta.2 → 0.1.0-beta.4
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/README.md +335 -97
- package/build/appsLookup.d.ts +14 -0
- package/build/appsLookup.d.ts.map +1 -0
- package/build/appsLookup.js +12 -0
- package/build/appsLookup.js.map +1 -0
- package/build/archiveBootstrap.d.ts +15 -1
- package/build/archiveBootstrap.d.ts.map +1 -0
- package/build/archiveBootstrap.js +62 -9
- package/build/archiveBootstrap.js.map +1 -0
- package/build/bindClient.d.ts +51 -0
- package/build/bindClient.d.ts.map +1 -0
- package/build/bindClient.js +115 -0
- package/build/bindClient.js.map +1 -0
- package/build/buyClient.d.ts +10 -3
- package/build/buyClient.d.ts.map +1 -0
- package/build/buyClient.js +33 -15
- package/build/buyClient.js.map +1 -0
- package/build/contractInterface.d.ts +11 -5
- package/build/contractInterface.d.ts.map +1 -0
- package/build/contractInterface.js +82 -17
- package/build/contractInterface.js.map +1 -0
- package/build/countryStats.d.ts +6 -1
- package/build/countryStats.d.ts.map +1 -0
- package/build/countryStats.js +33 -9
- package/build/countryStats.js.map +1 -0
- package/build/deployClient.d.ts.map +1 -0
- package/build/deployClient.js +1 -1
- package/build/deployClient.js.map +1 -0
- package/build/deviceIdentity.d.ts +68 -0
- package/build/deviceIdentity.d.ts.map +1 -0
- package/build/deviceIdentity.js +209 -0
- package/build/deviceIdentity.js.map +1 -0
- package/build/freezeClient.d.ts.map +1 -0
- package/build/freezeClient.js +1 -1
- package/build/freezeClient.js.map +1 -0
- package/build/generationClient.d.ts +1 -0
- package/build/generationClient.d.ts.map +1 -0
- package/build/generationClient.js +2 -2
- package/build/generationClient.js.map +1 -0
- package/build/index.d.ts +27 -37
- package/build/index.d.ts.map +1 -0
- package/build/index.js +85 -116
- package/build/index.js.map +1 -0
- package/build/licenseLeaf.d.ts +76 -0
- package/build/licenseLeaf.d.ts.map +1 -0
- package/build/licenseLeaf.js +189 -0
- package/build/licenseLeaf.js.map +1 -0
- package/build/licenseRecord.d.ts +20 -1
- package/build/licenseRecord.d.ts.map +1 -0
- package/build/licenseRecord.js +1 -1
- package/build/licenseRecord.js.map +1 -0
- package/build/licenseStore.d.ts +17 -2
- package/build/licenseStore.d.ts.map +1 -0
- package/build/licenseStore.js +167 -7
- package/build/licenseStore.js.map +1 -0
- package/build/manifestVerify.d.ts.map +1 -0
- package/build/manifestVerify.js +8 -3
- package/build/manifestVerify.js.map +1 -0
- package/build/migrateClient.d.ts +6 -21
- package/build/migrateClient.d.ts.map +1 -0
- package/build/migrateClient.js +30 -17
- package/build/migrateClient.js.map +1 -0
- package/build/onChainRoot.d.ts +67 -0
- package/build/onChainRoot.d.ts.map +1 -0
- package/build/onChainRoot.js +87 -0
- package/build/onChainRoot.js.map +1 -0
- package/build/ownershipLicenseHash.d.ts +25 -0
- package/build/ownershipLicenseHash.d.ts.map +1 -0
- package/build/ownershipLicenseHash.js +29 -0
- package/build/ownershipLicenseHash.js.map +1 -0
- package/build/ownershipSignature.d.ts +122 -0
- package/build/ownershipSignature.d.ts.map +1 -0
- package/build/ownershipSignature.js +244 -0
- package/build/ownershipSignature.js.map +1 -0
- package/build/proveErrors.d.ts +35 -0
- package/build/proveErrors.d.ts.map +1 -0
- package/build/proveErrors.js +49 -0
- package/build/proveErrors.js.map +1 -0
- package/build/refundClient.d.ts +2 -11
- package/build/refundClient.d.ts.map +1 -0
- package/build/refundClient.js +35 -17
- package/build/refundClient.js.map +1 -0
- package/build/renewClient.d.ts +4 -1
- package/build/renewClient.d.ts.map +1 -0
- package/build/renewClient.js +11 -4
- package/build/renewClient.js.map +1 -0
- package/build/seatName.d.ts +8 -0
- package/build/seatName.d.ts.map +1 -0
- package/build/seatName.js +136 -0
- package/build/seatName.js.map +1 -0
- package/build/statsClient.d.ts +47 -4
- package/build/statsClient.d.ts.map +1 -0
- package/build/statsClient.js +44 -5
- package/build/statsClient.js.map +1 -0
- package/build/trustAnchorVerify.d.ts +48 -0
- package/build/trustAnchorVerify.d.ts.map +1 -0
- package/build/trustAnchorVerify.js +431 -0
- package/build/trustAnchorVerify.js.map +1 -0
- package/build/trustAnchorVerifyCli.d.ts +35 -0
- package/build/trustAnchorVerifyCli.d.ts.map +1 -0
- package/build/trustAnchorVerifyCli.js +177 -0
- package/build/trustAnchorVerifyCli.js.map +1 -0
- package/build/txBroadcastClient.d.ts +26 -0
- package/build/txBroadcastClient.d.ts.map +1 -0
- package/build/txBroadcastClient.js +20 -0
- package/build/txBroadcastClient.js.map +1 -0
- package/build/unbindClient.d.ts +56 -0
- package/build/unbindClient.d.ts.map +1 -0
- package/build/unbindClient.js +113 -0
- package/build/unbindClient.js.map +1 -0
- package/build/vendorAccountClient.d.ts +56 -0
- package/build/vendorAccountClient.d.ts.map +1 -0
- package/build/vendorAccountClient.js +47 -0
- package/build/vendorAccountClient.js.map +1 -0
- package/build/verifyCore.d.ts +1 -1
- package/build/verifyCore.d.ts.map +1 -0
- package/build/verifyCore.js +11 -4
- package/build/verifyCore.js.map +1 -0
- package/build/verifyOffline.d.ts +85 -0
- package/build/verifyOffline.d.ts.map +1 -0
- package/build/verifyOffline.js +139 -0
- package/build/verifyOffline.js.map +1 -0
- package/build/verifyService.d.ts.map +1 -0
- package/build/verifyService.js +582 -455
- package/build/verifyService.js.map +1 -0
- package/build/verifyWithAncestors.d.ts +18 -0
- package/build/verifyWithAncestors.d.ts.map +1 -0
- package/build/verifyWithAncestors.js +119 -0
- package/build/verifyWithAncestors.js.map +1 -0
- package/build/zipToState.d.ts +2 -0
- package/build/zipToState.d.ts.map +1 -0
- package/build/zipToState.js +94 -0
- package/build/zipToState.js.map +1 -0
- package/package.json +57 -13
- package/src/appsLookup.ts +48 -0
- package/src/archiveBootstrap.ts +342 -0
- package/src/bindClient.ts +175 -0
- package/src/buyClient.ts +304 -0
- package/src/contractInterface.d.ts +61 -0
- package/src/contractInterface.ts +313 -0
- package/src/countryStats.ts +171 -0
- package/src/deployClient.ts +388 -0
- package/src/deviceIdentity.ts +249 -0
- package/src/freezeClient.ts +117 -0
- package/src/generationClient.ts +165 -0
- package/src/index.ts +329 -0
- package/src/licenseLeaf.ts +221 -0
- package/src/licenseRecord.ts +108 -0
- package/src/licenseStore.ts +538 -0
- package/src/manifestVerify.ts +111 -0
- package/src/migrateClient.ts +84 -0
- package/src/onChainRoot.ts +107 -0
- package/src/ownershipLicenseHash.ts +31 -0
- package/src/ownershipSignature.ts +354 -0
- package/src/proveErrors.ts +70 -0
- package/src/refundClient.ts +158 -0
- package/src/renewClient.ts +214 -0
- package/src/seatName.ts +165 -0
- package/src/statsClient.ts +235 -0
- package/src/trustAnchorVerify.ts +548 -0
- package/src/trustAnchorVerifyCli.ts +179 -0
- package/src/txBroadcastClient.ts +48 -0
- package/src/unbindClient.ts +149 -0
- package/src/vendorAccountClient.ts +110 -0
- package/src/verifyCore.ts +92 -0
- package/src/verifyOffline.ts +212 -0
- package/src/verifyService.ts +969 -0
- package/src/verifyWithAncestors.ts +153 -0
- package/src/zipToState.ts +94 -0
- package/build/licenseProof.d.ts +0 -26
- package/build/licenseProof.js +0 -41
- package/build/ownership.d.ts +0 -34
- package/build/ownership.js +0 -147
package/build/index.js
CHANGED
|
@@ -1,54 +1,43 @@
|
|
|
1
1
|
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
-
//
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
3
|
/**
|
|
4
4
|
* verifyLicense — client SDK.
|
|
5
5
|
*
|
|
6
|
-
* Calls the
|
|
7
|
-
* license has closed; from that point on returns from a
|
|
8
|
-
* never touches the network again. The refund-window
|
|
9
|
-
* side: the
|
|
10
|
-
* response, computed from its own record of
|
|
11
|
-
* against the on-chain `currentSlot`.
|
|
12
|
-
*
|
|
13
|
-
* file, so a user who forges
|
|
14
|
-
* branch prematurely.
|
|
6
|
+
* Calls the licensing service's GET /verify until the on-chain refund
|
|
7
|
+
* window for the license has closed; from that point on returns from a
|
|
8
|
+
* local anchor and never touches the network again. The refund-window
|
|
9
|
+
* decision is server-side: the service returns `refundWindowClosed:
|
|
10
|
+
* boolean` on its /verify response, computed from its own record of
|
|
11
|
+
* `purchaseSlot + REFUND_WINDOW_N` against the on-chain `currentSlot`.
|
|
12
|
+
* The SDK gates anchor writes on that boolean and never trusts a
|
|
13
|
+
* `purchaseSlot` supplied by the local proof file, so a user who forges
|
|
14
|
+
* their proof metadata cannot force the offline branch prematurely.
|
|
15
15
|
*
|
|
16
16
|
* Anchor purity: every field persisted in the Anchor derives from the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* service's /verify response — `anchoredSlot`, `expirySlot`. The only
|
|
18
|
+
* client-supplied input to the offline branch is `proof.expirySlot`,
|
|
19
|
+
* used solely as the loadAnchor binding key. A tampered proof file
|
|
20
|
+
* therefore never gets a wrong answer; it gets no offline branch at all
|
|
21
|
+
* and falls through to the licensing service on every call.
|
|
22
22
|
*/
|
|
23
23
|
import { REFUND_WINDOW_N, GRACE_PERIOD_N, MS_PER_SLOT_N, SLOTS_PER_DAY_N, } from './contractInterface.js';
|
|
24
24
|
const MS_PER_SLOT = MS_PER_SLOT_N;
|
|
25
25
|
const SLOTS_PER_DAY = SLOTS_PER_DAY_N;
|
|
26
|
+
// The one and only verify endpoint. Every issued license routes here
|
|
27
|
+
// regardless of network — the platform verifier resolves devnet vs
|
|
28
|
+
// mainnet from the zkAppAddress (each deployment is unique per network).
|
|
29
|
+
// Tests inject a mock via VerifyOptions.fetcher, not by rewriting this URL.
|
|
30
|
+
export const DEFAULT_VERIFIER_URL = 'https://zklicensing.com/api/verify';
|
|
31
|
+
// Anchor records are keyed by (zkAppAddress, licenseHash). zkAppAddress
|
|
32
|
+
// is generation-scoped by construction: each hardfork migration deploys
|
|
33
|
+
// the LicensingApp at a fresh address (see keeper's finalizeRedeploy
|
|
34
|
+
// and AppRecord.deploymentHistory[]), so keying on it implicitly
|
|
35
|
+
// captures generation. An anchor written pre-migration is orphaned by
|
|
36
|
+
// the new address on next verify and re-created cleanly — no
|
|
37
|
+
// cross-generation carry-over.
|
|
26
38
|
const ANCHOR_KEY_PREFIX = 'zklic_anchor_';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return `${ACTIVATION_KEY_PREFIX}${zkAppAddress}_${licenseHash}`;
|
|
30
|
-
}
|
|
31
|
-
export function loadActivation(storage, zkAppAddress, licenseHash, nowMs = Date.now()) {
|
|
32
|
-
try {
|
|
33
|
-
const raw = storage.getItem(activationKey(zkAppAddress, licenseHash));
|
|
34
|
-
if (!raw)
|
|
35
|
-
return null;
|
|
36
|
-
const rec = JSON.parse(raw);
|
|
37
|
-
if (nowMs > rec.expiresAt)
|
|
38
|
-
return null;
|
|
39
|
-
return rec;
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
export function saveActivation(storage, zkAppAddress, licenseHash, rec) {
|
|
46
|
-
try {
|
|
47
|
-
storage.setItem(activationKey(zkAppAddress, licenseHash), JSON.stringify(rec));
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
/* quota / read-only — non-fatal */
|
|
51
|
-
}
|
|
39
|
+
function anchorKey(zkAppAddress, licenseHash) {
|
|
40
|
+
return `${ANCHOR_KEY_PREFIX}${zkAppAddress}_${licenseHash}`;
|
|
52
41
|
}
|
|
53
42
|
function defaultStorage() {
|
|
54
43
|
if (typeof globalThis !== 'undefined' && globalThis.localStorage) {
|
|
@@ -56,19 +45,20 @@ function defaultStorage() {
|
|
|
56
45
|
}
|
|
57
46
|
return null;
|
|
58
47
|
}
|
|
59
|
-
function loadAnchor(storage, licenseHash, expirySlot) {
|
|
48
|
+
function loadAnchor(storage, zkAppAddress, licenseHash, expirySlot) {
|
|
60
49
|
try {
|
|
61
|
-
const raw = storage.getItem(
|
|
50
|
+
const raw = storage.getItem(anchorKey(zkAppAddress, licenseHash));
|
|
62
51
|
if (!raw)
|
|
63
52
|
return null;
|
|
64
53
|
const a = JSON.parse(raw);
|
|
65
|
-
// `a.expirySlot` is the
|
|
66
|
-
// `expirySlot` argument is the LOCAL proof file's value. Match
|
|
67
|
-
// file agrees with chain truth as of anchoring ⇒ offline is
|
|
68
|
-
// Mismatch has two honest meanings:
|
|
54
|
+
// `a.expirySlot` is the SERVICE's value captured at anchor time;
|
|
55
|
+
// the `expirySlot` argument is the LOCAL proof file's value. Match
|
|
56
|
+
// ⇒ local file agrees with chain truth as of anchoring ⇒ offline is
|
|
57
|
+
// safe. Mismatch has two honest meanings:
|
|
69
58
|
// - renewal: chain moved, local file updated ⇒ re-anchor via chain check
|
|
70
|
-
// - forgery: local file edited ⇒ every call falls through to the
|
|
71
|
-
// which reports truth. The forger gets no
|
|
59
|
+
// - forgery: local file edited ⇒ every call falls through to the
|
|
60
|
+
// licensing service, which reports truth. The forger gets no
|
|
61
|
+
// offline branch at all.
|
|
72
62
|
if (a.expirySlot !== expirySlot)
|
|
73
63
|
return null;
|
|
74
64
|
return a;
|
|
@@ -77,9 +67,9 @@ function loadAnchor(storage, licenseHash, expirySlot) {
|
|
|
77
67
|
return null;
|
|
78
68
|
}
|
|
79
69
|
}
|
|
80
|
-
function saveAnchor(storage, licenseHash, anchor) {
|
|
70
|
+
function saveAnchor(storage, zkAppAddress, licenseHash, anchor) {
|
|
81
71
|
try {
|
|
82
|
-
storage.setItem(
|
|
72
|
+
storage.setItem(anchorKey(zkAppAddress, licenseHash), JSON.stringify(anchor));
|
|
83
73
|
}
|
|
84
74
|
catch {
|
|
85
75
|
// Storage may be full or read-only; the offline path simply won't activate.
|
|
@@ -90,39 +80,6 @@ function computeRemainingDays(expirySlot, currentSlot) {
|
|
|
90
80
|
return 0;
|
|
91
81
|
return Math.floor((expirySlot - currentSlot) / SLOTS_PER_DAY);
|
|
92
82
|
}
|
|
93
|
-
// Renew the activation token when within this window of expiry. Picked so
|
|
94
|
-
// that a daily verify call has many chances to refresh before the token dies.
|
|
95
|
-
const TOKEN_RENEW_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
96
|
-
async function tryActivate(storage, zkAppAddress, licenseHash, current, bootstrapSecretHash, prover, verifierUrl, fetcher, nowMs) {
|
|
97
|
-
const secretHash = current?.secretHash ?? bootstrapSecretHash;
|
|
98
|
-
if (!secretHash)
|
|
99
|
-
return current;
|
|
100
|
-
// Existing token still fresh — nothing to do.
|
|
101
|
-
if (current?.token && current.expiresAt - nowMs > TOKEN_RENEW_THRESHOLD_MS)
|
|
102
|
-
return current;
|
|
103
|
-
try {
|
|
104
|
-
const base = verifierUrl.replace(/\/+$/, '');
|
|
105
|
-
const challengeResp = await fetcher(`${base}/challenge?licenseHash=${encodeURIComponent(licenseHash)}`);
|
|
106
|
-
if (!challengeResp.ok)
|
|
107
|
-
return current;
|
|
108
|
-
const { nonce } = (await challengeResp.json());
|
|
109
|
-
const proofJson = await prover({ licenseHash, nonce, secretHash });
|
|
110
|
-
const respondResp = await fetcher(`${base}/respond`, {
|
|
111
|
-
method: 'POST',
|
|
112
|
-
headers: { 'Content-Type': 'application/json' },
|
|
113
|
-
body: JSON.stringify({ zkAppAddress, proof: proofJson }),
|
|
114
|
-
});
|
|
115
|
-
if (!respondResp.ok)
|
|
116
|
-
return current;
|
|
117
|
-
const { token, expiresAt } = (await respondResp.json());
|
|
118
|
-
const next = { token, expiresAt, secretHash };
|
|
119
|
-
saveActivation(storage, zkAppAddress, licenseHash, next);
|
|
120
|
-
return next;
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
return current;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
83
|
function offlineCheck(anchor, nowMs) {
|
|
127
84
|
const elapsedSlots = Math.max(0, Math.floor((nowMs - anchor.anchoredAtMs) / MS_PER_SLOT));
|
|
128
85
|
const estCurrentSlot = anchor.anchoredSlot + elapsedSlots;
|
|
@@ -142,20 +99,18 @@ function offlineCheck(anchor, nowMs) {
|
|
|
142
99
|
? 'License is in grace period — renew to avoid interruption'
|
|
143
100
|
: null,
|
|
144
101
|
source: 'offline',
|
|
145
|
-
ownership: anchor.ownership,
|
|
146
102
|
};
|
|
147
103
|
}
|
|
148
104
|
export async function verifyLicense(proof, options = {}) {
|
|
149
105
|
const storage = options.storage === null ? null : options.storage ?? defaultStorage();
|
|
150
106
|
const fetcher = options.fetcher ?? (typeof fetch !== 'undefined' ? fetch : undefined);
|
|
151
107
|
const nowFn = options.now ?? (() => Date.now());
|
|
152
|
-
const verifierUrl = options.verifierUrl ?? proof.verifierUrl;
|
|
153
108
|
if (storage) {
|
|
154
|
-
const anchor = loadAnchor(storage, proof.licenseHash, proof.expirySlot);
|
|
109
|
+
const anchor = loadAnchor(storage, proof.zkAppAddress, proof.licenseHash, proof.expirySlot);
|
|
155
110
|
if (anchor)
|
|
156
111
|
return offlineCheck(anchor, nowFn());
|
|
157
112
|
}
|
|
158
|
-
if (!
|
|
113
|
+
if (!fetcher) {
|
|
159
114
|
return {
|
|
160
115
|
valid: false,
|
|
161
116
|
expirySlot: proof.expirySlot,
|
|
@@ -163,24 +118,13 @@ export async function verifyLicense(proof, options = {}) {
|
|
|
163
118
|
currentSlot: 0,
|
|
164
119
|
inGracePeriod: false,
|
|
165
120
|
remainingDays: 0,
|
|
166
|
-
reason: 'No
|
|
121
|
+
reason: 'No fetch implementation available; cannot verify',
|
|
167
122
|
source: 'chain',
|
|
168
|
-
ownership: 'unverified',
|
|
169
123
|
};
|
|
170
124
|
}
|
|
171
|
-
|
|
172
|
-
? loadActivation(storage, proof.zkAppAddress, proof.licenseHash, nowFn())
|
|
173
|
-
: null;
|
|
174
|
-
if (storage && options.prover && (activation || options.secretHash)) {
|
|
175
|
-
activation = await tryActivate(storage, proof.zkAppAddress, proof.licenseHash, activation, options.secretHash, options.prover, verifierUrl, fetcher, nowFn());
|
|
176
|
-
}
|
|
177
|
-
const tokenParam = activation?.token
|
|
178
|
-
? `&token=${encodeURIComponent(activation.token)}`
|
|
179
|
-
: '';
|
|
180
|
-
const url = `${verifierUrl.replace(/\/+$/, '')}` +
|
|
125
|
+
const url = `${DEFAULT_VERIFIER_URL}` +
|
|
181
126
|
`?licenseHash=${encodeURIComponent(proof.licenseHash)}` +
|
|
182
|
-
`&zkAppAddress=${encodeURIComponent(proof.zkAppAddress)}
|
|
183
|
-
tokenParam;
|
|
127
|
+
`&zkAppAddress=${encodeURIComponent(proof.zkAppAddress)}`;
|
|
184
128
|
let data;
|
|
185
129
|
try {
|
|
186
130
|
const resp = await fetcher(url);
|
|
@@ -194,7 +138,6 @@ export async function verifyLicense(proof, options = {}) {
|
|
|
194
138
|
remainingDays: 0,
|
|
195
139
|
reason: `Verification request failed (HTTP ${resp.status})`,
|
|
196
140
|
source: 'chain',
|
|
197
|
-
ownership: 'unverified',
|
|
198
141
|
};
|
|
199
142
|
}
|
|
200
143
|
data = await resp.json();
|
|
@@ -209,13 +152,12 @@ export async function verifyLicense(proof, options = {}) {
|
|
|
209
152
|
remainingDays: 0,
|
|
210
153
|
reason: err?.message ?? 'Verification request errored',
|
|
211
154
|
source: 'chain',
|
|
212
|
-
ownership: 'unverified',
|
|
213
155
|
};
|
|
214
156
|
}
|
|
215
|
-
//
|
|
216
|
-
// the null-safe form may be written into the anchor; the
|
|
217
|
-
// display value below falls back to the local proof file
|
|
218
|
-
// fields, which is safe (display only).
|
|
157
|
+
// Service's authoritative expiry, or null if the response omits it.
|
|
158
|
+
// Only the null-safe form may be written into the anchor; the
|
|
159
|
+
// chain-result display value below falls back to the local proof file
|
|
160
|
+
// for user-facing fields, which is safe (display only).
|
|
219
161
|
const respExpirySlotStrict = typeof data.expirySlot === 'number' ? data.expirySlot : null;
|
|
220
162
|
const respExpirySlot = respExpirySlotStrict ?? proof.expirySlot;
|
|
221
163
|
const respCurrentSlot = typeof data.currentSlot === 'number' ? data.currentSlot : 0;
|
|
@@ -230,24 +172,23 @@ export async function verifyLicense(proof, options = {}) {
|
|
|
230
172
|
: computeRemainingDays(respExpirySlot, respCurrentSlot),
|
|
231
173
|
reason: data.reason ?? null,
|
|
232
174
|
source: 'chain',
|
|
233
|
-
ownership: data.ownership === 'verified' ? 'verified' : 'unverified',
|
|
234
175
|
};
|
|
235
|
-
// Anchor gate uses the
|
|
236
|
-
// knows the true purchaseSlot (stored in its LicenseRecord)
|
|
237
|
-
// currentSlot; the SDK never trusts a purchaseSlot or
|
|
238
|
-
// by the local proof file, since that file is
|
|
239
|
-
// be forged. If the
|
|
240
|
-
// offline path — no anchor
|
|
176
|
+
// Anchor gate uses the service's own refund-window decision. The
|
|
177
|
+
// service knows the true purchaseSlot (stored in its LicenseRecord)
|
|
178
|
+
// and the true currentSlot; the SDK never trusts a purchaseSlot or
|
|
179
|
+
// expirySlot supplied by the local proof file, since that file is
|
|
180
|
+
// client-writable and could be forged. If the service omits
|
|
181
|
+
// `expirySlot`, we fail-closed on the offline path — no anchor
|
|
182
|
+
// written, subsequent calls stay online.
|
|
241
183
|
if (storage &&
|
|
242
184
|
result.valid &&
|
|
243
185
|
data.refundWindowClosed === true &&
|
|
244
186
|
typeof data.currentSlot === 'number' &&
|
|
245
187
|
respExpirySlotStrict !== null) {
|
|
246
|
-
saveAnchor(storage, proof.licenseHash, {
|
|
188
|
+
saveAnchor(storage, proof.zkAppAddress, proof.licenseHash, {
|
|
247
189
|
anchoredSlot: data.currentSlot,
|
|
248
190
|
anchoredAtMs: nowFn(),
|
|
249
191
|
expirySlot: respExpirySlotStrict,
|
|
250
|
-
ownership: result.ownership,
|
|
251
192
|
});
|
|
252
193
|
}
|
|
253
194
|
return result;
|
|
@@ -255,4 +196,32 @@ export async function verifyLicense(proof, options = {}) {
|
|
|
255
196
|
// Re-exported so tests and downstream tooling can reference the same values
|
|
256
197
|
// the SDK uses internally without re-deriving them.
|
|
257
198
|
export { REFUND_WINDOW_N, GRACE_PERIOD_N, MS_PER_SLOT };
|
|
199
|
+
// Non-custodial device binding surface. Re-exported at the
|
|
200
|
+
// barrel alongside buy / renew / refund so vendor SDK integrators can
|
|
201
|
+
// pull the whole family from a single import path. Each also has its
|
|
202
|
+
// own package subpath (`zklicensing/bind`, `zklicensing/unbind`,
|
|
203
|
+
// `zklicensing/device-identity`) to match the buy / renew / refund
|
|
204
|
+
// convention.
|
|
205
|
+
export { proveBind, confirmBind } from './bindClient.js';
|
|
206
|
+
export { proveUnbind, confirmUnbind } from './unbindClient.js';
|
|
207
|
+
export { getOrCreateDeviceIdentity, computeDeviceCommitment, } from './deviceIdentity.js';
|
|
208
|
+
// Offline device-binding verifier. Vendors reconstruct the
|
|
209
|
+
// LicenseLeaf from a synced snapshot, verify the leaf hash against the
|
|
210
|
+
// last-known licensesRoot, and confirm the caller's device commitment
|
|
211
|
+
// appears in `occupied[]`. Also available at the `zklicensing/verify-offline`
|
|
212
|
+
// subpath for callers that want to minimize their imported surface.
|
|
213
|
+
export { verifyOffline, deviceCommitmentFromFields } from './verifyOffline.js';
|
|
214
|
+
// Non-custodial trust anchor for `verifyOffline`. Reads the live
|
|
215
|
+
// `licensesRoot` (zkapp.appState[0]) directly from Mina — pair it with
|
|
216
|
+
// `verifyOffline` when you do not want to trust the licensing service's
|
|
217
|
+
// reported root. Also available at the `zklicensing/on-chain-root`
|
|
218
|
+
// subpath. o1js is imported lazily so callers that skip this helper
|
|
219
|
+
// don't pay the o1js dep cost at load time.
|
|
220
|
+
export { fetchOnChainLicensesRoot, DEFAULT_MINA_GRAPHQL_URLS } from './onChainRoot.js';
|
|
221
|
+
// Human-readable seat names for the device-binding UI ("Silent Otter",
|
|
222
|
+
// "Curious Panda"). Deterministic in `licenseHash + slotIndex + boundAtSlot`,
|
|
223
|
+
// so every viewer of the same license computes the same name per slot
|
|
224
|
+
// without a naming sidecar. Also available at the `zklicensing/seat-name`
|
|
225
|
+
// subpath.
|
|
226
|
+
export { seatNameFor, SEAT_NAME_COMBINATIONS } from './seatName.js';
|
|
258
227
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* licenseLeaf.ts
|
|
3
|
+
*
|
|
4
|
+
* On-chain leaf value stored at every occupied `licensesRoot` slot. Bundles
|
|
5
|
+
* the license expiry with the device-binding state (cap, slot commitments,
|
|
6
|
+
* fill counter) so a single tree lookup returns everything a verifier or a
|
|
7
|
+
* `/prove/bind` call needs.
|
|
8
|
+
*
|
|
9
|
+
* Wire shape (in order of hash preimage):
|
|
10
|
+
* licenseHash Field the tree key, also carried in the leaf
|
|
11
|
+
* so a stolen witness can't be re-pointed
|
|
12
|
+
* at another entry. Doubles as the owner-
|
|
13
|
+
* pubkey commitment — buyLicense asserts
|
|
14
|
+
* licenseHash == Poseidon(pk.x, pk.isOdd)
|
|
15
|
+
* so bind/unbind's ownership check reads
|
|
16
|
+
* the same field as the tree key. Storing
|
|
17
|
+
* them separately would be pure redundancy.
|
|
18
|
+
* expirySlot UInt32→Field UInt32 packed into a Field
|
|
19
|
+
* cap UInt32→Field 0 disables /prove/bind; ≤DEVICE_SLOT_CAP
|
|
20
|
+
* (kept as UInt32 in-circuit because o1js's
|
|
21
|
+
* provable types don't include UInt8)
|
|
22
|
+
* deviceSlotsRoot Field Poseidon fold over the 10 slot fields
|
|
23
|
+
* below (2 fields × DEVICE_SLOT_CAP)
|
|
24
|
+
* deviceSlotsFilled UInt32→Field how many of the DEVICE_SLOT_CAP slots
|
|
25
|
+
* are currently non-empty — carried
|
|
26
|
+
* alongside the fold so `bind()` can do
|
|
27
|
+
* a cheap "cap - filled > 0" check
|
|
28
|
+
* without re-summing the array
|
|
29
|
+
*
|
|
30
|
+
* DEVICE_SLOT_CAP is fixed at 5 so the slot arrays have a size known to the
|
|
31
|
+
* circuit at compile time. `cap` in the leaf may be smaller — it's a
|
|
32
|
+
* per-license limit; slots [cap..DEVICE_SLOT_CAP) must always stay empty
|
|
33
|
+
* (Field(0)) and `bind()` rejects any write to them. `cap = 0` disables
|
|
34
|
+
* binding entirely; `bind()` rejects immediately without ever touching a
|
|
35
|
+
* slot.
|
|
36
|
+
*
|
|
37
|
+
* Slot encoding (fields carried in the fold, per slot i):
|
|
38
|
+
* occupied[i] Field 0 (empty) or Poseidon(devicePubKeyHash)
|
|
39
|
+
* freedAt[i] Field packed (boundAtSlot << 32) | freedAtSlot; both
|
|
40
|
+
* are UInt32-range Mina slot numbers. On a fresh
|
|
41
|
+
* empty slot both halves are 0. On bind, boundAt
|
|
42
|
+
* gets the current slot and freedAt stays 0. On
|
|
43
|
+
* unbind, freedAt gets the current slot (boundAt
|
|
44
|
+
* is preserved for audit / cooldown math).
|
|
45
|
+
*
|
|
46
|
+
* The struct + helpers live in the SDK (not the circuit package) so both
|
|
47
|
+
* the on-chain `@method` bodies AND the off-chain keeper / verifier /
|
|
48
|
+
* dashboards can share a single source of truth for the leaf hash. The
|
|
49
|
+
* circuit re-exports them from `LicensingApp.ts` so existing consumers
|
|
50
|
+
* that import from there keep working.
|
|
51
|
+
*/
|
|
52
|
+
import { Field, PublicKey, UInt32 } from 'o1js';
|
|
53
|
+
import type { LicenseRecord } from './licenseRecord.js';
|
|
54
|
+
export declare const DEVICE_SLOT_CAP_N = 5;
|
|
55
|
+
export declare const DEVICE_SLOT_CAP: UInt32;
|
|
56
|
+
export declare const EMPTY_DEVICE_SLOTS_ROOT: Field;
|
|
57
|
+
export declare function ownerPubKeyHash(pub: PublicKey): Field;
|
|
58
|
+
export declare function packSlotTimes(boundAtSlot: UInt32, freedAtSlot: UInt32): Field;
|
|
59
|
+
export declare function unpackSlotTimes(packed: Field): {
|
|
60
|
+
boundAtSlot: UInt32;
|
|
61
|
+
freedAtSlot: UInt32;
|
|
62
|
+
};
|
|
63
|
+
export declare function computeDeviceSlotsRoot(occupied: Field[], freedAt: Field[]): Field;
|
|
64
|
+
export declare const DeviceSlot: any;
|
|
65
|
+
export type DeviceSlot = any;
|
|
66
|
+
export declare function emptyDeviceSlot(): DeviceSlot;
|
|
67
|
+
export declare const DeviceSlots: any;
|
|
68
|
+
export type DeviceSlots = any;
|
|
69
|
+
export declare function emptyDeviceSlots(): DeviceSlots;
|
|
70
|
+
export declare function deviceSlotsHash(ds: DeviceSlots): Field;
|
|
71
|
+
export declare const LicenseLeaf: any;
|
|
72
|
+
export type LicenseLeaf = any;
|
|
73
|
+
export declare function leafHash(leaf: LicenseLeaf): Field;
|
|
74
|
+
export declare function freshLeaf(licenseHash: Field, expirySlot: UInt32, cap: UInt32): LicenseLeaf;
|
|
75
|
+
export declare function leafFromRecord(record: LicenseRecord): LicenseLeaf;
|
|
76
|
+
//# sourceMappingURL=licenseLeaf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"licenseLeaf.d.ts","sourceRoot":"","sources":["../src/licenseLeaf.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,OAAO,EAAE,KAAK,EAAsB,SAAS,EAAU,MAAM,EAAE,MAAM,MAAM,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,eAAe,QAAiC,CAAC;AAK9D,eAAO,MAAM,uBAAuB,EAAE,KAErC,CAAC;AAOF,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,GAAG,KAAK,CAErD;AASD,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,KAAK,CAE7E;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,KAAK,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAQ3F;AAMD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,KAAK,EAAE,EACjB,OAAO,EAAG,KAAK,EAAE,GAChB,KAAK,CAYP;AAYD,eAAO,MAAM,UAAU,EAAE,GAGvB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,GAAG,CAAC;AAE7B,wBAAgB,eAAe,IAAI,UAAU,CAE5C;AAID,eAAO,MAAM,WAAW,EAAE,GAExB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC;AAE9B,wBAAgB,gBAAgB,IAAI,WAAW,CAI9C;AAKD,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,KAAK,CAMtD;AAMD,eAAO,MAAM,WAAW,EAAE,GAMxB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC;AAE9B,wBAAgB,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,KAAK,CAQjD;AAID,wBAAgB,SAAS,CACvB,WAAW,EAAE,KAAK,EAClB,UAAU,EAAG,MAAM,EACnB,GAAG,EAAU,MAAM,GAClB,WAAW,CAQb;AAMD,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAUjE"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Copyright (c) 2025-2026 zkLicensing project developers
|
|
2
|
+
// SPDX-License-Identifier: BUSL-1.1
|
|
3
|
+
/**
|
|
4
|
+
* licenseLeaf.ts
|
|
5
|
+
*
|
|
6
|
+
* On-chain leaf value stored at every occupied `licensesRoot` slot. Bundles
|
|
7
|
+
* the license expiry with the device-binding state (cap, slot commitments,
|
|
8
|
+
* fill counter) so a single tree lookup returns everything a verifier or a
|
|
9
|
+
* `/prove/bind` call needs.
|
|
10
|
+
*
|
|
11
|
+
* Wire shape (in order of hash preimage):
|
|
12
|
+
* licenseHash Field the tree key, also carried in the leaf
|
|
13
|
+
* so a stolen witness can't be re-pointed
|
|
14
|
+
* at another entry. Doubles as the owner-
|
|
15
|
+
* pubkey commitment — buyLicense asserts
|
|
16
|
+
* licenseHash == Poseidon(pk.x, pk.isOdd)
|
|
17
|
+
* so bind/unbind's ownership check reads
|
|
18
|
+
* the same field as the tree key. Storing
|
|
19
|
+
* them separately would be pure redundancy.
|
|
20
|
+
* expirySlot UInt32→Field UInt32 packed into a Field
|
|
21
|
+
* cap UInt32→Field 0 disables /prove/bind; ≤DEVICE_SLOT_CAP
|
|
22
|
+
* (kept as UInt32 in-circuit because o1js's
|
|
23
|
+
* provable types don't include UInt8)
|
|
24
|
+
* deviceSlotsRoot Field Poseidon fold over the 10 slot fields
|
|
25
|
+
* below (2 fields × DEVICE_SLOT_CAP)
|
|
26
|
+
* deviceSlotsFilled UInt32→Field how many of the DEVICE_SLOT_CAP slots
|
|
27
|
+
* are currently non-empty — carried
|
|
28
|
+
* alongside the fold so `bind()` can do
|
|
29
|
+
* a cheap "cap - filled > 0" check
|
|
30
|
+
* without re-summing the array
|
|
31
|
+
*
|
|
32
|
+
* DEVICE_SLOT_CAP is fixed at 5 so the slot arrays have a size known to the
|
|
33
|
+
* circuit at compile time. `cap` in the leaf may be smaller — it's a
|
|
34
|
+
* per-license limit; slots [cap..DEVICE_SLOT_CAP) must always stay empty
|
|
35
|
+
* (Field(0)) and `bind()` rejects any write to them. `cap = 0` disables
|
|
36
|
+
* binding entirely; `bind()` rejects immediately without ever touching a
|
|
37
|
+
* slot.
|
|
38
|
+
*
|
|
39
|
+
* Slot encoding (fields carried in the fold, per slot i):
|
|
40
|
+
* occupied[i] Field 0 (empty) or Poseidon(devicePubKeyHash)
|
|
41
|
+
* freedAt[i] Field packed (boundAtSlot << 32) | freedAtSlot; both
|
|
42
|
+
* are UInt32-range Mina slot numbers. On a fresh
|
|
43
|
+
* empty slot both halves are 0. On bind, boundAt
|
|
44
|
+
* gets the current slot and freedAt stays 0. On
|
|
45
|
+
* unbind, freedAt gets the current slot (boundAt
|
|
46
|
+
* is preserved for audit / cooldown math).
|
|
47
|
+
*
|
|
48
|
+
* The struct + helpers live in the SDK (not the circuit package) so both
|
|
49
|
+
* the on-chain `@method` bodies AND the off-chain keeper / verifier /
|
|
50
|
+
* dashboards can share a single source of truth for the leaf hash. The
|
|
51
|
+
* circuit re-exports them from `LicensingApp.ts` so existing consumers
|
|
52
|
+
* that import from there keep working.
|
|
53
|
+
*/
|
|
54
|
+
import { Field, Poseidon, Provable, Struct, UInt32 } from 'o1js';
|
|
55
|
+
// Fixed slot-array width. Per the device-binding-noncustodial plan: 5 is
|
|
56
|
+
// the sweet spot between "enough seats for a normal user" and "witnesses
|
|
57
|
+
// stay small." Changing this is a hardfork migration (would flip every
|
|
58
|
+
// leaf hash) — not a runtime tunable.
|
|
59
|
+
export const DEVICE_SLOT_CAP_N = 5;
|
|
60
|
+
export const DEVICE_SLOT_CAP = UInt32.from(DEVICE_SLOT_CAP_N);
|
|
61
|
+
// Poseidon-fold of DEVICE_SLOT_CAP × 2 = 10 zero Fields, used as the
|
|
62
|
+
// initial deviceSlotsRoot on every freshly-minted leaf. Precomputed once
|
|
63
|
+
// at module load so `freshLeaf()` doesn't reconstruct it per-call.
|
|
64
|
+
export const EMPTY_DEVICE_SLOTS_ROOT = Poseidon.hash(Array.from({ length: DEVICE_SLOT_CAP_N * 2 }, () => Field(0)));
|
|
65
|
+
// Poseidon-bound identity of the passphrase-derived ownership pubkey.
|
|
66
|
+
// The full PublicKey is (x: Field, isOdd: Bool); Poseidon(x, isOdd.toField())
|
|
67
|
+
// gives a single Field commitment. The buy circuit asserts this equals
|
|
68
|
+
// `licenseHash`, so the leaf carries no separate owner-pubkey field —
|
|
69
|
+
// `licenseHash` IS the on-chain owner-pubkey commitment.
|
|
70
|
+
export function ownerPubKeyHash(pub) {
|
|
71
|
+
return Poseidon.hash([pub.x, pub.isOdd.toField()]);
|
|
72
|
+
}
|
|
73
|
+
// Pack (boundAtSlot, freedAtSlot) into a single Field. Both are Mina
|
|
74
|
+
// slot numbers (UInt32-range, ≤ 2^32-1), so the concatenation fits in 64
|
|
75
|
+
// bits. Layout mirrors packPrices in LicensingApp.ts — low bits first so
|
|
76
|
+
// the more frequently-read half (freedAt, checked by every bind cooldown)
|
|
77
|
+
// unpacks with a single mask.
|
|
78
|
+
const SHIFT_32 = Field(1n << 32n);
|
|
79
|
+
export function packSlotTimes(boundAtSlot, freedAtSlot) {
|
|
80
|
+
return freedAtSlot.value.add(boundAtSlot.value.mul(SHIFT_32));
|
|
81
|
+
}
|
|
82
|
+
export function unpackSlotTimes(packed) {
|
|
83
|
+
// toBits(64) enforces the high 190 bits are zero — belt-and-suspenders
|
|
84
|
+
// since we only ever write via packSlotTimes, but keeps the circuit
|
|
85
|
+
// sound against an adversarially-crafted slot in a future upgrade.
|
|
86
|
+
const bits = packed.toBits(64);
|
|
87
|
+
const freedAtSlot = UInt32.Unsafe.fromField(Field.fromBits(bits.slice(0, 32)));
|
|
88
|
+
const boundAtSlot = UInt32.Unsafe.fromField(Field.fromBits(bits.slice(32, 64)));
|
|
89
|
+
return { boundAtSlot, freedAtSlot };
|
|
90
|
+
}
|
|
91
|
+
// Fold the parallel slot arrays into `deviceSlotsRoot`. Same order every
|
|
92
|
+
// time — occupied[0], freedAt[0], occupied[1], freedAt[1], ... — so a
|
|
93
|
+
// buyer that reconstructs the fold off-chain (verifier, dashboard) gets
|
|
94
|
+
// the same commitment the circuit computed.
|
|
95
|
+
export function computeDeviceSlotsRoot(occupied, freedAt) {
|
|
96
|
+
if (occupied.length !== DEVICE_SLOT_CAP_N || freedAt.length !== DEVICE_SLOT_CAP_N) {
|
|
97
|
+
throw new Error(`computeDeviceSlotsRoot expects exactly ${DEVICE_SLOT_CAP_N} entries in each array ` +
|
|
98
|
+
`(got occupied=${occupied.length}, freedAt=${freedAt.length})`);
|
|
99
|
+
}
|
|
100
|
+
const fields = [];
|
|
101
|
+
for (let i = 0; i < DEVICE_SLOT_CAP_N; i++) {
|
|
102
|
+
fields.push(occupied[i], freedAt[i]);
|
|
103
|
+
}
|
|
104
|
+
return Poseidon.hash(fields);
|
|
105
|
+
}
|
|
106
|
+
// Per-slot payload. The Struct wrapper is what lets o1js serialize this
|
|
107
|
+
// inside larger provable structures — it also gives us a fixed
|
|
108
|
+
// two-Field width so DeviceSlots.slots can be a `Provable.Array` of a
|
|
109
|
+
// well-known type instead of a bag of loose Fields.
|
|
110
|
+
//
|
|
111
|
+
// Same `const X: any = Struct(...)` pattern as contractInterface.ts —
|
|
112
|
+
// annotating as `any` sidesteps TS2742 (o1js sits outside the package
|
|
113
|
+
// rootDir under npm hoisting, so tsc can't emit portable declarations
|
|
114
|
+
// for the inferred Struct class). Instance methods and `static empty()`
|
|
115
|
+
// therefore live as standalone helpers below rather than on the class.
|
|
116
|
+
export const DeviceSlot = Struct({
|
|
117
|
+
occupied: Field, // 0 or Poseidon(devicePubKeyHash)
|
|
118
|
+
freedAt: Field, // packSlotTimes(boundAtSlot, freedAtSlot)
|
|
119
|
+
});
|
|
120
|
+
export function emptyDeviceSlot() {
|
|
121
|
+
return new DeviceSlot({ occupied: Field(0), freedAt: Field(0) });
|
|
122
|
+
}
|
|
123
|
+
// Fixed-width slot array. `Provable.Array(DeviceSlot, N)` promises the
|
|
124
|
+
// circuit an exact length so witnesses can address slots by static index.
|
|
125
|
+
export const DeviceSlots = Struct({
|
|
126
|
+
slots: Provable.Array(DeviceSlot, DEVICE_SLOT_CAP_N),
|
|
127
|
+
});
|
|
128
|
+
export function emptyDeviceSlots() {
|
|
129
|
+
return new DeviceSlots({
|
|
130
|
+
slots: Array.from({ length: DEVICE_SLOT_CAP_N }, () => emptyDeviceSlot()),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
// In-circuit fold — takes a DeviceSlots value so it composes cleanly
|
|
134
|
+
// inside `@method` bodies. The array length is fixed by the Struct's
|
|
135
|
+
// Provable.Array declaration, so no runtime length guard is needed.
|
|
136
|
+
export function deviceSlotsHash(ds) {
|
|
137
|
+
const fields = [];
|
|
138
|
+
for (const s of ds.slots) {
|
|
139
|
+
fields.push(s.occupied, s.freedAt);
|
|
140
|
+
}
|
|
141
|
+
return Poseidon.hash(fields);
|
|
142
|
+
}
|
|
143
|
+
// The leaf value itself. `LicensingApp.buyLicense` builds one of these
|
|
144
|
+
// with EMPTY_DEVICE_SLOTS_ROOT and writes leafHash(leaf) into licensesRoot;
|
|
145
|
+
// subsequent mutations (renew, refund, migrate, bind, unbind) each build
|
|
146
|
+
// a new leaf with the appropriate field(s) updated and write its hash.
|
|
147
|
+
export const LicenseLeaf = Struct({
|
|
148
|
+
licenseHash: Field,
|
|
149
|
+
expirySlot: UInt32,
|
|
150
|
+
cap: UInt32,
|
|
151
|
+
deviceSlotsRoot: Field,
|
|
152
|
+
deviceSlotsFilled: UInt32,
|
|
153
|
+
});
|
|
154
|
+
export function leafHash(leaf) {
|
|
155
|
+
return Poseidon.hash([
|
|
156
|
+
leaf.licenseHash,
|
|
157
|
+
leaf.expirySlot.value,
|
|
158
|
+
leaf.cap.value,
|
|
159
|
+
leaf.deviceSlotsRoot,
|
|
160
|
+
leaf.deviceSlotsFilled.value,
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
// Construct the leaf a fresh buy writes into the tree — no devices bound
|
|
164
|
+
// yet, filled counter at zero, slot-fold at the precomputed empty root.
|
|
165
|
+
export function freshLeaf(licenseHash, expirySlot, cap) {
|
|
166
|
+
return new LicenseLeaf({
|
|
167
|
+
licenseHash,
|
|
168
|
+
expirySlot,
|
|
169
|
+
cap,
|
|
170
|
+
deviceSlotsRoot: EMPTY_DEVICE_SLOTS_ROOT,
|
|
171
|
+
deviceSlotsFilled: UInt32.zero,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
// Reconstruct the leaf that lives at `record.licenseHash` in the licensesRoot
|
|
175
|
+
// MerkleMap. `LicenseRecord` optionally persists the device-binding fields;
|
|
176
|
+
// anything missing falls back to the fresh-leaf defaults, matching the state
|
|
177
|
+
// a just-issued license was minted with.
|
|
178
|
+
export function leafFromRecord(record) {
|
|
179
|
+
return new LicenseLeaf({
|
|
180
|
+
licenseHash: Field(record.licenseHash),
|
|
181
|
+
expirySlot: UInt32.from(record.expirySlot),
|
|
182
|
+
cap: UInt32.from(record.deviceCap ?? 0),
|
|
183
|
+
deviceSlotsRoot: record.deviceSlotsRoot !== undefined
|
|
184
|
+
? Field(record.deviceSlotsRoot)
|
|
185
|
+
: EMPTY_DEVICE_SLOTS_ROOT,
|
|
186
|
+
deviceSlotsFilled: UInt32.from(record.deviceSlotsFilled ?? 0),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=licenseLeaf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"licenseLeaf.js","sourceRoot":"","sources":["../src/licenseLeaf.ts"],"names":[],"mappings":"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"}
|
package/build/licenseRecord.d.ts
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
* the same type without dragging in filesystem code. licenseStore.ts
|
|
8
8
|
* re-exports these to keep existing Node imports working.
|
|
9
9
|
*/
|
|
10
|
+
export type FiatRateSnapshot = {
|
|
11
|
+
minaEurRate: number;
|
|
12
|
+
minaUsdRate: number;
|
|
13
|
+
minaGbpRate: number;
|
|
14
|
+
recordedAt: string;
|
|
15
|
+
};
|
|
10
16
|
export type LicenseRecord = {
|
|
11
17
|
zkAppAddress: string;
|
|
12
18
|
licenseHash: string;
|
|
@@ -15,22 +21,35 @@ export type LicenseRecord = {
|
|
|
15
21
|
purchaseAmount: string;
|
|
16
22
|
txHash: string;
|
|
17
23
|
createdAt: string;
|
|
24
|
+
deviceCap?: number;
|
|
25
|
+
deviceSlotsRoot?: string;
|
|
26
|
+
deviceSlotsFilled?: number;
|
|
27
|
+
devices?: Array<{
|
|
28
|
+
slotIndex: number;
|
|
29
|
+
deviceCommitment: string;
|
|
30
|
+
boundAtSlot: number;
|
|
31
|
+
freedAtSlot: number;
|
|
32
|
+
}>;
|
|
18
33
|
buyerAddress?: string;
|
|
19
34
|
buyerCountry?: string;
|
|
35
|
+
buyerZip?: string;
|
|
20
36
|
pending?: boolean;
|
|
21
37
|
provedAtSlot?: number;
|
|
22
38
|
initialExpirySlot?: number;
|
|
23
|
-
tier?: 'monthly' | 'yearly' | 'fiveYear';
|
|
39
|
+
tier?: 'monthly' | 'yearly' | 'fiveYear' | 'perpetual';
|
|
24
40
|
renewedAtLeastOnce?: boolean;
|
|
25
41
|
refunded?: boolean;
|
|
26
42
|
released?: boolean;
|
|
27
43
|
releaseSlot?: number;
|
|
44
|
+
releaseTxHash?: string;
|
|
28
45
|
renewals?: Array<{
|
|
29
46
|
slot: number;
|
|
30
47
|
amount: string;
|
|
31
48
|
newExpiry: number;
|
|
32
49
|
txHash?: string;
|
|
50
|
+
fiatSnapshot?: FiatRateSnapshot;
|
|
33
51
|
}>;
|
|
52
|
+
fiatSnapshot?: FiatRateSnapshot;
|
|
34
53
|
refundWaiverAcceptedAt?: string;
|
|
35
54
|
escrowless?: boolean;
|
|
36
55
|
};
|