@continuonai/rcan-ts 1.1.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/dist/browser.d.mts +90 -3
- package/dist/browser.mjs +169 -4
- package/dist/browser.mjs.map +1 -1
- package/dist/index.d.mts +90 -3
- package/dist/index.d.ts +90 -3
- package/dist/index.js +173 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +169 -4
- package/dist/index.mjs.map +1 -1
- package/dist/rcan-validate.js +7 -2
- package/dist/rcan.iife.js +16 -3
- package/package.json +28 -5
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.
|
|
99
|
+
var SPEC_VERSION = "2.2.0";
|
|
100
|
+
var SDK_VERSION = "1.2.0";
|
|
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."
|
|
@@ -273,6 +276,7 @@ var RCANMessage = class _RCANMessage {
|
|
|
273
276
|
if (this.mediaChunks !== void 0) obj.mediaChunks = this.mediaChunks;
|
|
274
277
|
if (this.firmwareHash !== void 0) obj.firmwareHash = this.firmwareHash;
|
|
275
278
|
if (this.attestationRef !== void 0) obj.attestationRef = this.attestationRef;
|
|
279
|
+
if (this.pqSig !== void 0) obj.pqSig = this.pqSig;
|
|
276
280
|
return obj;
|
|
277
281
|
}
|
|
278
282
|
/** Serialize to JSON string */
|
|
@@ -317,7 +321,8 @@ var RCANMessage = class _RCANMessage {
|
|
|
317
321
|
transportEncoding: obj.transportEncoding,
|
|
318
322
|
mediaChunks: obj.mediaChunks,
|
|
319
323
|
firmwareHash: obj.firmwareHash,
|
|
320
|
-
attestationRef: obj.attestationRef
|
|
324
|
+
attestationRef: obj.attestationRef,
|
|
325
|
+
pqSig: obj.pqSig
|
|
321
326
|
});
|
|
322
327
|
}
|
|
323
328
|
};
|
|
@@ -3084,6 +3089,163 @@ async function verifyM2mTrustedToken(token, targetRrn, options) {
|
|
|
3084
3089
|
return claims;
|
|
3085
3090
|
}
|
|
3086
3091
|
|
|
3092
|
+
// src/pqSigning.ts
|
|
3093
|
+
function toBase64url(bytes) {
|
|
3094
|
+
let binary = "";
|
|
3095
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
3096
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
3097
|
+
}
|
|
3098
|
+
function fromBase64url(b64) {
|
|
3099
|
+
const padded = b64.replace(/-/g, "+").replace(/_/g, "/");
|
|
3100
|
+
const binary = atob(padded);
|
|
3101
|
+
const bytes = new Uint8Array(binary.length);
|
|
3102
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
3103
|
+
return bytes;
|
|
3104
|
+
}
|
|
3105
|
+
async function sha256hex(data) {
|
|
3106
|
+
const g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
3107
|
+
const webcrypto = g.crypto;
|
|
3108
|
+
const subtle = webcrypto?.subtle;
|
|
3109
|
+
if (subtle) {
|
|
3110
|
+
const buf = await subtle.digest("SHA-256", data);
|
|
3111
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 8);
|
|
3112
|
+
}
|
|
3113
|
+
const nodeCrypto = await import("crypto").catch(() => null);
|
|
3114
|
+
if (nodeCrypto) {
|
|
3115
|
+
return nodeCrypto.createHash("sha256").update(data).digest("hex").slice(0, 8);
|
|
3116
|
+
}
|
|
3117
|
+
throw new Error("No SHA-256 implementation available");
|
|
3118
|
+
}
|
|
3119
|
+
var _mlDsaModule;
|
|
3120
|
+
async function requireNoblePostQuantum() {
|
|
3121
|
+
if (_mlDsaModule) return _mlDsaModule;
|
|
3122
|
+
if (typeof __require !== "undefined") {
|
|
3123
|
+
try {
|
|
3124
|
+
_mlDsaModule = __require("@noble/post-quantum/ml-dsa.js");
|
|
3125
|
+
return _mlDsaModule;
|
|
3126
|
+
} catch {
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
try {
|
|
3130
|
+
_mlDsaModule = await import("@noble/post-quantum/ml-dsa.js");
|
|
3131
|
+
return _mlDsaModule;
|
|
3132
|
+
} catch {
|
|
3133
|
+
throw new Error(
|
|
3134
|
+
"ML-DSA signing requires @noble/post-quantum. Install with: npm install @noble/post-quantum"
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
var MLDSAKeyPair = class _MLDSAKeyPair {
|
|
3139
|
+
keyId;
|
|
3140
|
+
publicKey;
|
|
3141
|
+
secretKey;
|
|
3142
|
+
constructor(data) {
|
|
3143
|
+
this.keyId = data.keyId;
|
|
3144
|
+
this.publicKey = data.publicKey;
|
|
3145
|
+
this.secretKey = data.secretKey;
|
|
3146
|
+
}
|
|
3147
|
+
/** Generate a new ML-DSA-65 key pair. */
|
|
3148
|
+
static async generate() {
|
|
3149
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3150
|
+
const kp = ml_dsa65.keygen();
|
|
3151
|
+
const keyId = await sha256hex(kp.publicKey);
|
|
3152
|
+
return new _MLDSAKeyPair({
|
|
3153
|
+
publicKey: kp.publicKey,
|
|
3154
|
+
secretKey: kp.secretKey,
|
|
3155
|
+
keyId
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
/** Build a verify-only key pair from raw public key bytes. */
|
|
3159
|
+
static async fromPublicKey(publicKey) {
|
|
3160
|
+
const keyId = await sha256hex(publicKey);
|
|
3161
|
+
return new _MLDSAKeyPair({ publicKey, keyId });
|
|
3162
|
+
}
|
|
3163
|
+
/** Build a full key pair from saved bytes (public + secret). */
|
|
3164
|
+
static async fromKeyMaterial(publicKey, secretKey) {
|
|
3165
|
+
const keyId = await sha256hex(publicKey);
|
|
3166
|
+
return new _MLDSAKeyPair({ publicKey, secretKey, keyId });
|
|
3167
|
+
}
|
|
3168
|
+
get hasPrivateKey() {
|
|
3169
|
+
return this.secretKey !== void 0;
|
|
3170
|
+
}
|
|
3171
|
+
/** Sign raw bytes; returns ML-DSA-65 signature (3309 bytes). */
|
|
3172
|
+
async signBytes(data) {
|
|
3173
|
+
if (!this.secretKey) {
|
|
3174
|
+
throw new Error("Cannot sign: MLDSAKeyPair has no private key (verify-only)");
|
|
3175
|
+
}
|
|
3176
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3177
|
+
return ml_dsa65.sign(data, this.secretKey);
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Verify an ML-DSA-65 signature.
|
|
3181
|
+
* @returns true if valid
|
|
3182
|
+
* @throws {Error} on invalid signature
|
|
3183
|
+
*/
|
|
3184
|
+
async verifyBytes(data, signature) {
|
|
3185
|
+
const { ml_dsa65 } = await requireNoblePostQuantum();
|
|
3186
|
+
const ok = ml_dsa65.verify(signature, data, this.publicKey);
|
|
3187
|
+
if (!ok) throw new Error("ML-DSA signature verification failed");
|
|
3188
|
+
}
|
|
3189
|
+
toString() {
|
|
3190
|
+
const mode = this.hasPrivateKey ? "private+public" : "public-only";
|
|
3191
|
+
return `MLDSAKeyPair(keyId=${this.keyId}, alg=ML-DSA-65, ${mode})`;
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
function canonicalMessageBytes(msg) {
|
|
3195
|
+
const m = msg;
|
|
3196
|
+
const payload = {
|
|
3197
|
+
rcan: msg.rcan,
|
|
3198
|
+
msg_id: m["msgId"] ?? m["msg_id"] ?? "",
|
|
3199
|
+
timestamp: msg.timestamp,
|
|
3200
|
+
cmd: msg.cmd,
|
|
3201
|
+
target: msg.target,
|
|
3202
|
+
params: msg.params
|
|
3203
|
+
};
|
|
3204
|
+
const sorted = JSON.stringify(
|
|
3205
|
+
Object.fromEntries(Object.entries(payload).sort()),
|
|
3206
|
+
null,
|
|
3207
|
+
void 0
|
|
3208
|
+
);
|
|
3209
|
+
return new TextEncoder().encode(sorted);
|
|
3210
|
+
}
|
|
3211
|
+
async function addPQSignature(msg, keypair) {
|
|
3212
|
+
const payload = canonicalMessageBytes(msg);
|
|
3213
|
+
const rawSig = await keypair.signBytes(payload);
|
|
3214
|
+
const block = {
|
|
3215
|
+
alg: "ml-dsa-65",
|
|
3216
|
+
kid: keypair.keyId,
|
|
3217
|
+
sig: toBase64url(rawSig)
|
|
3218
|
+
};
|
|
3219
|
+
msg["pqSig"] = block;
|
|
3220
|
+
return msg;
|
|
3221
|
+
}
|
|
3222
|
+
async function verifyPQSignature(msg, trustedKeys, requirePQ = false) {
|
|
3223
|
+
const pqSig = msg["pqSig"];
|
|
3224
|
+
if (!pqSig) {
|
|
3225
|
+
if (requirePQ) {
|
|
3226
|
+
throw new Error("ML-DSA signature (pqSig) required but missing from message");
|
|
3227
|
+
}
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
if (pqSig.alg !== "ml-dsa-65") {
|
|
3231
|
+
throw new Error(`Unsupported PQ signature algorithm: ${pqSig.alg}`);
|
|
3232
|
+
}
|
|
3233
|
+
const matched = trustedKeys.find((k) => k.keyId === pqSig.kid);
|
|
3234
|
+
if (!matched) {
|
|
3235
|
+
throw new Error(
|
|
3236
|
+
`No trusted ML-DSA key with kid=${pqSig.kid}. Known kids: [${trustedKeys.map((k) => k.keyId).join(", ")}]`
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
let rawSig;
|
|
3240
|
+
try {
|
|
3241
|
+
rawSig = fromBase64url(pqSig.sig);
|
|
3242
|
+
} catch (e) {
|
|
3243
|
+
throw new Error(`Invalid base64url ML-DSA signature: ${e}`);
|
|
3244
|
+
}
|
|
3245
|
+
const payload = canonicalMessageBytes(msg);
|
|
3246
|
+
await matched.verifyBytes(payload, rawSig);
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3087
3249
|
// src/index.ts
|
|
3088
3250
|
var VERSION = "0.6.0";
|
|
3089
3251
|
var RCAN_VERSION = "1.6";
|
|
@@ -3108,6 +3270,7 @@ export {
|
|
|
3108
3270
|
LevelOfAssurance,
|
|
3109
3271
|
M2MAuthError,
|
|
3110
3272
|
M2M_TRUSTED_ISSUER,
|
|
3273
|
+
MLDSAKeyPair,
|
|
3111
3274
|
MediaEncoding,
|
|
3112
3275
|
MessageType,
|
|
3113
3276
|
NodeClient,
|
|
@@ -3154,6 +3317,7 @@ export {
|
|
|
3154
3317
|
addDelegationHop,
|
|
3155
3318
|
addMediaInline,
|
|
3156
3319
|
addMediaRef,
|
|
3320
|
+
addPQSignature,
|
|
3157
3321
|
assertClockSynced,
|
|
3158
3322
|
authorityAccessFromWire,
|
|
3159
3323
|
authorityAccessToWire,
|
|
@@ -3228,6 +3392,7 @@ export {
|
|
|
3228
3392
|
validateURI,
|
|
3229
3393
|
validateVersionCompat,
|
|
3230
3394
|
verifyM2mTrustedToken,
|
|
3231
|
-
verifyM2mTrustedTokenClaims
|
|
3395
|
+
verifyM2mTrustedTokenClaims,
|
|
3396
|
+
verifyPQSignature
|
|
3232
3397
|
};
|
|
3233
3398
|
//# sourceMappingURL=index.mjs.map
|