@continuonai/rcan-ts 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.d.mts +66 -4
- package/dist/browser.mjs +143 -4
- package/dist/browser.mjs.map +1 -1
- package/dist/index.d.mts +66 -4
- package/dist/index.d.ts +66 -4
- package/dist/index.js +149 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +143 -4
- package/dist/index.mjs.map +1 -1
- package/dist/rcan-validate.js +6 -2
- package/dist/rcan.iife.js +16 -3
- package/package.json +30 -4
package/dist/index.mjs
CHANGED
|
@@ -96,8 +96,8 @@ var RobotURI = class _RobotURI {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
// src/version.ts
|
|
99
|
-
var SPEC_VERSION = "2.
|
|
100
|
-
var SDK_VERSION = "1.1
|
|
99
|
+
var SPEC_VERSION = "2.2.0";
|
|
100
|
+
var SDK_VERSION = "1.2.1";
|
|
101
101
|
function validateVersionCompat(incomingVersion, localVersion = SPEC_VERSION) {
|
|
102
102
|
const parseParts = (v) => {
|
|
103
103
|
const parts = v.split(".");
|
|
@@ -195,6 +195,8 @@ var RCANMessage = class _RCANMessage {
|
|
|
195
195
|
firmwareHash;
|
|
196
196
|
/** v2.1: URI to sender's SBOM attestation endpoint */
|
|
197
197
|
attestationRef;
|
|
198
|
+
/** v2.2: ML-DSA-65 post-quantum signature (field 16, FIPS 204). Hybrid alongside Ed25519. */
|
|
199
|
+
pqSig;
|
|
198
200
|
constructor(data) {
|
|
199
201
|
if (!data.cmd || data.cmd.trim() === "") {
|
|
200
202
|
throw new RCANMessageError("'cmd' is required");
|
|
@@ -225,6 +227,7 @@ var RCANMessage = class _RCANMessage {
|
|
|
225
227
|
this.mediaChunks = data.mediaChunks;
|
|
226
228
|
this.firmwareHash = data.firmwareHash;
|
|
227
229
|
this.attestationRef = data.attestationRef;
|
|
230
|
+
this.pqSig = data.pqSig;
|
|
228
231
|
if (this.signature !== void 0 && this.signature["sig"] === "pending") {
|
|
229
232
|
throw new RCANMessageError(
|
|
230
233
|
"signature.sig:'pending' is not valid in RCAN v2.1. Sign the message before sending."
|
|
@@ -317,7 +320,8 @@ var RCANMessage = class _RCANMessage {
|
|
|
317
320
|
transportEncoding: obj.transportEncoding,
|
|
318
321
|
mediaChunks: obj.mediaChunks,
|
|
319
322
|
firmwareHash: obj.firmwareHash,
|
|
320
|
-
attestationRef: obj.attestationRef
|
|
323
|
+
attestationRef: obj.attestationRef,
|
|
324
|
+
pqSig: obj.pqSig
|
|
321
325
|
});
|
|
322
326
|
}
|
|
323
327
|
};
|
|
@@ -3084,6 +3088,136 @@ async function verifyM2mTrustedToken(token, targetRrn, options) {
|
|
|
3084
3088
|
return claims;
|
|
3085
3089
|
}
|
|
3086
3090
|
|
|
3091
|
+
// src/pqSigning.ts
|
|
3092
|
+
function toBase64url(bytes) {
|
|
3093
|
+
let binary = "";
|
|
3094
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
3095
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3096
|
+
}
|
|
3097
|
+
function fromBase64url(b64) {
|
|
3098
|
+
const padded = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
3099
|
+
const binary = atob(padded);
|
|
3100
|
+
const bytes = new Uint8Array(binary.length);
|
|
3101
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
3102
|
+
return bytes;
|
|
3103
|
+
}
|
|
3104
|
+
async function sha256hex(data) {
|
|
3105
|
+
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
3106
|
+
const buf = await crypto.subtle.digest("SHA-256", data.buffer);
|
|
3107
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
|
|
3108
|
+
}
|
|
3109
|
+
const { createHash } = __require("crypto");
|
|
3110
|
+
return createHash("sha256").update(data).digest("hex").slice(0, 8);
|
|
3111
|
+
}
|
|
3112
|
+
var _mlDsaModule;
|
|
3113
|
+
async function requireMlDsa() {
|
|
3114
|
+
if (_mlDsaModule) return _mlDsaModule;
|
|
3115
|
+
if (typeof __require !== "undefined") {
|
|
3116
|
+
try {
|
|
3117
|
+
_mlDsaModule = __require("@noble/post-quantum/ml-dsa.js");
|
|
3118
|
+
return _mlDsaModule;
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
try {
|
|
3123
|
+
_mlDsaModule = await import("@noble/post-quantum/ml-dsa.js");
|
|
3124
|
+
return _mlDsaModule;
|
|
3125
|
+
} catch {
|
|
3126
|
+
throw new Error(
|
|
3127
|
+
"ML-DSA-65 signing requires @noble/post-quantum. Install with: npm install @noble/post-quantum"
|
|
3128
|
+
);
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
var MLDSAKeyPair = class _MLDSAKeyPair {
|
|
3132
|
+
keyId;
|
|
3133
|
+
publicKey;
|
|
3134
|
+
secretKey;
|
|
3135
|
+
constructor(data) {
|
|
3136
|
+
this.keyId = data.keyId;
|
|
3137
|
+
this.publicKey = data.publicKey;
|
|
3138
|
+
this.secretKey = data.secretKey;
|
|
3139
|
+
}
|
|
3140
|
+
static async generate() {
|
|
3141
|
+
const m = await requireMlDsa();
|
|
3142
|
+
const kp = m.ml_dsa65.keygen();
|
|
3143
|
+
const keyId = await sha256hex(kp.publicKey);
|
|
3144
|
+
return new _MLDSAKeyPair({ publicKey: kp.publicKey, secretKey: kp.secretKey, keyId });
|
|
3145
|
+
}
|
|
3146
|
+
static async fromPublicKey(publicKey) {
|
|
3147
|
+
const keyId = await sha256hex(publicKey);
|
|
3148
|
+
return new _MLDSAKeyPair({ publicKey, keyId });
|
|
3149
|
+
}
|
|
3150
|
+
static async fromKeyMaterial(publicKey, secretKey) {
|
|
3151
|
+
const keyId = await sha256hex(publicKey);
|
|
3152
|
+
return new _MLDSAKeyPair({ publicKey, secretKey, keyId });
|
|
3153
|
+
}
|
|
3154
|
+
get hasPrivateKey() {
|
|
3155
|
+
return this.secretKey !== void 0;
|
|
3156
|
+
}
|
|
3157
|
+
async signBytes(data) {
|
|
3158
|
+
if (!this.secretKey) throw new Error("Cannot sign: MLDSAKeyPair has no private key (verify-only)");
|
|
3159
|
+
const m = await requireMlDsa();
|
|
3160
|
+
return m.ml_dsa65.sign(data, this.secretKey);
|
|
3161
|
+
}
|
|
3162
|
+
async verifyBytes(data, signature) {
|
|
3163
|
+
const m = await requireMlDsa();
|
|
3164
|
+
const ok = m.ml_dsa65.verify(signature, data, this.publicKey);
|
|
3165
|
+
if (!ok) throw new Error("ML-DSA-65 signature verification failed");
|
|
3166
|
+
}
|
|
3167
|
+
toString() {
|
|
3168
|
+
return `MLDSAKeyPair(keyId=${this.keyId}, alg=ML-DSA-65, ${this.hasPrivateKey ? "private+public" : "public-only"})`;
|
|
3169
|
+
}
|
|
3170
|
+
};
|
|
3171
|
+
function canonicalMessageBytes(msg) {
|
|
3172
|
+
const payload = {
|
|
3173
|
+
rcan: msg.rcan,
|
|
3174
|
+
msg_id: msg["msgId"] ?? "",
|
|
3175
|
+
timestamp: msg.timestamp,
|
|
3176
|
+
cmd: msg.cmd,
|
|
3177
|
+
target: msg.target,
|
|
3178
|
+
params: msg.params
|
|
3179
|
+
};
|
|
3180
|
+
return new TextEncoder().encode(
|
|
3181
|
+
JSON.stringify(Object.fromEntries(Object.entries(payload).sort()))
|
|
3182
|
+
);
|
|
3183
|
+
}
|
|
3184
|
+
async function signMessage(msg, keypair) {
|
|
3185
|
+
const payload = canonicalMessageBytes(msg);
|
|
3186
|
+
const rawSig = await keypair.signBytes(payload);
|
|
3187
|
+
msg["signature"] = {
|
|
3188
|
+
alg: "ml-dsa-65",
|
|
3189
|
+
kid: keypair.keyId,
|
|
3190
|
+
sig: toBase64url(rawSig)
|
|
3191
|
+
};
|
|
3192
|
+
return msg;
|
|
3193
|
+
}
|
|
3194
|
+
async function verifyMessage(msg, trustedKeys) {
|
|
3195
|
+
const sig = msg.signature;
|
|
3196
|
+
if (!sig) throw new Error("Message is unsigned \u2014 signature field missing");
|
|
3197
|
+
if (sig.alg !== "ml-dsa-65") {
|
|
3198
|
+
throw new Error(
|
|
3199
|
+
`Unsupported signature algorithm: ${sig.alg}. RCAN v2.2 requires ml-dsa-65 (Ed25519 is deprecated).`
|
|
3200
|
+
);
|
|
3201
|
+
}
|
|
3202
|
+
const matched = trustedKeys.find((k) => k.keyId === sig.kid);
|
|
3203
|
+
if (!matched) {
|
|
3204
|
+
throw new Error(
|
|
3205
|
+
`No trusted ML-DSA-65 key with kid=${sig.kid}. Known kids: [${trustedKeys.map((k) => k.keyId).join(", ")}]`
|
|
3206
|
+
);
|
|
3207
|
+
}
|
|
3208
|
+
let rawSig;
|
|
3209
|
+
try {
|
|
3210
|
+
rawSig = fromBase64url(sig.sig);
|
|
3211
|
+
} catch (e) {
|
|
3212
|
+
throw new Error(`Invalid base64url sig: ${e}`);
|
|
3213
|
+
}
|
|
3214
|
+
await matched.verifyBytes(canonicalMessageBytes(msg), rawSig);
|
|
3215
|
+
}
|
|
3216
|
+
var addPQSignature = signMessage;
|
|
3217
|
+
async function verifyPQSignature(msg, trustedKeys, _requirePQ = true) {
|
|
3218
|
+
return verifyMessage(msg, trustedKeys);
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3087
3221
|
// src/index.ts
|
|
3088
3222
|
var VERSION = "0.6.0";
|
|
3089
3223
|
var RCAN_VERSION = "1.6";
|
|
@@ -3108,6 +3242,7 @@ export {
|
|
|
3108
3242
|
LevelOfAssurance,
|
|
3109
3243
|
M2MAuthError,
|
|
3110
3244
|
M2M_TRUSTED_ISSUER,
|
|
3245
|
+
MLDSAKeyPair,
|
|
3111
3246
|
MediaEncoding,
|
|
3112
3247
|
MessageType,
|
|
3113
3248
|
NodeClient,
|
|
@@ -3154,6 +3289,7 @@ export {
|
|
|
3154
3289
|
addDelegationHop,
|
|
3155
3290
|
addMediaInline,
|
|
3156
3291
|
addMediaRef,
|
|
3292
|
+
addPQSignature,
|
|
3157
3293
|
assertClockSynced,
|
|
3158
3294
|
authorityAccessFromWire,
|
|
3159
3295
|
authorityAccessToWire,
|
|
@@ -3207,6 +3343,7 @@ export {
|
|
|
3207
3343
|
parseM2mTrustedToken,
|
|
3208
3344
|
roleFromJwtLevel,
|
|
3209
3345
|
selectTransport,
|
|
3346
|
+
signMessage,
|
|
3210
3347
|
validateAuthorityAccess,
|
|
3211
3348
|
validateCompetitionScope,
|
|
3212
3349
|
validateConfig,
|
|
@@ -3228,6 +3365,8 @@ export {
|
|
|
3228
3365
|
validateURI,
|
|
3229
3366
|
validateVersionCompat,
|
|
3230
3367
|
verifyM2mTrustedToken,
|
|
3231
|
-
verifyM2mTrustedTokenClaims
|
|
3368
|
+
verifyM2mTrustedTokenClaims,
|
|
3369
|
+
verifyMessage,
|
|
3370
|
+
verifyPQSignature
|
|
3232
3371
|
};
|
|
3233
3372
|
//# sourceMappingURL=index.mjs.map
|