space-data-module-sdk 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "space-data-module-sdk",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Module SDK for building, validating, signing, and deploying WebAssembly modules on the Space Data Network.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -25,6 +25,10 @@
25
25
  "./compliance": "./src/compliance/index.js",
26
26
  "./auth": "./src/auth/index.js",
27
27
  "./transport": "./src/transport/index.js",
28
+ "./licensing": {
29
+ "types": "./src/licensing/index.d.ts",
30
+ "default": "./src/licensing/index.js"
31
+ },
28
32
  "./compiler": "./src/compiler/index.js",
29
33
  "./compiler/emception": "./src/compiler/emception.js",
30
34
  "./bundle": "./src/bundle/index.js",
@@ -68,7 +72,7 @@
68
72
  "flatsql": "^0.4.0",
69
73
  "hd-wallet-wasm": "1.6.0",
70
74
  "sdn-emception": "1.0.0",
71
- "spacedatastandards.org": "git+https://github.com/DigitalArsenal/spacedatastandards.org.git#09af351a1"
75
+ "spacedatastandards.org": "git+https://github.com/DigitalArsenal/spacedatastandards.org.git#c43c766bf4e8b18af125ed73b993e79c6ce20b24"
72
76
  },
73
77
  "devDependencies": {
74
78
  "express": "^4.21.2"
package/src/browser.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./manifest/browser.js";
2
2
  export * from "./auth/index.js";
3
3
  export * from "./transport/index.js";
4
+ export * from "./licensing/index.js";
4
5
  export * from "./bundle/index.js";
5
6
  export * from "./capabilities.js";
6
7
  export * from "./deployment/index.js";
package/src/index.d.ts CHANGED
@@ -1783,6 +1783,194 @@ export function inspectModule(source: Uint8Array | ArrayBuffer | WebAssembly.Mod
1783
1783
  imports: WebAssembly.ModuleImportDescriptor[];
1784
1784
  }>;
1785
1785
 
1786
+ // --- Licensing ---
1787
+
1788
+ export class LicensingProtocolError extends Error {
1789
+ code: string;
1790
+ constructor(code: string, message: string);
1791
+ }
1792
+
1793
+ export interface LicensingChallengeMessage {
1794
+ messageType: string;
1795
+ role: string;
1796
+ reqId: string;
1797
+ moduleId: string;
1798
+ moduleVersion?: string;
1799
+ requesterPeerId?: string;
1800
+ requesterXpub?: string;
1801
+ requesterSigningPublicKey?: Uint8Array;
1802
+ requesterEphemeralPublicKey?: Uint8Array;
1803
+ requestedDomain?: string;
1804
+ requestedTimeoutMs?: number;
1805
+ requestedAtMs?: number;
1806
+ challengeNonce?: Uint8Array;
1807
+ expiresAtMs?: number;
1808
+ providerPeerId?: string;
1809
+ errorCode?: string;
1810
+ errorMessage?: string;
1811
+ rawBytes: Uint8Array;
1812
+ }
1813
+
1814
+ export interface LicensingProofMessage {
1815
+ messageType: string;
1816
+ reqId: string;
1817
+ moduleId: string;
1818
+ moduleVersion?: string;
1819
+ requesterPeerId?: string;
1820
+ requesterXpub?: string;
1821
+ requestedDomain?: string;
1822
+ requestedTimeoutMs: number;
1823
+ requesterEphemeralPublicKey: Uint8Array;
1824
+ challengeNonce: Uint8Array;
1825
+ challengeExpiresAtMs: number;
1826
+ providerPeerId?: string;
1827
+ signature: Uint8Array;
1828
+ requesterSigningPublicKey: Uint8Array;
1829
+ timestampMs: number;
1830
+ rejectionCode?: string;
1831
+ rejectionMessage?: string;
1832
+ rawBytes: Uint8Array;
1833
+ }
1834
+
1835
+ export interface LicensingGrantModuleDescriptor {
1836
+ cid: string;
1837
+ contentHash: Uint8Array;
1838
+ sizeBytes: number;
1839
+ moduleId: string;
1840
+ moduleVersion?: string;
1841
+ requiredScope?: string;
1842
+ keyId?: string;
1843
+ allowedDomains: string[];
1844
+ maxGrantTimeoutMs: number;
1845
+ encrypted: boolean;
1846
+ }
1847
+
1848
+ export interface LicensingWrappedContentKeyHeader {
1849
+ version: number;
1850
+ keyExchange: string;
1851
+ symmetric: string;
1852
+ keyDerivation: string;
1853
+ ephemeralPublicKey: Uint8Array;
1854
+ nonceStart: Uint8Array;
1855
+ recipientKeyId: Uint8Array;
1856
+ context?: string;
1857
+ schemaHash: Uint8Array;
1858
+ rootType?: string;
1859
+ timestamp?: number;
1860
+ }
1861
+
1862
+ export interface LicensingWrappedContentKey {
1863
+ wrappingAlgorithm: string;
1864
+ contentKeyId?: string;
1865
+ contentKeyRole?: string;
1866
+ contentKeyAlgorithm?: string;
1867
+ contentKeyEncoding?: string;
1868
+ keyBytes: Uint8Array;
1869
+ contentKeyVersion?: number;
1870
+ recipientKeyId?: string;
1871
+ requesterEphemeralPublicKey: Uint8Array;
1872
+ providerEphemeralPublicKey: Uint8Array;
1873
+ hkdfSalt: Uint8Array;
1874
+ iv: Uint8Array;
1875
+ ciphertext: Uint8Array;
1876
+ tag: Uint8Array;
1877
+ expiresAtMs: number;
1878
+ recipientPublicKey: Uint8Array;
1879
+ ephemeralPublicKey: Uint8Array;
1880
+ nonce: Uint8Array;
1881
+ header: LicensingWrappedContentKeyHeader;
1882
+ encryptedPayload: Uint8Array;
1883
+ recipientKeyIdBytes: Uint8Array;
1884
+ schemaHash: Uint8Array;
1885
+ keyMaterialRootType?: string;
1886
+ }
1887
+
1888
+ export interface LicensingGrantMessage {
1889
+ messageType: string;
1890
+ reqId: string;
1891
+ moduleId: string;
1892
+ moduleVersion?: string;
1893
+ requesterPeerId?: string;
1894
+ requesterXpub?: string;
1895
+ requestedDomain?: string;
1896
+ requestedTimeoutMs: number;
1897
+ grantedDomain?: string;
1898
+ grantedTimeoutMs: number;
1899
+ expiresAtMs: number;
1900
+ requiredScope?: string;
1901
+ grantStatus?: string;
1902
+ denialReason?: string;
1903
+ capabilityToken: Uint8Array;
1904
+ grantVerifierPublicKey: Uint8Array;
1905
+ providerSignature: Uint8Array;
1906
+ moduleDescriptor: LicensingGrantModuleDescriptor | null;
1907
+ wrappedContentKey: LicensingWrappedContentKey | null;
1908
+ rawBytes: Uint8Array;
1909
+ }
1910
+
1911
+ export function encodeLicensingChallengeRequest(options: {
1912
+ reqId: string;
1913
+ moduleId: string;
1914
+ moduleVersion?: string;
1915
+ requesterPeerId: string;
1916
+ requesterXpub?: string;
1917
+ requesterSigningPublicKey: Uint8Array;
1918
+ requesterEphemeralPublicKey: Uint8Array;
1919
+ requesterDomain: string;
1920
+ requestedTimeoutMs: number;
1921
+ requestedAtMs: number;
1922
+ providerPeerId: string;
1923
+ }): Uint8Array;
1924
+
1925
+ export function decodeLicensingChallengeMessage(
1926
+ bytes: Uint8Array | ArrayBuffer | ArrayBufferView,
1927
+ ): LicensingChallengeMessage;
1928
+
1929
+ export function encodeLicensingProof(options: {
1930
+ reqId: string;
1931
+ moduleId: string;
1932
+ moduleVersion?: string;
1933
+ requesterPeerId: string;
1934
+ requesterXpub?: string;
1935
+ requesterDomain: string;
1936
+ requestedTimeoutMs: number;
1937
+ requesterEphemeralPublicKey: Uint8Array;
1938
+ challengeNonce: Uint8Array;
1939
+ challengeExpiresAtMs: number;
1940
+ providerPeerId: string;
1941
+ signature: Uint8Array;
1942
+ requesterSigningPublicKey: Uint8Array;
1943
+ timestampMs: number;
1944
+ }): Uint8Array;
1945
+
1946
+ export function decodeLicensingProofMessage(
1947
+ bytes: Uint8Array | ArrayBuffer | ArrayBufferView,
1948
+ ): LicensingProofMessage;
1949
+
1950
+ export function decodeLicensingGrant(
1951
+ bytes: Uint8Array | ArrayBuffer | ArrayBufferView,
1952
+ ): LicensingGrantMessage;
1953
+
1954
+ export function validateLicensingGrant(
1955
+ grant: LicensingGrantMessage,
1956
+ options?: {
1957
+ reqId?: string;
1958
+ moduleId?: string;
1959
+ moduleVersion?: string;
1960
+ expectedDomain?: string;
1961
+ requestedTimeoutMs?: number;
1962
+ grantVerifierPublicKeyLength?: number;
1963
+ },
1964
+ ): LicensingGrantMessage;
1965
+
1966
+ export function extractGrantModuleDescriptor(
1967
+ grant: LicensingGrantMessage,
1968
+ ): LicensingGrantModuleDescriptor;
1969
+
1970
+ export function extractWrappedContentKey(
1971
+ grant: LicensingGrantMessage,
1972
+ ): LicensingWrappedContentKey;
1973
+
1786
1974
  // --- Runtime constants ---
1787
1975
 
1788
1976
  export const DefaultManifestExports: {
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./manifest/index.js";
2
2
  export * from "./compliance/index.js";
3
3
  export * from "./auth/index.js";
4
4
  export * from "./transport/index.js";
5
+ export * from "./licensing/index.js";
5
6
  export * from "./compiler/index.js";
6
7
  export * from "./bundle/index.js";
7
8
  export * from "./capabilities.js";
@@ -0,0 +1,20 @@
1
+ export { LicensingProtocolError } from "../index.js";
2
+ export {
3
+ decodeLicensingChallengeMessage,
4
+ decodeLicensingGrant,
5
+ decodeLicensingProofMessage,
6
+ encodeLicensingChallengeRequest,
7
+ encodeLicensingProof,
8
+ extractGrantModuleDescriptor,
9
+ extractWrappedContentKey,
10
+ validateLicensingGrant,
11
+ } from "../index.js";
12
+
13
+ export type {
14
+ LicensingChallengeMessage,
15
+ LicensingGrantMessage,
16
+ LicensingGrantModuleDescriptor,
17
+ LicensingProofMessage,
18
+ LicensingWrappedContentKey,
19
+ LicensingWrappedContentKeyHeader,
20
+ } from "../index.js";
@@ -0,0 +1,11 @@
1
+ export {
2
+ LicensingProtocolError,
3
+ decodeLicensingChallengeMessage,
4
+ decodeLicensingGrant,
5
+ decodeLicensingProofMessage,
6
+ encodeLicensingChallengeRequest,
7
+ encodeLicensingProof,
8
+ extractGrantModuleDescriptor,
9
+ extractWrappedContentKey,
10
+ validateLicensingGrant,
11
+ } from "./records.js";
@@ -0,0 +1,858 @@
1
+ import * as flatbuffers from "flatbuffers";
2
+
3
+ import {
4
+ LCH,
5
+ licensingChallengeMessageType,
6
+ licensingChallengeRole,
7
+ } from "spacedatastandards.org/lib/js/LCH/main.js";
8
+ import { KMF, keyMaterialAlgorithm, keyMaterialEncoding, keyMaterialRole } from "spacedatastandards.org/lib/js/KMF/main.js";
9
+ import {
10
+ ENC,
11
+ KDF,
12
+ KeyExchange,
13
+ LGR,
14
+ PLG,
15
+ SymmetricAlgo,
16
+ licensingGrantMessageType,
17
+ } from "spacedatastandards.org/lib/js/LGR/main.js";
18
+ import { LPF, licensingProofMessageType } from "spacedatastandards.org/lib/js/LPF/main.js";
19
+
20
+ export class LicensingProtocolError extends Error {
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.name = "LicensingProtocolError";
24
+ this.code = code;
25
+ }
26
+ }
27
+
28
+ export function encodeLicensingChallengeRequest(options) {
29
+ const builder = new flatbuffers.Builder(512);
30
+ const reqIdOffset = builder.createString(
31
+ normalizeRequiredString(options.reqId, "reqId"),
32
+ );
33
+ const moduleIdOffset = builder.createString(
34
+ normalizeRequiredString(options.moduleId, "moduleId"),
35
+ );
36
+ const moduleVersionOffset = createOptionalString(builder, options.moduleVersion);
37
+ const requesterPeerIdOffset = builder.createString(
38
+ normalizeRequiredString(options.requesterPeerId, "requesterPeerId"),
39
+ );
40
+ const requesterXpubOffset = createOptionalString(builder, options.requesterXpub);
41
+ const requesterSigningPubkeyOffset = LCH.createRequesterSigningPubkeyVector(
42
+ builder,
43
+ cloneRequiredBytes(
44
+ options.requesterSigningPublicKey,
45
+ "requesterSigningPublicKey",
46
+ ),
47
+ );
48
+ const requesterEphemeralPubkeyOffset = LCH.createRequesterEphemeralPubkeyVector(
49
+ builder,
50
+ cloneRequiredBytes(
51
+ options.requesterEphemeralPublicKey,
52
+ "requesterEphemeralPublicKey",
53
+ ),
54
+ );
55
+ const requesterDomainOffset = builder.createString(
56
+ normalizeRequiredString(options.requesterDomain, "requesterDomain"),
57
+ );
58
+ const providerPeerIdOffset = builder.createString(
59
+ normalizeRequiredString(options.providerPeerId, "providerPeerId"),
60
+ );
61
+ const root = LCH.createLCH(
62
+ builder,
63
+ licensingChallengeMessageType.Request,
64
+ licensingChallengeRole.Requester,
65
+ reqIdOffset,
66
+ moduleIdOffset,
67
+ moduleVersionOffset,
68
+ requesterPeerIdOffset,
69
+ requesterXpubOffset,
70
+ requesterSigningPubkeyOffset,
71
+ requesterEphemeralPubkeyOffset,
72
+ requesterDomainOffset,
73
+ BigInt(normalizePositiveInteger(options.requestedTimeoutMs, "requestedTimeoutMs")),
74
+ BigInt(normalizeInteger(options.requestedAtMs, "requestedAtMs")),
75
+ 0,
76
+ 0n,
77
+ providerPeerIdOffset,
78
+ 0,
79
+ 0,
80
+ );
81
+ LCH.finishLCHBuffer(builder, root);
82
+ return builder.asUint8Array();
83
+ }
84
+
85
+ export function decodeLicensingChallengeMessage(bytes) {
86
+ const payload = toUint8Array(bytes, "challenge bytes");
87
+ const bb = new flatbuffers.ByteBuffer(payload);
88
+ if (!LCH.bufferHasIdentifier(bb)) {
89
+ throw new LicensingProtocolError(
90
+ "invalid_response",
91
+ "invalid licensing challenge identifier",
92
+ );
93
+ }
94
+
95
+ const message = LCH.getRootAsLCH(bb);
96
+ const reqId = message.REQUEST_ID();
97
+ const moduleId = message.MODULE_ID();
98
+ if (!reqId || !moduleId) {
99
+ throw new LicensingProtocolError(
100
+ "invalid_response",
101
+ "challenge message is missing required identifiers",
102
+ );
103
+ }
104
+
105
+ const messageType = challengeMessageTypeName(message.MESSAGE_TYPE());
106
+ const role = challengeRoleName(message.ROLE());
107
+
108
+ if (message.MESSAGE_TYPE() === licensingChallengeMessageType.Error) {
109
+ return {
110
+ messageType,
111
+ role,
112
+ reqId,
113
+ moduleId,
114
+ moduleVersion: trimOptional(message.MODULE_VERSION()),
115
+ errorCode: normalizeProtocolCode(message.ERROR_CODE(), "challenge_rejected"),
116
+ errorMessage: trimOptional(message.ERROR_MESSAGE()),
117
+ rawBytes: cloneBytes(payload),
118
+ };
119
+ }
120
+
121
+ const decoded = {
122
+ messageType,
123
+ role,
124
+ reqId,
125
+ moduleId,
126
+ moduleVersion: trimOptional(message.MODULE_VERSION()),
127
+ requesterPeerId: trimOptional(message.REQUESTER_PEER_ID()),
128
+ requesterXpub: trimOptional(message.REQUESTER_XPUB()),
129
+ requesterSigningPublicKey: cloneOptionalBytes(
130
+ message.requesterSigningPubkeyArray(),
131
+ ),
132
+ requesterEphemeralPublicKey: cloneOptionalBytes(
133
+ message.requesterEphemeralPubkeyArray(),
134
+ ),
135
+ requestedDomain: trimOptional(message.REQUESTED_DOMAIN()),
136
+ requestedTimeoutMs: numberFromUint64(
137
+ message.REQUESTED_TIMEOUT_MS(),
138
+ "challenge.REQUESTED_TIMEOUT_MS",
139
+ ),
140
+ requestedAtMs: numberFromUint64(
141
+ message.REQUESTED_AT(),
142
+ "challenge.REQUESTED_AT",
143
+ ),
144
+ challengeNonce: cloneOptionalBytes(message.challengeNonceArray()),
145
+ expiresAtMs: numberFromUint64(message.EXPIRES_AT(), "challenge.EXPIRES_AT"),
146
+ providerPeerId: trimOptional(message.PROVIDER_PEER_ID()),
147
+ errorCode: trimOptional(message.ERROR_CODE()),
148
+ errorMessage: trimOptional(message.ERROR_MESSAGE()),
149
+ rawBytes: cloneBytes(payload),
150
+ };
151
+
152
+ if (
153
+ message.MESSAGE_TYPE() === licensingChallengeMessageType.Response &&
154
+ decoded.challengeNonce.length === 0
155
+ ) {
156
+ throw new LicensingProtocolError(
157
+ "invalid_response",
158
+ "challenge response is missing the challenge nonce",
159
+ );
160
+ }
161
+
162
+ return decoded;
163
+ }
164
+
165
+ export function encodeLicensingProof(options) {
166
+ const builder = new flatbuffers.Builder(512);
167
+ const reqIdOffset = builder.createString(
168
+ normalizeRequiredString(options.reqId, "reqId"),
169
+ );
170
+ const moduleIdOffset = builder.createString(
171
+ normalizeRequiredString(options.moduleId, "moduleId"),
172
+ );
173
+ const moduleVersionOffset = createOptionalString(builder, options.moduleVersion);
174
+ const requesterPeerIdOffset = builder.createString(
175
+ normalizeRequiredString(options.requesterPeerId, "requesterPeerId"),
176
+ );
177
+ const requesterXpubOffset = createOptionalString(builder, options.requesterXpub);
178
+ const requesterDomainOffset = builder.createString(
179
+ normalizeRequiredString(options.requesterDomain, "requesterDomain"),
180
+ );
181
+ const requesterEphemeralPubkeyOffset = LPF.createRequesterEphemeralPubkeyVector(
182
+ builder,
183
+ cloneRequiredBytes(
184
+ options.requesterEphemeralPublicKey,
185
+ "requesterEphemeralPublicKey",
186
+ ),
187
+ );
188
+ const challengeNonceOffset = LPF.createChallengeNonceVector(
189
+ builder,
190
+ cloneRequiredBytes(options.challengeNonce, "challengeNonce"),
191
+ );
192
+ const providerPeerIdOffset = builder.createString(
193
+ normalizeRequiredString(options.providerPeerId, "providerPeerId"),
194
+ );
195
+ const signatureOffset = LPF.createSignatureVector(
196
+ builder,
197
+ cloneRequiredBytes(options.signature, "signature"),
198
+ );
199
+ const signingPubkeyOffset = LPF.createSigningPubkeyVector(
200
+ builder,
201
+ cloneRequiredBytes(
202
+ options.requesterSigningPublicKey,
203
+ "requesterSigningPublicKey",
204
+ ),
205
+ );
206
+ const root = LPF.createLPF(
207
+ builder,
208
+ licensingProofMessageType.ProofRequest,
209
+ reqIdOffset,
210
+ moduleIdOffset,
211
+ moduleVersionOffset,
212
+ requesterPeerIdOffset,
213
+ requesterXpubOffset,
214
+ requesterDomainOffset,
215
+ BigInt(normalizePositiveInteger(options.requestedTimeoutMs, "requestedTimeoutMs")),
216
+ requesterEphemeralPubkeyOffset,
217
+ challengeNonceOffset,
218
+ BigInt(normalizeInteger(options.challengeExpiresAtMs, "challengeExpiresAtMs")),
219
+ providerPeerIdOffset,
220
+ signatureOffset,
221
+ signingPubkeyOffset,
222
+ BigInt(normalizeInteger(options.timestampMs, "timestampMs")),
223
+ 0,
224
+ 0,
225
+ );
226
+ LPF.finishLPFBuffer(builder, root);
227
+ return builder.asUint8Array();
228
+ }
229
+
230
+ export function decodeLicensingProofMessage(bytes) {
231
+ const payload = toUint8Array(bytes, "proof bytes");
232
+ const bb = new flatbuffers.ByteBuffer(payload);
233
+ if (!LPF.bufferHasIdentifier(bb)) {
234
+ throw new LicensingProtocolError(
235
+ "invalid_response",
236
+ "invalid licensing proof identifier",
237
+ );
238
+ }
239
+
240
+ const message = LPF.getRootAsLPF(bb);
241
+ const reqId = message.REQUEST_ID();
242
+ const moduleId = message.MODULE_ID();
243
+ if (!reqId || !moduleId) {
244
+ throw new LicensingProtocolError(
245
+ "invalid_response",
246
+ "proof message is missing required identifiers",
247
+ );
248
+ }
249
+
250
+ return {
251
+ messageType: proofMessageTypeName(message.MESSAGE_TYPE()),
252
+ reqId,
253
+ moduleId,
254
+ moduleVersion: trimOptional(message.MODULE_VERSION()),
255
+ requesterPeerId: trimOptional(message.REQUESTER_PEER_ID()),
256
+ requesterXpub: trimOptional(message.REQUESTER_XPUB()),
257
+ requestedDomain: trimOptional(message.REQUESTED_DOMAIN()),
258
+ requestedTimeoutMs: numberFromUint64(
259
+ message.REQUESTED_TIMEOUT_MS(),
260
+ "proof.REQUESTED_TIMEOUT_MS",
261
+ ),
262
+ requesterEphemeralPublicKey: cloneOptionalBytes(
263
+ message.requesterEphemeralPubkeyArray(),
264
+ ),
265
+ challengeNonce: cloneOptionalBytes(message.challengeNonceArray()),
266
+ challengeExpiresAtMs: numberFromUint64(
267
+ message.CHALLENGE_EXPIRES_AT(),
268
+ "proof.CHALLENGE_EXPIRES_AT",
269
+ ),
270
+ providerPeerId: trimOptional(message.PROVIDER_PEER_ID()),
271
+ signature: cloneOptionalBytes(message.signatureArray()),
272
+ requesterSigningPublicKey: cloneOptionalBytes(message.signingPubkeyArray()),
273
+ timestampMs: numberFromUint64(message.TIMESTAMP_MS(), "proof.TIMESTAMP_MS"),
274
+ rejectionCode: trimOptional(message.REJECTION_CODE()),
275
+ rejectionMessage: trimOptional(message.REJECTION_MESSAGE()),
276
+ rawBytes: cloneBytes(payload),
277
+ };
278
+ }
279
+
280
+ export function decodeLicensingGrant(bytes) {
281
+ const payload = toUint8Array(bytes, "grant bytes");
282
+ const bb = new flatbuffers.ByteBuffer(payload);
283
+ if (!LGR.bufferHasIdentifier(bb)) {
284
+ throw new LicensingProtocolError(
285
+ "invalid_response",
286
+ "invalid licensing grant identifier",
287
+ );
288
+ }
289
+
290
+ const grant = LGR.getRootAsLGR(bb);
291
+ const reqId = grant.REQUEST_ID();
292
+ const moduleId = grant.MODULE_ID();
293
+ if (!reqId || !moduleId) {
294
+ throw new LicensingProtocolError(
295
+ "invalid_response",
296
+ "grant response is missing required identifiers",
297
+ );
298
+ }
299
+
300
+ const messageType = grantMessageTypeName(grant.MESSAGE_TYPE());
301
+ const decoded = {
302
+ messageType,
303
+ reqId,
304
+ moduleId,
305
+ moduleVersion: trimOptional(grant.MODULE_VERSION()),
306
+ requesterPeerId: trimOptional(grant.REQUESTER_PEER_ID()),
307
+ requesterXpub: trimOptional(grant.REQUESTER_XPUB()),
308
+ requestedDomain: trimOptional(grant.REQUESTED_DOMAIN()),
309
+ requestedTimeoutMs: numberFromUint64(
310
+ grant.REQUESTED_TIMEOUT_MS(),
311
+ "grant.REQUESTED_TIMEOUT_MS",
312
+ ),
313
+ grantedDomain: trimOptional(grant.GRANTED_DOMAIN()),
314
+ grantedTimeoutMs: numberFromUint64(
315
+ grant.GRANTED_TIMEOUT_MS(),
316
+ "grant.GRANTED_TIMEOUT_MS",
317
+ ),
318
+ expiresAtMs: numberFromUint64(grant.EXPIRES_AT(), "grant.EXPIRES_AT"),
319
+ requiredScope: trimOptional(grant.REQUIRED_SCOPE()),
320
+ grantStatus: trimOptional(grant.GRANT_STATUS()),
321
+ denialReason: trimOptional(grant.DENIAL_REASON()),
322
+ capabilityToken: cloneOptionalBytes(grant.capabilityTokenArray()),
323
+ grantVerifierPublicKey: cloneOptionalBytes(
324
+ grant.grantVerifierPubkeyArray(),
325
+ ),
326
+ providerSignature: cloneOptionalBytes(grant.providerSignatureArray()),
327
+ moduleDescriptor: null,
328
+ wrappedContentKey: null,
329
+ rawBytes: cloneBytes(payload),
330
+ };
331
+
332
+ if (grant.MESSAGE_TYPE() === licensingGrantMessageType.Granted) {
333
+ decoded.moduleDescriptor = decodeGrantModuleDescriptor(grant.MODULE_DESCRIPTOR());
334
+ decoded.wrappedContentKey = decodeWrappedContentKey(
335
+ grant.WRAPPED_CONTENT_KEY_HEADER(),
336
+ grant.wrappedContentKeyPayloadArray(),
337
+ decoded.expiresAtMs,
338
+ );
339
+ }
340
+
341
+ return decoded;
342
+ }
343
+
344
+ export function validateLicensingGrant(grant, options = {}) {
345
+ if (!grant || typeof grant !== "object") {
346
+ throw new TypeError("grant is required");
347
+ }
348
+
349
+ if (grant.messageType === "denied") {
350
+ throw new LicensingProtocolError(
351
+ normalizeProtocolCode(grant.grantStatus, "grant_denied"),
352
+ grant.denialReason || "grant request denied",
353
+ );
354
+ }
355
+
356
+ if (grant.messageType !== "granted") {
357
+ throw new LicensingProtocolError(
358
+ "unexpected_response",
359
+ "expected licensing grant response",
360
+ );
361
+ }
362
+
363
+ if (!grant.moduleDescriptor || !grant.wrappedContentKey) {
364
+ throw new LicensingProtocolError(
365
+ "invalid_grant",
366
+ "grant response is missing the module descriptor or wrapped content key",
367
+ );
368
+ }
369
+
370
+ const grantedDomain = trimOptional(grant.grantedDomain);
371
+ if (!grantedDomain) {
372
+ throw new LicensingProtocolError(
373
+ "invalid_grant",
374
+ "grant response is missing the granted domain",
375
+ );
376
+ }
377
+ if (grant.grantedTimeoutMs <= 0) {
378
+ throw new LicensingProtocolError(
379
+ "grant_policy_mismatch",
380
+ "grant timeout must be a positive duration",
381
+ );
382
+ }
383
+
384
+ if (options.reqId && grant.reqId !== options.reqId) {
385
+ throw new LicensingProtocolError(
386
+ "request_mismatch",
387
+ "grant response request id mismatch",
388
+ );
389
+ }
390
+ if (options.moduleId && grant.moduleId !== options.moduleId) {
391
+ throw new LicensingProtocolError(
392
+ "request_mismatch",
393
+ "grant response module id mismatch",
394
+ );
395
+ }
396
+ if (
397
+ options.moduleVersion &&
398
+ grant.moduleVersion &&
399
+ grant.moduleVersion !== options.moduleVersion
400
+ ) {
401
+ throw new LicensingProtocolError(
402
+ "request_mismatch",
403
+ "grant response module version mismatch",
404
+ );
405
+ }
406
+ if (
407
+ (options.expectedDomain ?? grant.requestedDomain) &&
408
+ grantedDomain !== (options.expectedDomain ?? grant.requestedDomain)
409
+ ) {
410
+ throw new LicensingProtocolError(
411
+ "grant_policy_mismatch",
412
+ "grant domain does not match the requested domain",
413
+ );
414
+ }
415
+ if (
416
+ Number.isFinite(options.requestedTimeoutMs) &&
417
+ (grant.grantedTimeoutMs <= 0 ||
418
+ grant.grantedTimeoutMs > options.requestedTimeoutMs)
419
+ ) {
420
+ throw new LicensingProtocolError(
421
+ "grant_policy_mismatch",
422
+ "grant timeout exceeds the requested timeout",
423
+ );
424
+ }
425
+ const expectedVerifierKeyLength = Number.isFinite(
426
+ options.grantVerifierPublicKeyLength,
427
+ )
428
+ ? options.grantVerifierPublicKeyLength
429
+ : 32;
430
+ if (grant.grantVerifierPublicKey.length !== expectedVerifierKeyLength) {
431
+ throw new LicensingProtocolError(
432
+ "invalid_grant",
433
+ `grant verifier public key must be ${expectedVerifierKeyLength} bytes`,
434
+ );
435
+ }
436
+
437
+ return grant;
438
+ }
439
+
440
+ export function extractGrantModuleDescriptor(grant) {
441
+ if (!grant?.moduleDescriptor) {
442
+ throw new LicensingProtocolError(
443
+ "invalid_grant",
444
+ "grant response is missing the module descriptor",
445
+ );
446
+ }
447
+ return grant.moduleDescriptor;
448
+ }
449
+
450
+ export function extractWrappedContentKey(grant) {
451
+ if (!grant?.wrappedContentKey) {
452
+ throw new LicensingProtocolError(
453
+ "invalid_grant",
454
+ "grant response is missing the wrapped content key",
455
+ );
456
+ }
457
+ return grant.wrappedContentKey;
458
+ }
459
+
460
+ function decodeGrantModuleDescriptor(moduleDescriptor) {
461
+ if (!moduleDescriptor) {
462
+ throw new LicensingProtocolError(
463
+ "invalid_grant",
464
+ "grant response is missing the module descriptor",
465
+ );
466
+ }
467
+
468
+ const cid = moduleDescriptor.WASM_CID();
469
+ if (!cid) {
470
+ throw new LicensingProtocolError(
471
+ "invalid_grant",
472
+ "grant response is missing the published CID",
473
+ );
474
+ }
475
+
476
+ const encrypted = Boolean(moduleDescriptor.ENCRYPTED());
477
+ const contentHash = encrypted
478
+ ? cloneOptionalBytes(
479
+ moduleDescriptor.encryptedWasmHashArray() ??
480
+ moduleDescriptor.wasmHashArray(),
481
+ )
482
+ : cloneOptionalBytes(moduleDescriptor.wasmHashArray());
483
+ const sizeBytes =
484
+ encrypted && moduleDescriptor.ENCRYPTED_WASM_SIZE() > 0n
485
+ ? numberFromUint64(
486
+ moduleDescriptor.ENCRYPTED_WASM_SIZE(),
487
+ "moduleDescriptor.ENCRYPTED_WASM_SIZE",
488
+ )
489
+ : numberFromUint64(
490
+ moduleDescriptor.WASM_SIZE(),
491
+ "moduleDescriptor.WASM_SIZE",
492
+ );
493
+
494
+ return {
495
+ cid,
496
+ contentHash,
497
+ sizeBytes,
498
+ moduleId: normalizeRequiredString(
499
+ moduleDescriptor.PLUGIN_ID() || "",
500
+ "moduleDescriptor.PLUGIN_ID",
501
+ ),
502
+ moduleVersion: trimOptional(moduleDescriptor.VERSION()),
503
+ requiredScope: trimOptional(moduleDescriptor.REQUIRED_SCOPE()),
504
+ keyId: trimOptional(moduleDescriptor.KEY_ID()),
505
+ allowedDomains: readAllowedDomains(moduleDescriptor),
506
+ maxGrantTimeoutMs: numberFromUint64(
507
+ moduleDescriptor.MAX_GRANT_TIMEOUT_MS(),
508
+ "moduleDescriptor.MAX_GRANT_TIMEOUT_MS",
509
+ ),
510
+ encrypted,
511
+ };
512
+ }
513
+
514
+ function decodeWrappedContentKey(header, payload, expiresAtMs) {
515
+ const encryptedPayload = cloneOptionalBytes(payload);
516
+ if (!header || encryptedPayload.length === 0) {
517
+ throw new LicensingProtocolError(
518
+ "invalid_grant",
519
+ "grant response is missing the wrapped content key payload",
520
+ );
521
+ }
522
+
523
+ const providerEphemeralPublicKey = cloneOptionalBytes(
524
+ header.ephemeralPublicKeyArray(),
525
+ );
526
+ const nonceStart = cloneOptionalBytes(header.nonceStartArray());
527
+ const recipientKeyIdBytes = cloneOptionalBytes(header.recipientKeyIdArray());
528
+ const schemaHash = cloneOptionalBytes(header.schemaHashArray());
529
+ if (providerEphemeralPublicKey.length === 0 || nonceStart.length === 0) {
530
+ throw new LicensingProtocolError(
531
+ "invalid_grant",
532
+ "wrapped content key header is incomplete",
533
+ );
534
+ }
535
+
536
+ const keyMaterial = decodeWrappedKeyMaterialFrame(encryptedPayload);
537
+ return {
538
+ wrappingAlgorithm: wrappedContentKeySchemeName(header),
539
+ contentKeyId: keyMaterial?.keyId,
540
+ contentKeyRole: keyMaterial?.roleName,
541
+ contentKeyAlgorithm: keyMaterial?.algorithmName,
542
+ contentKeyEncoding: keyMaterial?.encodingName,
543
+ keyBytes: keyMaterial?.keyBytes ?? new Uint8Array(0),
544
+ contentKeyVersion: keyMaterial?.version,
545
+ recipientKeyId:
546
+ recipientKeyIdBytes.length > 0 ? bytesToHex(recipientKeyIdBytes) : undefined,
547
+ requesterEphemeralPublicKey: new Uint8Array(0),
548
+ providerEphemeralPublicKey,
549
+ hkdfSalt: new Uint8Array(0),
550
+ iv: nonceStart,
551
+ ciphertext: encryptedPayload,
552
+ tag: new Uint8Array(0),
553
+ expiresAtMs,
554
+ recipientPublicKey: new Uint8Array(0),
555
+ ephemeralPublicKey: providerEphemeralPublicKey,
556
+ nonce: nonceStart,
557
+ header: {
558
+ version: header.VERSION(),
559
+ keyExchange: keyExchangeName(header.KEY_EXCHANGE()),
560
+ symmetric: symmetricAlgorithmName(header.SYMMETRIC()),
561
+ keyDerivation: keyDerivationName(header.KEY_DERIVATION()),
562
+ ephemeralPublicKey: providerEphemeralPublicKey,
563
+ nonceStart,
564
+ recipientKeyId: recipientKeyIdBytes,
565
+ context: trimOptional(header.CONTEXT()),
566
+ schemaHash,
567
+ rootType: trimOptional(header.ROOT_TYPE()),
568
+ timestamp:
569
+ header.TIMESTAMP() > 0n
570
+ ? numberFromUint64(
571
+ header.TIMESTAMP(),
572
+ "wrappedContentKeyHeader.TIMESTAMP",
573
+ )
574
+ : undefined,
575
+ },
576
+ encryptedPayload,
577
+ recipientKeyIdBytes,
578
+ schemaHash,
579
+ keyMaterialRootType: trimOptional(header.ROOT_TYPE()),
580
+ };
581
+ }
582
+
583
+ function decodeWrappedKeyMaterialFrame(bytes) {
584
+ const bb = new flatbuffers.ByteBuffer(bytes);
585
+ if (!KMF.bufferHasIdentifier(bb)) {
586
+ return null;
587
+ }
588
+
589
+ const kmf = KMF.getRootAsKMF(bb);
590
+ return {
591
+ keyId: trimOptional(kmf.KEY_ID()),
592
+ role: kmf.ROLE(),
593
+ roleName: keyMaterialRoleName(kmf.ROLE()),
594
+ algorithm: kmf.ALGORITHM(),
595
+ algorithmName: keyMaterialAlgorithmName(kmf.ALGORITHM()),
596
+ encoding: kmf.ENCODING(),
597
+ encodingName: keyMaterialEncodingName(kmf.ENCODING()),
598
+ keyBytes: cloneOptionalBytes(kmf.keyBytesArray()),
599
+ version: kmf.VERSION(),
600
+ expiresAtMs: numberFromUint64(kmf.EXPIRES_AT(), "kmf.EXPIRES_AT"),
601
+ };
602
+ }
603
+
604
+ function createOptionalString(builder, value) {
605
+ const normalized = trimOptional(value);
606
+ return normalized ? builder.createString(normalized) : 0;
607
+ }
608
+
609
+ function toUint8Array(value, label) {
610
+ if (value instanceof Uint8Array) {
611
+ return value;
612
+ }
613
+ if (value instanceof ArrayBuffer) {
614
+ return new Uint8Array(value);
615
+ }
616
+ if (ArrayBuffer.isView(value)) {
617
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
618
+ }
619
+ throw new TypeError(`${label} must be a Uint8Array, ArrayBuffer, or view.`);
620
+ }
621
+
622
+ function normalizeRequiredString(value, name) {
623
+ const normalized = String(value ?? "").trim();
624
+ if (!normalized) {
625
+ throw new Error(`${name} is required`);
626
+ }
627
+ return normalized;
628
+ }
629
+
630
+ function trimOptional(value) {
631
+ const normalized = String(value ?? "").trim();
632
+ return normalized || undefined;
633
+ }
634
+
635
+ function normalizeInteger(value, name) {
636
+ if (!Number.isFinite(value)) {
637
+ throw new Error(`${name} must be a finite number`);
638
+ }
639
+ return Math.trunc(value);
640
+ }
641
+
642
+ function normalizePositiveInteger(value, name) {
643
+ const normalized = normalizeInteger(value, name);
644
+ if (normalized <= 0) {
645
+ throw new Error(`${name} must be a positive number`);
646
+ }
647
+ return normalized;
648
+ }
649
+
650
+ function cloneRequiredBytes(value, name) {
651
+ const bytes = cloneOptionalBytes(value);
652
+ if (bytes.length === 0) {
653
+ throw new Error(`${name} is required`);
654
+ }
655
+ return bytes;
656
+ }
657
+
658
+ function cloneOptionalBytes(value) {
659
+ if (!value) {
660
+ return new Uint8Array(0);
661
+ }
662
+ return value instanceof Uint8Array ? value.slice() : Uint8Array.from(value);
663
+ }
664
+
665
+ function cloneBytes(value) {
666
+ return value.slice();
667
+ }
668
+
669
+ function numberFromUint64(value, name) {
670
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
671
+ throw new LicensingProtocolError(
672
+ "invalid_response",
673
+ `${name} exceeds JavaScript safe integer range`,
674
+ );
675
+ }
676
+ return Number(value);
677
+ }
678
+
679
+ function normalizeProtocolCode(value, fallback) {
680
+ const normalized = String(value ?? "").trim();
681
+ return normalized || fallback;
682
+ }
683
+
684
+ function challengeMessageTypeName(value) {
685
+ switch (value) {
686
+ case licensingChallengeMessageType.Request:
687
+ return "request";
688
+ case licensingChallengeMessageType.Response:
689
+ return "response";
690
+ case licensingChallengeMessageType.Error:
691
+ return "error";
692
+ default:
693
+ return `unknown_${value}`;
694
+ }
695
+ }
696
+
697
+ function challengeRoleName(value) {
698
+ switch (value) {
699
+ case licensingChallengeRole.Requester:
700
+ return "requester";
701
+ case licensingChallengeRole.Provider:
702
+ return "provider";
703
+ default:
704
+ return `unknown_${value}`;
705
+ }
706
+ }
707
+
708
+ function proofMessageTypeName(value) {
709
+ switch (value) {
710
+ case licensingProofMessageType.ProofRequest:
711
+ return "proof-request";
712
+ case licensingProofMessageType.ProofAccepted:
713
+ return "proof-accepted";
714
+ case licensingProofMessageType.ProofRejected:
715
+ return "proof-rejected";
716
+ default:
717
+ return `unknown_${value}`;
718
+ }
719
+ }
720
+
721
+ function grantMessageTypeName(value) {
722
+ switch (value) {
723
+ case licensingGrantMessageType.Request:
724
+ return "request";
725
+ case licensingGrantMessageType.Granted:
726
+ return "granted";
727
+ case licensingGrantMessageType.Denied:
728
+ return "denied";
729
+ default:
730
+ return `unknown_${value}`;
731
+ }
732
+ }
733
+
734
+ function wrappedContentKeySchemeName(value) {
735
+ if (
736
+ value.KEY_EXCHANGE() === KeyExchange.X25519 &&
737
+ value.KEY_DERIVATION() === KDF.HKDF_SHA256 &&
738
+ value.SYMMETRIC() === SymmetricAlgo.AES_256_CTR
739
+ ) {
740
+ return "x25519-hkdf-sha256-aes-256-ctr-rec";
741
+ }
742
+ return [
743
+ keyExchangeName(value.KEY_EXCHANGE()),
744
+ keyDerivationName(value.KEY_DERIVATION()),
745
+ symmetricAlgorithmName(value.SYMMETRIC()),
746
+ "rec",
747
+ ]
748
+ .join("-")
749
+ .toLowerCase();
750
+ }
751
+
752
+ function keyExchangeName(value) {
753
+ switch (value) {
754
+ case KeyExchange.X25519:
755
+ return "X25519";
756
+ case KeyExchange.Secp256k1:
757
+ return "Secp256k1";
758
+ case KeyExchange.P256:
759
+ return "P256";
760
+ default:
761
+ return `UNKNOWN_${value}`;
762
+ }
763
+ }
764
+
765
+ function symmetricAlgorithmName(value) {
766
+ switch (value) {
767
+ case SymmetricAlgo.AES_256_CTR:
768
+ return "AES_256_CTR";
769
+ default:
770
+ return `UNKNOWN_${value}`;
771
+ }
772
+ }
773
+
774
+ function keyDerivationName(value) {
775
+ switch (value) {
776
+ case KDF.HKDF_SHA256:
777
+ return "HKDF_SHA256";
778
+ default:
779
+ return `UNKNOWN_${value}`;
780
+ }
781
+ }
782
+
783
+ function keyMaterialRoleName(value) {
784
+ switch (value) {
785
+ case keyMaterialRole.PublicationContent:
786
+ return "PublicationContent";
787
+ case keyMaterialRole.RequesterSigning:
788
+ return "RequesterSigning";
789
+ case keyMaterialRole.VerificationKey:
790
+ return "VerificationKey";
791
+ case keyMaterialRole.DecryptKey:
792
+ return "DecryptKey";
793
+ case keyMaterialRole.Unknown:
794
+ return "Unknown";
795
+ default:
796
+ return `UNKNOWN_${value}`;
797
+ }
798
+ }
799
+
800
+ function keyMaterialAlgorithmName(value) {
801
+ switch (value) {
802
+ case keyMaterialAlgorithm.Ed25519Seed:
803
+ return "Ed25519Seed";
804
+ case keyMaterialAlgorithm.Ed25519Public:
805
+ return "Ed25519Public";
806
+ case keyMaterialAlgorithm.X25519Private:
807
+ return "X25519Private";
808
+ case keyMaterialAlgorithm.X25519Public:
809
+ return "X25519Public";
810
+ case keyMaterialAlgorithm.Aes256Gcm:
811
+ return "Aes256Gcm";
812
+ case keyMaterialAlgorithm.Opaque:
813
+ return "Opaque";
814
+ case keyMaterialAlgorithm.Unknown:
815
+ return "Unknown";
816
+ default:
817
+ return `UNKNOWN_${value}`;
818
+ }
819
+ }
820
+
821
+ function keyMaterialEncodingName(value) {
822
+ switch (value) {
823
+ case keyMaterialEncoding.RawBytes:
824
+ return "RawBytes";
825
+ case keyMaterialEncoding.Seed32:
826
+ return "Seed32";
827
+ case keyMaterialEncoding.PublicKey32:
828
+ return "PublicKey32";
829
+ case keyMaterialEncoding.Unknown:
830
+ return "Unknown";
831
+ default:
832
+ return `UNKNOWN_${value}`;
833
+ }
834
+ }
835
+
836
+ function bytesToHex(bytes) {
837
+ return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join(
838
+ "",
839
+ );
840
+ }
841
+
842
+ function readAllowedDomains(descriptor) {
843
+ const allowedDomains = [];
844
+ for (let index = 0; index < descriptor.allowedDomainsLength(); index += 1) {
845
+ const domain = descriptor.ALLOWED_DOMAINS(index);
846
+ if (domain) {
847
+ allowedDomains.push(domain);
848
+ }
849
+ }
850
+ return allowedDomains;
851
+ }
852
+
853
+ export {
854
+ bytesToHex,
855
+ keyDerivationName,
856
+ keyExchangeName,
857
+ symmetricAlgorithmName,
858
+ };