@continuonai/rcan-ts 0.8.0 → 1.2.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/README.md +1 -0
- package/dist/browser.d.mts +482 -39
- package/dist/browser.mjs +627 -73
- package/dist/browser.mjs.map +1 -1
- package/dist/index.d.mts +482 -39
- package/dist/index.d.ts +482 -39
- package/dist/index.js +665 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +627 -73
- package/dist/index.mjs.map +1 -1
- package/dist/rcan-validate.js +22 -2
- package/dist/rcan.iife.js +16 -3
- package/package.json +28 -5
package/dist/index.js
CHANGED
|
@@ -30,20 +30,27 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AUTHORITY_ERROR_CODES: () => AUTHORITY_ERROR_CODES,
|
|
33
34
|
AuditChain: () => AuditChain,
|
|
34
35
|
AuditError: () => AuditError,
|
|
36
|
+
COMPETITION_SCOPE_LEVEL: () => COMPETITION_SCOPE_LEVEL,
|
|
35
37
|
CONTRIBUTE_SCOPE_LEVEL: () => CONTRIBUTE_SCOPE_LEVEL,
|
|
36
38
|
ClockDriftError: () => ClockDriftError,
|
|
37
39
|
CommitmentRecord: () => CommitmentRecord,
|
|
38
40
|
ConfidenceGate: () => ConfidenceGate,
|
|
39
41
|
DEFAULT_LOA_POLICY: () => DEFAULT_LOA_POLICY,
|
|
40
42
|
DataCategory: () => DataCategory,
|
|
43
|
+
FIRMWARE_MANIFEST_PATH: () => FIRMWARE_MANIFEST_PATH,
|
|
41
44
|
FaultCode: () => FaultCode,
|
|
42
45
|
FederationSyncType: () => FederationSyncType,
|
|
46
|
+
FirmwareIntegrityError: () => FirmwareIntegrityError,
|
|
43
47
|
GateError: () => GateError,
|
|
44
48
|
HiTLGate: () => HiTLGate,
|
|
45
49
|
KeyStore: () => KeyStore,
|
|
46
50
|
LevelOfAssurance: () => LevelOfAssurance,
|
|
51
|
+
M2MAuthError: () => M2MAuthError,
|
|
52
|
+
M2M_TRUSTED_ISSUER: () => M2M_TRUSTED_ISSUER,
|
|
53
|
+
MLDSAKeyPair: () => MLDSAKeyPair,
|
|
47
54
|
MediaEncoding: () => MediaEncoding,
|
|
48
55
|
MessageType: () => MessageType,
|
|
49
56
|
NodeClient: () => NodeClient,
|
|
@@ -69,13 +76,18 @@ __export(index_exports, {
|
|
|
69
76
|
RCANValidationError: () => RCANValidationError,
|
|
70
77
|
RCANVersionIncompatibleError: () => RCANVersionIncompatibleError,
|
|
71
78
|
RCAN_VERSION: () => RCAN_VERSION,
|
|
79
|
+
ROLE_JWT_LEVEL: () => ROLE_JWT_LEVEL,
|
|
80
|
+
RRF_REVOCATION_CACHE_TTL_MS: () => RRF_REVOCATION_CACHE_TTL_MS,
|
|
81
|
+
RRF_REVOCATION_URL: () => RRF_REVOCATION_URL,
|
|
72
82
|
RegistryClient: () => RegistryClient,
|
|
73
83
|
RegistryTier: () => RegistryTier,
|
|
74
84
|
ReplayCache: () => ReplayCache,
|
|
75
85
|
RevocationCache: () => RevocationCache,
|
|
76
86
|
RobotURI: () => RobotURI,
|
|
77
87
|
RobotURIError: () => RobotURIError,
|
|
88
|
+
Role: () => Role,
|
|
78
89
|
SAFETY_MESSAGE_TYPE: () => SAFETY_MESSAGE_TYPE,
|
|
90
|
+
SCOPE_MIN_ROLE: () => SCOPE_MIN_ROLE,
|
|
79
91
|
SDK_VERSION: () => SDK_VERSION,
|
|
80
92
|
SPEC_VERSION: () => SPEC_VERSION,
|
|
81
93
|
TransportEncoding: () => TransportEncoding,
|
|
@@ -85,7 +97,11 @@ __export(index_exports, {
|
|
|
85
97
|
addDelegationHop: () => addDelegationHop,
|
|
86
98
|
addMediaInline: () => addMediaInline,
|
|
87
99
|
addMediaRef: () => addMediaRef,
|
|
100
|
+
addPQSignature: () => addPQSignature,
|
|
88
101
|
assertClockSynced: () => assertClockSynced,
|
|
102
|
+
authorityAccessFromWire: () => authorityAccessFromWire,
|
|
103
|
+
authorityAccessToWire: () => authorityAccessToWire,
|
|
104
|
+
canonicalManifestJson: () => canonicalManifestJson,
|
|
89
105
|
checkClockSync: () => checkClockSync,
|
|
90
106
|
checkRevocation: () => checkRevocation,
|
|
91
107
|
decodeBleFrames: () => decodeBleFrames,
|
|
@@ -94,11 +110,18 @@ __export(index_exports, {
|
|
|
94
110
|
encodeBleFrames: () => encodeBleFrames,
|
|
95
111
|
encodeCompact: () => encodeCompact,
|
|
96
112
|
encodeMinimal: () => encodeMinimal,
|
|
113
|
+
extractIdentityFromJwt: () => extractIdentityFromJwt,
|
|
97
114
|
extractLoaFromJwt: () => extractLoaFromJwt,
|
|
115
|
+
extractRoleFromJwt: () => extractRoleFromJwt,
|
|
98
116
|
fetchCanonicalSchema: () => fetchCanonicalSchema,
|
|
117
|
+
fetchRRFRevocations: () => fetchRRFRevocations,
|
|
118
|
+
isAuthorityRequestValid: () => isAuthorityRequestValid,
|
|
119
|
+
isM2mTrustedRevoked: () => isM2mTrustedRevoked,
|
|
99
120
|
isPreemptedBy: () => isPreemptedBy,
|
|
100
121
|
isSafetyMessage: () => isSafetyMessage,
|
|
101
122
|
makeCloudRelayMessage: () => makeCloudRelayMessage,
|
|
123
|
+
makeCompetitionEnter: () => makeCompetitionEnter,
|
|
124
|
+
makeCompetitionScore: () => makeCompetitionScore,
|
|
102
125
|
makeConfigUpdate: () => makeConfigUpdate,
|
|
103
126
|
makeConsentDeny: () => makeConsentDeny,
|
|
104
127
|
makeConsentGrant: () => makeConsentGrant,
|
|
@@ -111,8 +134,10 @@ __export(index_exports, {
|
|
|
111
134
|
makeFaultReport: () => makeFaultReport,
|
|
112
135
|
makeFederationSync: () => makeFederationSync,
|
|
113
136
|
makeKeyRotationMessage: () => makeKeyRotationMessage,
|
|
137
|
+
makePersonalResearchResult: () => makePersonalResearchResult,
|
|
114
138
|
makeResumeMessage: () => makeResumeMessage,
|
|
115
139
|
makeRevocationBroadcast: () => makeRevocationBroadcast,
|
|
140
|
+
makeSeasonStanding: () => makeSeasonStanding,
|
|
116
141
|
makeStopMessage: () => makeStopMessage,
|
|
117
142
|
makeStreamChunk: () => makeStreamChunk,
|
|
118
143
|
makeTrainingConsentDeny: () => makeTrainingConsentDeny,
|
|
@@ -120,7 +145,14 @@ __export(index_exports, {
|
|
|
120
145
|
makeTrainingConsentRequest: () => makeTrainingConsentRequest,
|
|
121
146
|
makeTrainingDataMessage: () => makeTrainingDataMessage,
|
|
122
147
|
makeTransparencyMessage: () => makeTransparencyMessage,
|
|
148
|
+
manifestFromWire: () => manifestFromWire,
|
|
149
|
+
manifestToWire: () => manifestToWire,
|
|
150
|
+
parseM2mPeerToken: () => parseM2mPeerToken,
|
|
151
|
+
parseM2mTrustedToken: () => parseM2mTrustedToken,
|
|
152
|
+
roleFromJwtLevel: () => roleFromJwtLevel,
|
|
123
153
|
selectTransport: () => selectTransport,
|
|
154
|
+
validateAuthorityAccess: () => validateAuthorityAccess,
|
|
155
|
+
validateCompetitionScope: () => validateCompetitionScope,
|
|
124
156
|
validateConfig: () => validateConfig,
|
|
125
157
|
validateConfigAgainstSchema: () => validateConfigAgainstSchema,
|
|
126
158
|
validateConfigUpdate: () => validateConfigUpdate,
|
|
@@ -129,14 +161,19 @@ __export(index_exports, {
|
|
|
129
161
|
validateCrossRegistryCommand: () => validateCrossRegistryCommand,
|
|
130
162
|
validateDelegationChain: () => validateDelegationChain,
|
|
131
163
|
validateLoaForScope: () => validateLoaForScope,
|
|
164
|
+
validateManifest: () => validateManifest,
|
|
132
165
|
validateMediaChunks: () => validateMediaChunks,
|
|
133
166
|
validateMessage: () => validateMessage,
|
|
134
167
|
validateNodeAgainstSchema: () => validateNodeAgainstSchema,
|
|
135
168
|
validateReplay: () => validateReplay,
|
|
169
|
+
validateRoleForScope: () => validateRoleForScope,
|
|
136
170
|
validateSafetyMessage: () => validateSafetyMessage,
|
|
137
171
|
validateTrainingDataMessage: () => validateTrainingDataMessage,
|
|
138
172
|
validateURI: () => validateURI,
|
|
139
|
-
validateVersionCompat: () => validateVersionCompat
|
|
173
|
+
validateVersionCompat: () => validateVersionCompat,
|
|
174
|
+
verifyM2mTrustedToken: () => verifyM2mTrustedToken,
|
|
175
|
+
verifyM2mTrustedTokenClaims: () => verifyM2mTrustedTokenClaims,
|
|
176
|
+
verifyPQSignature: () => verifyPQSignature
|
|
140
177
|
});
|
|
141
178
|
module.exports = __toCommonJS(index_exports);
|
|
142
179
|
|
|
@@ -231,8 +268,8 @@ var RobotURI = class _RobotURI {
|
|
|
231
268
|
};
|
|
232
269
|
|
|
233
270
|
// src/version.ts
|
|
234
|
-
var SPEC_VERSION = "
|
|
235
|
-
var SDK_VERSION = "
|
|
271
|
+
var SPEC_VERSION = "2.2.0";
|
|
272
|
+
var SDK_VERSION = "1.2.0";
|
|
236
273
|
function validateVersionCompat(incomingVersion, localVersion = SPEC_VERSION) {
|
|
237
274
|
const parseParts = (v) => {
|
|
238
275
|
const parts = v.split(".");
|
|
@@ -283,9 +320,14 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
|
283
320
|
MessageType2[MessageType2["CONTRIBUTE_RESULT"] = 34] = "CONTRIBUTE_RESULT";
|
|
284
321
|
MessageType2[MessageType2["CONTRIBUTE_CANCEL"] = 35] = "CONTRIBUTE_CANCEL";
|
|
285
322
|
MessageType2[MessageType2["TRAINING_DATA"] = 36] = "TRAINING_DATA";
|
|
286
|
-
MessageType2[MessageType2["
|
|
287
|
-
MessageType2[MessageType2["
|
|
288
|
-
MessageType2[MessageType2["
|
|
323
|
+
MessageType2[MessageType2["COMPETITION_ENTER"] = 37] = "COMPETITION_ENTER";
|
|
324
|
+
MessageType2[MessageType2["COMPETITION_SCORE"] = 38] = "COMPETITION_SCORE";
|
|
325
|
+
MessageType2[MessageType2["SEASON_STANDING"] = 39] = "SEASON_STANDING";
|
|
326
|
+
MessageType2[MessageType2["PERSONAL_RESEARCH_RESULT"] = 40] = "PERSONAL_RESEARCH_RESULT";
|
|
327
|
+
MessageType2[MessageType2["AUTHORITY_ACCESS"] = 41] = "AUTHORITY_ACCESS";
|
|
328
|
+
MessageType2[MessageType2["AUTHORITY_RESPONSE"] = 42] = "AUTHORITY_RESPONSE";
|
|
329
|
+
MessageType2[MessageType2["FIRMWARE_ATTESTATION"] = 43] = "FIRMWARE_ATTESTATION";
|
|
330
|
+
MessageType2[MessageType2["SBOM_UPDATE"] = 44] = "SBOM_UPDATE";
|
|
289
331
|
return MessageType2;
|
|
290
332
|
})(MessageType || {});
|
|
291
333
|
var RCANMessageError = class extends Error {
|
|
@@ -321,6 +363,12 @@ var RCANMessage = class _RCANMessage {
|
|
|
321
363
|
transportEncoding;
|
|
322
364
|
/** v1.6: GAP-18 multi-modal media chunks */
|
|
323
365
|
mediaChunks;
|
|
366
|
+
/** v2.1: SHA-256 of sender's firmware manifest */
|
|
367
|
+
firmwareHash;
|
|
368
|
+
/** v2.1: URI to sender's SBOM attestation endpoint */
|
|
369
|
+
attestationRef;
|
|
370
|
+
/** v2.2: ML-DSA-65 post-quantum signature (field 16, FIPS 204). Hybrid alongside Ed25519. */
|
|
371
|
+
pqSig;
|
|
324
372
|
constructor(data) {
|
|
325
373
|
if (!data.cmd || data.cmd.trim() === "") {
|
|
326
374
|
throw new RCANMessageError("'cmd' is required");
|
|
@@ -349,6 +397,14 @@ var RCANMessage = class _RCANMessage {
|
|
|
349
397
|
this.loa = data.loa;
|
|
350
398
|
this.transportEncoding = data.transportEncoding;
|
|
351
399
|
this.mediaChunks = data.mediaChunks;
|
|
400
|
+
this.firmwareHash = data.firmwareHash;
|
|
401
|
+
this.attestationRef = data.attestationRef;
|
|
402
|
+
this.pqSig = data.pqSig;
|
|
403
|
+
if (this.signature !== void 0 && this.signature["sig"] === "pending") {
|
|
404
|
+
throw new RCANMessageError(
|
|
405
|
+
"signature.sig:'pending' is not valid in RCAN v2.1. Sign the message before sending."
|
|
406
|
+
);
|
|
407
|
+
}
|
|
352
408
|
if (this.confidence !== void 0) {
|
|
353
409
|
if (this.confidence < 0 || this.confidence > 1) {
|
|
354
410
|
throw new RCANMessageError(
|
|
@@ -390,6 +446,9 @@ var RCANMessage = class _RCANMessage {
|
|
|
390
446
|
if (this.loa !== void 0) obj.loa = this.loa;
|
|
391
447
|
if (this.transportEncoding !== void 0) obj.transportEncoding = this.transportEncoding;
|
|
392
448
|
if (this.mediaChunks !== void 0) obj.mediaChunks = this.mediaChunks;
|
|
449
|
+
if (this.firmwareHash !== void 0) obj.firmwareHash = this.firmwareHash;
|
|
450
|
+
if (this.attestationRef !== void 0) obj.attestationRef = this.attestationRef;
|
|
451
|
+
if (this.pqSig !== void 0) obj.pqSig = this.pqSig;
|
|
393
452
|
return obj;
|
|
394
453
|
}
|
|
395
454
|
/** Serialize to JSON string */
|
|
@@ -432,7 +491,10 @@ var RCANMessage = class _RCANMessage {
|
|
|
432
491
|
readOnly: obj.readOnly,
|
|
433
492
|
loa: obj.loa,
|
|
434
493
|
transportEncoding: obj.transportEncoding,
|
|
435
|
-
mediaChunks: obj.mediaChunks
|
|
494
|
+
mediaChunks: obj.mediaChunks,
|
|
495
|
+
firmwareHash: obj.firmwareHash,
|
|
496
|
+
attestationRef: obj.attestationRef,
|
|
497
|
+
pqSig: obj.pqSig
|
|
436
498
|
});
|
|
437
499
|
}
|
|
438
500
|
};
|
|
@@ -2186,80 +2248,133 @@ function makeFaultReport(params) {
|
|
|
2186
2248
|
}
|
|
2187
2249
|
|
|
2188
2250
|
// src/identity.ts
|
|
2189
|
-
var
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2251
|
+
var Role = /* @__PURE__ */ ((Role2) => {
|
|
2252
|
+
Role2[Role2["GUEST"] = 1] = "GUEST";
|
|
2253
|
+
Role2[Role2["OPERATOR"] = 2] = "OPERATOR";
|
|
2254
|
+
Role2[Role2["CONTRIBUTOR"] = 3] = "CONTRIBUTOR";
|
|
2255
|
+
Role2[Role2["ADMIN"] = 4] = "ADMIN";
|
|
2256
|
+
Role2[Role2["M2M_PEER"] = 5] = "M2M_PEER";
|
|
2257
|
+
Role2[Role2["CREATOR"] = 6] = "CREATOR";
|
|
2258
|
+
Role2[Role2["M2M_TRUSTED"] = 7] = "M2M_TRUSTED";
|
|
2259
|
+
return Role2;
|
|
2260
|
+
})(Role || {});
|
|
2261
|
+
var LevelOfAssurance = Role;
|
|
2262
|
+
var ROLE_JWT_LEVEL = {
|
|
2263
|
+
[1 /* GUEST */]: 1,
|
|
2264
|
+
[2 /* OPERATOR */]: 2,
|
|
2265
|
+
[3 /* CONTRIBUTOR */]: 2.5,
|
|
2266
|
+
[4 /* ADMIN */]: 3,
|
|
2267
|
+
[5 /* M2M_PEER */]: 4,
|
|
2268
|
+
[6 /* CREATOR */]: 5,
|
|
2269
|
+
[7 /* M2M_TRUSTED */]: 6
|
|
2270
|
+
};
|
|
2271
|
+
var JWT_LEVEL_TO_ROLE = new Map(
|
|
2272
|
+
Object.entries(ROLE_JWT_LEVEL).map(
|
|
2273
|
+
([role, level]) => [level, Number(role)]
|
|
2274
|
+
)
|
|
2275
|
+
);
|
|
2276
|
+
function roleFromJwtLevel(level) {
|
|
2277
|
+
return JWT_LEVEL_TO_ROLE.get(level);
|
|
2278
|
+
}
|
|
2279
|
+
var SCOPE_MIN_ROLE = {
|
|
2280
|
+
"status": 1 /* GUEST */,
|
|
2281
|
+
"discover": 1 /* GUEST */,
|
|
2282
|
+
"chat": 1 /* GUEST */,
|
|
2283
|
+
"observer": 1 /* GUEST */,
|
|
2284
|
+
"contribute": 3 /* CONTRIBUTOR */,
|
|
2285
|
+
"control": 2 /* OPERATOR */,
|
|
2286
|
+
"teleop": 2 /* OPERATOR */,
|
|
2287
|
+
"training": 4 /* ADMIN */,
|
|
2288
|
+
"training_data": 4 /* ADMIN */,
|
|
2289
|
+
"config": 4 /* ADMIN */,
|
|
2290
|
+
"authority": 4 /* ADMIN */,
|
|
2291
|
+
"admin": 6 /* CREATOR */,
|
|
2292
|
+
"safety": 6 /* CREATOR */,
|
|
2293
|
+
"estop": 6 /* CREATOR */,
|
|
2294
|
+
"fleet.trusted": 7 /* M2M_TRUSTED */
|
|
2295
|
+
};
|
|
2195
2296
|
var DEFAULT_LOA_POLICY = {
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2297
|
+
minRoleForDiscover: 1 /* GUEST */,
|
|
2298
|
+
minRoleForStatus: 1 /* GUEST */,
|
|
2299
|
+
minRoleForChat: 1 /* GUEST */,
|
|
2300
|
+
minRoleForControl: 1 /* GUEST */,
|
|
2301
|
+
minRoleForSafety: 1 /* GUEST */
|
|
2201
2302
|
};
|
|
2202
2303
|
var PRODUCTION_LOA_POLICY = {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2304
|
+
minRoleForDiscover: 1 /* GUEST */,
|
|
2305
|
+
minRoleForStatus: 1 /* GUEST */,
|
|
2306
|
+
minRoleForChat: 1 /* GUEST */,
|
|
2307
|
+
minRoleForControl: 2 /* OPERATOR */,
|
|
2308
|
+
minRoleForSafety: 6 /* CREATOR */
|
|
2208
2309
|
};
|
|
2209
|
-
function
|
|
2310
|
+
function decodeJwtPayload(token) {
|
|
2210
2311
|
try {
|
|
2211
2312
|
const parts = token.split(".");
|
|
2212
|
-
if (parts.length < 2) return
|
|
2313
|
+
if (parts.length < 2) return null;
|
|
2213
2314
|
const payloadB64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
2214
2315
|
const padded = payloadB64 + "=".repeat((4 - payloadB64.length % 4) % 4);
|
|
2215
|
-
|
|
2216
|
-
if (typeof atob !== "undefined") {
|
|
2217
|
-
json = atob(padded);
|
|
2218
|
-
} else {
|
|
2219
|
-
json = Buffer.from(padded, "base64").toString("utf-8");
|
|
2220
|
-
}
|
|
2221
|
-
const claims = JSON.parse(json);
|
|
2222
|
-
const loa = claims["loa"];
|
|
2223
|
-
if (typeof loa === "number" && loa >= 1 && loa <= 3) {
|
|
2224
|
-
return loa;
|
|
2225
|
-
}
|
|
2316
|
+
return JSON.parse(atob(padded));
|
|
2226
2317
|
} catch {
|
|
2318
|
+
return null;
|
|
2227
2319
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
return policy.minLoaStatus;
|
|
2237
|
-
case "chat":
|
|
2238
|
-
return policy.minLoaChat;
|
|
2239
|
-
case "contribute":
|
|
2240
|
-
return policy.minLoaChat;
|
|
2241
|
-
// v1.7: between chat and control
|
|
2242
|
-
case "control":
|
|
2243
|
-
return policy.minLoaControl;
|
|
2244
|
-
case "safety":
|
|
2245
|
-
return policy.minLoaSafety;
|
|
2246
|
-
default:
|
|
2247
|
-
return null;
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
function validateLoaForScope(loa, scope, policy = DEFAULT_LOA_POLICY) {
|
|
2251
|
-
const min = minLoaForScope(scope, policy);
|
|
2252
|
-
if (min === null) {
|
|
2253
|
-
return { valid: true, reason: "unknown scope; allowed by default" };
|
|
2254
|
-
}
|
|
2255
|
-
if (loa >= min) {
|
|
2256
|
-
return { valid: true, reason: "ok" };
|
|
2320
|
+
}
|
|
2321
|
+
function extractRoleFromJwt(token) {
|
|
2322
|
+
const payload = decodeJwtPayload(token);
|
|
2323
|
+
if (!payload) return 1 /* GUEST */;
|
|
2324
|
+
const rcanRole = payload["rcan_role"];
|
|
2325
|
+
if (rcanRole !== void 0 && rcanRole !== null) {
|
|
2326
|
+
const role = roleFromJwtLevel(Number(rcanRole));
|
|
2327
|
+
if (role !== void 0) return role;
|
|
2257
2328
|
}
|
|
2329
|
+
const loa = payload["loa"];
|
|
2330
|
+
if (loa !== void 0 && loa !== null) {
|
|
2331
|
+
const role = roleFromJwtLevel(Number(loa));
|
|
2332
|
+
if (role !== void 0) return role;
|
|
2333
|
+
}
|
|
2334
|
+
return 1 /* GUEST */;
|
|
2335
|
+
}
|
|
2336
|
+
function extractLoaFromJwt(token) {
|
|
2337
|
+
return extractRoleFromJwt(token);
|
|
2338
|
+
}
|
|
2339
|
+
function extractIdentityFromJwt(token) {
|
|
2340
|
+
const payload = decodeJwtPayload(token);
|
|
2341
|
+
if (!payload) {
|
|
2342
|
+
return { sub: "", role: 1 /* GUEST */, jwtLevel: 1, scopes: [] };
|
|
2343
|
+
}
|
|
2344
|
+
const rcanRole = payload["rcan_role"];
|
|
2345
|
+
const loa = payload["loa"];
|
|
2346
|
+
const rawLevel = rcanRole !== void 0 ? Number(rcanRole) : loa !== void 0 ? Number(loa) : 1;
|
|
2347
|
+
const role = roleFromJwtLevel(rawLevel) ?? 1 /* GUEST */;
|
|
2348
|
+
const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
|
|
2258
2349
|
return {
|
|
2259
|
-
|
|
2260
|
-
|
|
2350
|
+
sub: String(payload["sub"] ?? ""),
|
|
2351
|
+
role,
|
|
2352
|
+
jwtLevel: ROLE_JWT_LEVEL[role],
|
|
2353
|
+
registryUrl: payload["registry_url"],
|
|
2354
|
+
scopes,
|
|
2355
|
+
verifiedAt: payload["verified_at"],
|
|
2356
|
+
peerRrn: payload["peer_rrn"],
|
|
2357
|
+
fleetRrns: Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : void 0
|
|
2261
2358
|
};
|
|
2262
2359
|
}
|
|
2360
|
+
function validateRoleForScope(role, scope) {
|
|
2361
|
+
const required = SCOPE_MIN_ROLE[scope.toLowerCase()];
|
|
2362
|
+
if (required === void 0) {
|
|
2363
|
+
if (role >= 2 /* OPERATOR */) return { ok: true, reason: "" };
|
|
2364
|
+
return {
|
|
2365
|
+
ok: false,
|
|
2366
|
+
reason: `Unknown scope '${scope}': applying OPERATOR minimum. Caller has ${Role[role]}.`
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
if (role >= required) return { ok: true, reason: "" };
|
|
2370
|
+
return {
|
|
2371
|
+
ok: false,
|
|
2372
|
+
reason: `Scope '${scope}' requires ${Role[required]} (JWT level ${ROLE_JWT_LEVEL[required]}), but caller has ${Role[role]} (JWT level ${ROLE_JWT_LEVEL[role]})`
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
function validateLoaForScope(role, scope) {
|
|
2376
|
+
return validateRoleForScope(role, scope);
|
|
2377
|
+
}
|
|
2263
2378
|
|
|
2264
2379
|
// src/federation.ts
|
|
2265
2380
|
var RegistryTier = /* @__PURE__ */ ((RegistryTier2) => {
|
|
@@ -2383,12 +2498,12 @@ function generateId5() {
|
|
|
2383
2498
|
}
|
|
2384
2499
|
function makeFederationSync(source, target, syncType, payload) {
|
|
2385
2500
|
return new RCANMessage({
|
|
2386
|
-
rcan: "1.
|
|
2387
|
-
rcanVersion: "1.
|
|
2501
|
+
rcan: "2.1.0",
|
|
2502
|
+
rcanVersion: "2.1.0",
|
|
2388
2503
|
cmd: "federation_sync",
|
|
2389
2504
|
target,
|
|
2390
2505
|
params: {
|
|
2391
|
-
msg_type: 23 /*
|
|
2506
|
+
msg_type: 23 /* FLEET_COMMAND */,
|
|
2392
2507
|
msg_id: generateId5(),
|
|
2393
2508
|
source_registry: source,
|
|
2394
2509
|
target_registry: target,
|
|
@@ -2415,17 +2530,17 @@ async function validateCrossRegistryCommand(msg, localRegistry, trustCache) {
|
|
|
2415
2530
|
reason: `REGISTRY_UNKNOWN: ${sourceRegistry} is not in the local trust cache`
|
|
2416
2531
|
};
|
|
2417
2532
|
}
|
|
2418
|
-
let loa = 1 /*
|
|
2533
|
+
let loa = 1 /* GUEST */;
|
|
2419
2534
|
const registryJwt = msg.params?.["registry_jwt"];
|
|
2420
2535
|
if (registryJwt) {
|
|
2421
2536
|
loa = extractLoaFromJwt(registryJwt);
|
|
2422
2537
|
} else if (typeof msg.loa === "number") {
|
|
2423
2538
|
loa = msg.loa;
|
|
2424
2539
|
}
|
|
2425
|
-
if (loa < 2 /*
|
|
2540
|
+
if (loa < 2 /* OPERATOR */) {
|
|
2426
2541
|
return {
|
|
2427
2542
|
valid: false,
|
|
2428
|
-
reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (
|
|
2543
|
+
reason: `LOA_INSUFFICIENT: cross-registry commands require LoA>=2 (OPERATOR), got role=${loa}`
|
|
2429
2544
|
};
|
|
2430
2545
|
}
|
|
2431
2546
|
return { valid: true, reason: "cross-registry command accepted" };
|
|
@@ -2864,25 +2979,471 @@ function isPreemptedBy(scopeLevel) {
|
|
|
2864
2979
|
return scopeLevel >= 3;
|
|
2865
2980
|
}
|
|
2866
2981
|
|
|
2982
|
+
// src/competition.ts
|
|
2983
|
+
var COMPETITION_SCOPE_LEVEL = 2;
|
|
2984
|
+
var _idCounter2 = 0;
|
|
2985
|
+
function _generateRunId() {
|
|
2986
|
+
return `run-${Date.now()}-${++_idCounter2}`;
|
|
2987
|
+
}
|
|
2988
|
+
function makeCompetitionEnter(params = {}) {
|
|
2989
|
+
return {
|
|
2990
|
+
type: 37 /* COMPETITION_ENTER */,
|
|
2991
|
+
competition_id: params.competition_id ?? "",
|
|
2992
|
+
competition_format: params.competition_format ?? "sprint",
|
|
2993
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
2994
|
+
model_id: params.model_id ?? "",
|
|
2995
|
+
robot_rrn: params.robot_rrn ?? "",
|
|
2996
|
+
entered_at: params.entered_at ?? Date.now() / 1e3
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function makeCompetitionScore(params = {}) {
|
|
3000
|
+
const score = params.score ?? 0;
|
|
3001
|
+
if (score < 0 || score > 1) {
|
|
3002
|
+
throw new Error(`score must be in [0.0, 1.0], got ${score}`);
|
|
3003
|
+
}
|
|
3004
|
+
return {
|
|
3005
|
+
type: 38 /* COMPETITION_SCORE */,
|
|
3006
|
+
competition_id: params.competition_id ?? "",
|
|
3007
|
+
candidate_id: params.candidate_id ?? "",
|
|
3008
|
+
score,
|
|
3009
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
3010
|
+
verified: params.verified ?? false,
|
|
3011
|
+
submitted_at: params.submitted_at ?? Date.now() / 1e3
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
function makeSeasonStanding(params = {}) {
|
|
3015
|
+
return {
|
|
3016
|
+
type: 39 /* SEASON_STANDING */,
|
|
3017
|
+
season_id: params.season_id ?? "",
|
|
3018
|
+
class_id: params.class_id ?? "",
|
|
3019
|
+
standings: params.standings ?? [],
|
|
3020
|
+
days_remaining: params.days_remaining ?? 0,
|
|
3021
|
+
broadcast_at: params.broadcast_at ?? Date.now() / 1e3
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
function makePersonalResearchResult(params = {}) {
|
|
3025
|
+
const score = params.score ?? 0;
|
|
3026
|
+
if (score < 0 || score > 1) {
|
|
3027
|
+
throw new Error(`score must be in [0.0, 1.0], got ${score}`);
|
|
3028
|
+
}
|
|
3029
|
+
return {
|
|
3030
|
+
type: 40 /* PERSONAL_RESEARCH_RESULT */,
|
|
3031
|
+
run_id: params.run_id ?? _generateRunId(),
|
|
3032
|
+
run_type: params.run_type ?? "personal",
|
|
3033
|
+
candidate_id: params.candidate_id ?? "",
|
|
3034
|
+
score,
|
|
3035
|
+
hardware_tier: params.hardware_tier ?? "",
|
|
3036
|
+
model_id: params.model_id ?? "",
|
|
3037
|
+
owner_uid: params.owner_uid ?? "",
|
|
3038
|
+
metrics: params.metrics ?? {
|
|
3039
|
+
success_rate: 0,
|
|
3040
|
+
p66_rate: 0,
|
|
3041
|
+
token_efficiency: 0,
|
|
3042
|
+
latency_score: 0
|
|
3043
|
+
},
|
|
3044
|
+
submitted_to_community: params.submitted_to_community ?? false,
|
|
3045
|
+
created_at: params.created_at ?? Date.now() / 1e3
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
function validateCompetitionScope(scopeLevel) {
|
|
3049
|
+
return scopeLevel >= COMPETITION_SCOPE_LEVEL;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
// src/firmware.ts
|
|
3053
|
+
var FIRMWARE_MANIFEST_PATH = "/.well-known/rcan-firmware-manifest.json";
|
|
3054
|
+
function manifestToWire(m) {
|
|
3055
|
+
const wire = {
|
|
3056
|
+
rrn: m.rrn,
|
|
3057
|
+
firmware_version: m.firmwareVersion,
|
|
3058
|
+
build_hash: m.buildHash,
|
|
3059
|
+
components: m.components,
|
|
3060
|
+
signed_at: m.signedAt
|
|
3061
|
+
};
|
|
3062
|
+
if (m.signature) wire.signature = m.signature;
|
|
3063
|
+
return wire;
|
|
3064
|
+
}
|
|
3065
|
+
function manifestFromWire(w) {
|
|
3066
|
+
return {
|
|
3067
|
+
rrn: w.rrn,
|
|
3068
|
+
firmwareVersion: w.firmware_version,
|
|
3069
|
+
buildHash: w.build_hash,
|
|
3070
|
+
components: w.components ?? [],
|
|
3071
|
+
signedAt: w.signed_at ?? "",
|
|
3072
|
+
signature: w.signature
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
function canonicalManifestJson(m) {
|
|
3076
|
+
const obj = {
|
|
3077
|
+
build_hash: m.buildHash,
|
|
3078
|
+
components: m.components.map((c) => ({
|
|
3079
|
+
hash: c.hash,
|
|
3080
|
+
name: c.name,
|
|
3081
|
+
version: c.version
|
|
3082
|
+
})),
|
|
3083
|
+
firmware_version: m.firmwareVersion,
|
|
3084
|
+
rrn: m.rrn,
|
|
3085
|
+
signed_at: m.signedAt
|
|
3086
|
+
};
|
|
3087
|
+
return JSON.stringify(obj);
|
|
3088
|
+
}
|
|
3089
|
+
var FirmwareIntegrityError = class extends Error {
|
|
3090
|
+
constructor(message) {
|
|
3091
|
+
super(message);
|
|
3092
|
+
this.name = "FirmwareIntegrityError";
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
function validateManifest(m) {
|
|
3096
|
+
const errors = [];
|
|
3097
|
+
if (!m.rrn) errors.push("rrn is required");
|
|
3098
|
+
if (!m.firmwareVersion) errors.push("firmwareVersion is required");
|
|
3099
|
+
if (!m.buildHash) errors.push("buildHash is required");
|
|
3100
|
+
if (!m.buildHash.startsWith("sha256:")) errors.push("buildHash must start with 'sha256:'");
|
|
3101
|
+
if (!m.signedAt) errors.push("signedAt is required");
|
|
3102
|
+
if (!m.signature) errors.push("signature is required (manifest must be signed)");
|
|
3103
|
+
for (const [i, c] of m.components.entries()) {
|
|
3104
|
+
if (!c.name) errors.push(`components[${i}].name is required`);
|
|
3105
|
+
if (!c.version) errors.push(`components[${i}].version is required`);
|
|
3106
|
+
if (!c.hash.startsWith("sha256:")) errors.push(`components[${i}].hash must start with 'sha256:'`);
|
|
3107
|
+
}
|
|
3108
|
+
return errors;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/authority.ts
|
|
3112
|
+
function authorityAccessToWire(p) {
|
|
3113
|
+
return {
|
|
3114
|
+
request_id: p.requestId,
|
|
3115
|
+
authority_id: p.authorityId,
|
|
3116
|
+
requested_data: p.requestedData,
|
|
3117
|
+
justification: p.justification,
|
|
3118
|
+
expires_at: p.expiresAt
|
|
3119
|
+
};
|
|
3120
|
+
}
|
|
3121
|
+
function authorityAccessFromWire(w) {
|
|
3122
|
+
return {
|
|
3123
|
+
requestId: w.request_id,
|
|
3124
|
+
authorityId: w.authority_id,
|
|
3125
|
+
requestedData: w.requested_data ?? [],
|
|
3126
|
+
justification: w.justification ?? "",
|
|
3127
|
+
expiresAt: w.expires_at ?? 0
|
|
3128
|
+
};
|
|
3129
|
+
}
|
|
3130
|
+
function validateAuthorityAccess(p) {
|
|
3131
|
+
const errors = [];
|
|
3132
|
+
if (!p.requestId) errors.push("requestId is required");
|
|
3133
|
+
if (!p.authorityId) errors.push("authorityId is required");
|
|
3134
|
+
if (!p.requestedData || p.requestedData.length === 0)
|
|
3135
|
+
errors.push("requestedData must include at least one category");
|
|
3136
|
+
if (!p.justification) errors.push("justification is required");
|
|
3137
|
+
if (!p.expiresAt || p.expiresAt <= 0) errors.push("expiresAt must be a positive Unix timestamp");
|
|
3138
|
+
if (p.expiresAt < Date.now() / 1e3) errors.push("expiresAt is in the past \u2014 request has expired");
|
|
3139
|
+
return errors;
|
|
3140
|
+
}
|
|
3141
|
+
function isAuthorityRequestValid(p) {
|
|
3142
|
+
return Date.now() / 1e3 < p.expiresAt && validateAuthorityAccess(p).length === 0;
|
|
3143
|
+
}
|
|
3144
|
+
var AUTHORITY_ERROR_CODES = {
|
|
3145
|
+
NOT_RECOGNIZED: "AUTHORITY_NOT_RECOGNIZED",
|
|
3146
|
+
REQUEST_EXPIRED: "AUTHORITY_REQUEST_EXPIRED",
|
|
3147
|
+
INVALID_TOKEN: "AUTHORITY_INVALID_TOKEN",
|
|
3148
|
+
RATE_LIMITED: "AUTHORITY_RATE_LIMITED"
|
|
3149
|
+
};
|
|
3150
|
+
|
|
3151
|
+
// src/m2m.ts
|
|
3152
|
+
var RRF_REVOCATION_URL = "https://api.rrf.rcan.dev/v2/revocations";
|
|
3153
|
+
var M2M_TRUSTED_ISSUER = "rrf.rcan.dev";
|
|
3154
|
+
var RRF_REVOCATION_CACHE_TTL_MS = 55e3;
|
|
3155
|
+
var M2MAuthError = class extends Error {
|
|
3156
|
+
constructor(message) {
|
|
3157
|
+
super(message);
|
|
3158
|
+
this.name = "M2MAuthError";
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
function decodeJwtPayload2(token) {
|
|
3162
|
+
const parts = token.split(".");
|
|
3163
|
+
if (parts.length < 2) throw new M2MAuthError("Invalid JWT structure");
|
|
3164
|
+
const b64 = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
3165
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
3166
|
+
try {
|
|
3167
|
+
return JSON.parse(atob(padded));
|
|
3168
|
+
} catch (e) {
|
|
3169
|
+
throw new M2MAuthError(`JWT payload decode failed: ${String(e)}`);
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function parseM2mPeerToken(token) {
|
|
3173
|
+
const payload = decodeJwtPayload2(token);
|
|
3174
|
+
const exp = Number(payload["exp"] ?? 0);
|
|
3175
|
+
if (exp > 0 && Date.now() / 1e3 > exp) {
|
|
3176
|
+
throw new M2MAuthError(`M2M_PEER token expired (sub=${String(payload["sub"])})`);
|
|
3177
|
+
}
|
|
3178
|
+
const peerRrn = String(payload["peer_rrn"] ?? "");
|
|
3179
|
+
if (!peerRrn) throw new M2MAuthError("M2M_PEER token missing peer_rrn claim");
|
|
3180
|
+
return {
|
|
3181
|
+
sub: String(payload["sub"] ?? ""),
|
|
3182
|
+
peerRrn,
|
|
3183
|
+
scopes: Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [],
|
|
3184
|
+
exp,
|
|
3185
|
+
iss: String(payload["iss"] ?? "")
|
|
3186
|
+
};
|
|
3187
|
+
}
|
|
3188
|
+
function parseM2mTrustedToken(token) {
|
|
3189
|
+
const payload = decodeJwtPayload2(token);
|
|
3190
|
+
const iss = String(payload["iss"] ?? "");
|
|
3191
|
+
if (iss !== M2M_TRUSTED_ISSUER) {
|
|
3192
|
+
throw new M2MAuthError(
|
|
3193
|
+
`M2M_TRUSTED issuer must be '${M2M_TRUSTED_ISSUER}', got '${iss}'`
|
|
3194
|
+
);
|
|
3195
|
+
}
|
|
3196
|
+
const scopes = Array.isArray(payload["rcan_scopes"]) ? payload["rcan_scopes"] : Array.isArray(payload["scopes"]) ? payload["scopes"] : [];
|
|
3197
|
+
if (!scopes.includes("fleet.trusted")) {
|
|
3198
|
+
throw new M2MAuthError("M2M_TRUSTED token missing required 'fleet.trusted' scope");
|
|
3199
|
+
}
|
|
3200
|
+
const exp = Number(payload["exp"] ?? 0);
|
|
3201
|
+
if (exp > 0 && Date.now() / 1e3 > exp) {
|
|
3202
|
+
throw new M2MAuthError(`M2M_TRUSTED token expired (sub=${String(payload["sub"])})`);
|
|
3203
|
+
}
|
|
3204
|
+
const rrfSig = String(payload["rrf_sig"] ?? "");
|
|
3205
|
+
if (!rrfSig) throw new M2MAuthError("M2M_TRUSTED token missing rrf_sig claim");
|
|
3206
|
+
const fleetRrns = Array.isArray(payload["fleet_rrns"]) ? payload["fleet_rrns"] : [];
|
|
3207
|
+
return {
|
|
3208
|
+
sub: String(payload["sub"] ?? ""),
|
|
3209
|
+
fleetRrns,
|
|
3210
|
+
scopes,
|
|
3211
|
+
exp,
|
|
3212
|
+
iss,
|
|
3213
|
+
rrfSig
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
function verifyM2mTrustedTokenClaims(token, targetRrn) {
|
|
3217
|
+
const claims = parseM2mTrustedToken(token);
|
|
3218
|
+
if (!claims.fleetRrns.includes(targetRrn)) {
|
|
3219
|
+
throw new M2MAuthError(
|
|
3220
|
+
`M2M_TRUSTED token does not authorize commanding '${targetRrn}'. Authorized fleet: [${claims.fleetRrns.join(", ")}]`
|
|
3221
|
+
);
|
|
3222
|
+
}
|
|
3223
|
+
return claims;
|
|
3224
|
+
}
|
|
3225
|
+
var _revocationCache = null;
|
|
3226
|
+
async function fetchRRFRevocations(url = RRF_REVOCATION_URL) {
|
|
3227
|
+
const now = Date.now();
|
|
3228
|
+
if (_revocationCache && now - _revocationCache.fetchedAt < RRF_REVOCATION_CACHE_TTL_MS) {
|
|
3229
|
+
return _revocationCache;
|
|
3230
|
+
}
|
|
3231
|
+
try {
|
|
3232
|
+
const resp = await fetch(url, { signal: AbortSignal.timeout?.(5e3) });
|
|
3233
|
+
const data = await resp.json();
|
|
3234
|
+
_revocationCache = {
|
|
3235
|
+
revokedOrchestrators: new Set(data.revoked_orchestrators ?? []),
|
|
3236
|
+
revokedJtis: new Set(data.revoked_jtis ?? []),
|
|
3237
|
+
fetchedAt: now
|
|
3238
|
+
};
|
|
3239
|
+
} catch {
|
|
3240
|
+
if (_revocationCache) return _revocationCache;
|
|
3241
|
+
_revocationCache = { revokedOrchestrators: /* @__PURE__ */ new Set(), revokedJtis: /* @__PURE__ */ new Set(), fetchedAt: now };
|
|
3242
|
+
}
|
|
3243
|
+
return _revocationCache;
|
|
3244
|
+
}
|
|
3245
|
+
async function isM2mTrustedRevoked(claims, jti) {
|
|
3246
|
+
const cache = await fetchRRFRevocations();
|
|
3247
|
+
if (cache.revokedOrchestrators.has(claims.sub)) return true;
|
|
3248
|
+
if (jti && cache.revokedJtis.has(jti)) return true;
|
|
3249
|
+
return false;
|
|
3250
|
+
}
|
|
3251
|
+
async function verifyM2mTrustedToken(token, targetRrn, options) {
|
|
3252
|
+
const claims = verifyM2mTrustedTokenClaims(token, targetRrn);
|
|
3253
|
+
if (!options?.skipRevocationCheck) {
|
|
3254
|
+
const revoked = await isM2mTrustedRevoked(claims);
|
|
3255
|
+
if (revoked) {
|
|
3256
|
+
throw new M2MAuthError(
|
|
3257
|
+
`M2M_TRUSTED orchestrator '${claims.sub}' is on the RRF revocation list`
|
|
3258
|
+
);
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
return claims;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
// src/pqSigning.ts
|
|
3265
|
+
function toBase64url(bytes) {
|
|
3266
|
+
let binary = "";
|
|
3267
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
3268
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3269
|
+
}
|
|
3270
|
+
function fromBase64url(b64) {
|
|
3271
|
+
const padded = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
3272
|
+
const binary = atob(padded);
|
|
3273
|
+
const bytes = new Uint8Array(binary.length);
|
|
3274
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
3275
|
+
return bytes;
|
|
3276
|
+
}
|
|
3277
|
+
async function sha256hex(data) {
|
|
3278
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
3279
|
+
const webcrypto = g.crypto;
|
|
3280
|
+
const subtle = webcrypto?.subtle;
|
|
3281
|
+
if (subtle) {
|
|
3282
|
+
const buf = await subtle.digest("SHA-256", data);
|
|
3283
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
|
|
3284
|
+
}
|
|
3285
|
+
const nodeCrypto = await import("crypto").catch(() => null);
|
|
3286
|
+
if (nodeCrypto) {
|
|
3287
|
+
return nodeCrypto.createHash("sha256").update(data).digest("hex").slice(0, 8);
|
|
3288
|
+
}
|
|
3289
|
+
throw new Error("No SHA-256 implementation available");
|
|
3290
|
+
}
|
|
3291
|
+
var _mlDsaModule;
|
|
3292
|
+
async function requireNoblePostQuantum() {
|
|
3293
|
+
if (_mlDsaModule) return _mlDsaModule;
|
|
3294
|
+
if (typeof require !== "undefined") {
|
|
3295
|
+
try {
|
|
3296
|
+
_mlDsaModule = require("@noble/post-quantum/ml-dsa.js");
|
|
3297
|
+
return _mlDsaModule;
|
|
3298
|
+
} catch {
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
try {
|
|
3302
|
+
_mlDsaModule = await import("@noble/post-quantum/ml-dsa.js");
|
|
3303
|
+
return _mlDsaModule;
|
|
3304
|
+
} catch {
|
|
3305
|
+
throw new Error(
|
|
3306
|
+
"ML-DSA signing requires @noble/post-quantum. Install with: npm install @noble/post-quantum"
|
|
3307
|
+
);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
var MLDSAKeyPair = class _MLDSAKeyPair {
|
|
3311
|
+
keyId;
|
|
3312
|
+
publicKey;
|
|
3313
|
+
secretKey;
|
|
3314
|
+
constructor(data) {
|
|
3315
|
+
this.keyId = data.keyId;
|
|
3316
|
+
this.publicKey = data.publicKey;
|
|
3317
|
+
this.secretKey = data.secretKey;
|
|
3318
|
+
}
|
|
3319
|
+
/** Generate a new ML-DSA-65 key pair. */
|
|
3320
|
+
static async generate() {
|
|
3321
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3322
|
+
const kp = ml_dsa65.keygen();
|
|
3323
|
+
const keyId = await sha256hex(kp.publicKey);
|
|
3324
|
+
return new _MLDSAKeyPair({
|
|
3325
|
+
publicKey: kp.publicKey,
|
|
3326
|
+
secretKey: kp.secretKey,
|
|
3327
|
+
keyId
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
/** Build a verify-only key pair from raw public key bytes. */
|
|
3331
|
+
static async fromPublicKey(publicKey) {
|
|
3332
|
+
const keyId = await sha256hex(publicKey);
|
|
3333
|
+
return new _MLDSAKeyPair({ publicKey, keyId });
|
|
3334
|
+
}
|
|
3335
|
+
/** Build a full key pair from saved bytes (public + secret). */
|
|
3336
|
+
static async fromKeyMaterial(publicKey, secretKey) {
|
|
3337
|
+
const keyId = await sha256hex(publicKey);
|
|
3338
|
+
return new _MLDSAKeyPair({ publicKey, secretKey, keyId });
|
|
3339
|
+
}
|
|
3340
|
+
get hasPrivateKey() {
|
|
3341
|
+
return this.secretKey !== void 0;
|
|
3342
|
+
}
|
|
3343
|
+
/** Sign raw bytes; returns ML-DSA-65 signature (3309 bytes). */
|
|
3344
|
+
async signBytes(data) {
|
|
3345
|
+
if (!this.secretKey) {
|
|
3346
|
+
throw new Error("Cannot sign: MLDSAKeyPair has no private key (verify-only)");
|
|
3347
|
+
}
|
|
3348
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3349
|
+
return ml_dsa65.sign(data, this.secretKey);
|
|
3350
|
+
}
|
|
3351
|
+
/**
|
|
3352
|
+
* Verify an ML-DSA-65 signature.
|
|
3353
|
+
* @returns true if valid
|
|
3354
|
+
* @throws {Error} on invalid signature
|
|
3355
|
+
*/
|
|
3356
|
+
async verifyBytes(data, signature) {
|
|
3357
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3358
|
+
const ok = ml_dsa65.verify(signature, data, this.publicKey);
|
|
3359
|
+
if (!ok) throw new Error("ML-DSA signature verification failed");
|
|
3360
|
+
}
|
|
3361
|
+
toString() {
|
|
3362
|
+
const mode = this.hasPrivateKey ? "private+public" : "public-only";
|
|
3363
|
+
return `MLDSAKeyPair(keyId=${this.keyId}, alg=ML-DSA-65, ${mode})`;
|
|
3364
|
+
}
|
|
3365
|
+
};
|
|
3366
|
+
function canonicalMessageBytes(msg) {
|
|
3367
|
+
const m = msg;
|
|
3368
|
+
const payload = {
|
|
3369
|
+
rcan: msg.rcan,
|
|
3370
|
+
msg_id: m["msgId"] ?? m["msg_id"] ?? "",
|
|
3371
|
+
timestamp: msg.timestamp,
|
|
3372
|
+
cmd: msg.cmd,
|
|
3373
|
+
target: msg.target,
|
|
3374
|
+
params: msg.params
|
|
3375
|
+
};
|
|
3376
|
+
const sorted = JSON.stringify(
|
|
3377
|
+
Object.fromEntries(Object.entries(payload).sort()),
|
|
3378
|
+
null,
|
|
3379
|
+
void 0
|
|
3380
|
+
);
|
|
3381
|
+
return new TextEncoder().encode(sorted);
|
|
3382
|
+
}
|
|
3383
|
+
async function addPQSignature(msg, keypair) {
|
|
3384
|
+
const payload = canonicalMessageBytes(msg);
|
|
3385
|
+
const rawSig = await keypair.signBytes(payload);
|
|
3386
|
+
const block = {
|
|
3387
|
+
alg: "ml-dsa-65",
|
|
3388
|
+
kid: keypair.keyId,
|
|
3389
|
+
sig: toBase64url(rawSig)
|
|
3390
|
+
};
|
|
3391
|
+
msg["pqSig"] = block;
|
|
3392
|
+
return msg;
|
|
3393
|
+
}
|
|
3394
|
+
async function verifyPQSignature(msg, trustedKeys, requirePQ = false) {
|
|
3395
|
+
const pqSig = msg["pqSig"];
|
|
3396
|
+
if (!pqSig) {
|
|
3397
|
+
if (requirePQ) {
|
|
3398
|
+
throw new Error("ML-DSA signature (pqSig) required but missing from message");
|
|
3399
|
+
}
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
if (pqSig.alg !== "ml-dsa-65") {
|
|
3403
|
+
throw new Error(`Unsupported PQ signature algorithm: ${pqSig.alg}`);
|
|
3404
|
+
}
|
|
3405
|
+
const matched = trustedKeys.find((k) => k.keyId === pqSig.kid);
|
|
3406
|
+
if (!matched) {
|
|
3407
|
+
throw new Error(
|
|
3408
|
+
`No trusted ML-DSA key with kid=${pqSig.kid}. Known kids: [${trustedKeys.map((k) => k.keyId).join(", ")}]`
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
let rawSig;
|
|
3412
|
+
try {
|
|
3413
|
+
rawSig = fromBase64url(pqSig.sig);
|
|
3414
|
+
} catch (e) {
|
|
3415
|
+
throw new Error(`Invalid base64url ML-DSA signature: ${e}`);
|
|
3416
|
+
}
|
|
3417
|
+
const payload = canonicalMessageBytes(msg);
|
|
3418
|
+
await matched.verifyBytes(payload, rawSig);
|
|
3419
|
+
}
|
|
3420
|
+
|
|
2867
3421
|
// src/index.ts
|
|
2868
3422
|
var VERSION = "0.6.0";
|
|
2869
3423
|
var RCAN_VERSION = "1.6";
|
|
2870
3424
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2871
3425
|
0 && (module.exports = {
|
|
3426
|
+
AUTHORITY_ERROR_CODES,
|
|
2872
3427
|
AuditChain,
|
|
2873
3428
|
AuditError,
|
|
3429
|
+
COMPETITION_SCOPE_LEVEL,
|
|
2874
3430
|
CONTRIBUTE_SCOPE_LEVEL,
|
|
2875
3431
|
ClockDriftError,
|
|
2876
3432
|
CommitmentRecord,
|
|
2877
3433
|
ConfidenceGate,
|
|
2878
3434
|
DEFAULT_LOA_POLICY,
|
|
2879
3435
|
DataCategory,
|
|
3436
|
+
FIRMWARE_MANIFEST_PATH,
|
|
2880
3437
|
FaultCode,
|
|
2881
3438
|
FederationSyncType,
|
|
3439
|
+
FirmwareIntegrityError,
|
|
2882
3440
|
GateError,
|
|
2883
3441
|
HiTLGate,
|
|
2884
3442
|
KeyStore,
|
|
2885
3443
|
LevelOfAssurance,
|
|
3444
|
+
M2MAuthError,
|
|
3445
|
+
M2M_TRUSTED_ISSUER,
|
|
3446
|
+
MLDSAKeyPair,
|
|
2886
3447
|
MediaEncoding,
|
|
2887
3448
|
MessageType,
|
|
2888
3449
|
NodeClient,
|
|
@@ -2908,13 +3469,18 @@ var RCAN_VERSION = "1.6";
|
|
|
2908
3469
|
RCANValidationError,
|
|
2909
3470
|
RCANVersionIncompatibleError,
|
|
2910
3471
|
RCAN_VERSION,
|
|
3472
|
+
ROLE_JWT_LEVEL,
|
|
3473
|
+
RRF_REVOCATION_CACHE_TTL_MS,
|
|
3474
|
+
RRF_REVOCATION_URL,
|
|
2911
3475
|
RegistryClient,
|
|
2912
3476
|
RegistryTier,
|
|
2913
3477
|
ReplayCache,
|
|
2914
3478
|
RevocationCache,
|
|
2915
3479
|
RobotURI,
|
|
2916
3480
|
RobotURIError,
|
|
3481
|
+
Role,
|
|
2917
3482
|
SAFETY_MESSAGE_TYPE,
|
|
3483
|
+
SCOPE_MIN_ROLE,
|
|
2918
3484
|
SDK_VERSION,
|
|
2919
3485
|
SPEC_VERSION,
|
|
2920
3486
|
TransportEncoding,
|
|
@@ -2924,7 +3490,11 @@ var RCAN_VERSION = "1.6";
|
|
|
2924
3490
|
addDelegationHop,
|
|
2925
3491
|
addMediaInline,
|
|
2926
3492
|
addMediaRef,
|
|
3493
|
+
addPQSignature,
|
|
2927
3494
|
assertClockSynced,
|
|
3495
|
+
authorityAccessFromWire,
|
|
3496
|
+
authorityAccessToWire,
|
|
3497
|
+
canonicalManifestJson,
|
|
2928
3498
|
checkClockSync,
|
|
2929
3499
|
checkRevocation,
|
|
2930
3500
|
decodeBleFrames,
|
|
@@ -2933,11 +3503,18 @@ var RCAN_VERSION = "1.6";
|
|
|
2933
3503
|
encodeBleFrames,
|
|
2934
3504
|
encodeCompact,
|
|
2935
3505
|
encodeMinimal,
|
|
3506
|
+
extractIdentityFromJwt,
|
|
2936
3507
|
extractLoaFromJwt,
|
|
3508
|
+
extractRoleFromJwt,
|
|
2937
3509
|
fetchCanonicalSchema,
|
|
3510
|
+
fetchRRFRevocations,
|
|
3511
|
+
isAuthorityRequestValid,
|
|
3512
|
+
isM2mTrustedRevoked,
|
|
2938
3513
|
isPreemptedBy,
|
|
2939
3514
|
isSafetyMessage,
|
|
2940
3515
|
makeCloudRelayMessage,
|
|
3516
|
+
makeCompetitionEnter,
|
|
3517
|
+
makeCompetitionScore,
|
|
2941
3518
|
makeConfigUpdate,
|
|
2942
3519
|
makeConsentDeny,
|
|
2943
3520
|
makeConsentGrant,
|
|
@@ -2950,8 +3527,10 @@ var RCAN_VERSION = "1.6";
|
|
|
2950
3527
|
makeFaultReport,
|
|
2951
3528
|
makeFederationSync,
|
|
2952
3529
|
makeKeyRotationMessage,
|
|
3530
|
+
makePersonalResearchResult,
|
|
2953
3531
|
makeResumeMessage,
|
|
2954
3532
|
makeRevocationBroadcast,
|
|
3533
|
+
makeSeasonStanding,
|
|
2955
3534
|
makeStopMessage,
|
|
2956
3535
|
makeStreamChunk,
|
|
2957
3536
|
makeTrainingConsentDeny,
|
|
@@ -2959,7 +3538,14 @@ var RCAN_VERSION = "1.6";
|
|
|
2959
3538
|
makeTrainingConsentRequest,
|
|
2960
3539
|
makeTrainingDataMessage,
|
|
2961
3540
|
makeTransparencyMessage,
|
|
3541
|
+
manifestFromWire,
|
|
3542
|
+
manifestToWire,
|
|
3543
|
+
parseM2mPeerToken,
|
|
3544
|
+
parseM2mTrustedToken,
|
|
3545
|
+
roleFromJwtLevel,
|
|
2962
3546
|
selectTransport,
|
|
3547
|
+
validateAuthorityAccess,
|
|
3548
|
+
validateCompetitionScope,
|
|
2963
3549
|
validateConfig,
|
|
2964
3550
|
validateConfigAgainstSchema,
|
|
2965
3551
|
validateConfigUpdate,
|
|
@@ -2968,13 +3554,18 @@ var RCAN_VERSION = "1.6";
|
|
|
2968
3554
|
validateCrossRegistryCommand,
|
|
2969
3555
|
validateDelegationChain,
|
|
2970
3556
|
validateLoaForScope,
|
|
3557
|
+
validateManifest,
|
|
2971
3558
|
validateMediaChunks,
|
|
2972
3559
|
validateMessage,
|
|
2973
3560
|
validateNodeAgainstSchema,
|
|
2974
3561
|
validateReplay,
|
|
3562
|
+
validateRoleForScope,
|
|
2975
3563
|
validateSafetyMessage,
|
|
2976
3564
|
validateTrainingDataMessage,
|
|
2977
3565
|
validateURI,
|
|
2978
|
-
validateVersionCompat
|
|
3566
|
+
validateVersionCompat,
|
|
3567
|
+
verifyM2mTrustedToken,
|
|
3568
|
+
verifyM2mTrustedTokenClaims,
|
|
3569
|
+
verifyPQSignature
|
|
2979
3570
|
});
|
|
2980
3571
|
//# sourceMappingURL=index.js.map
|