@byollm/protocol 0.1.0-alpha.93 → 0.1.0-alpha.94

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  > [!WARNING]
2
- > **Alpha (`0.1.0-alpha.93`) — under active development. Don't use this yet.**
2
+ > **Alpha (`0.1.0-alpha.94`) — under active development. Don't use this yet.**
3
3
  >
4
4
  > Install it deliberately: `npm install @byollm/protocol@alpha`.
5
5
  >
package/dist/index.d.ts CHANGED
@@ -3195,4 +3195,70 @@ interface ConsoleOrder {
3195
3195
  }
3196
3196
  declare function consoleOrder(from: "browser" | "box"): ConsoleOrder;
3197
3197
 
3198
- export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, CONSOLE_FRAME_VERSION, CONSOLE_MAX_DATA_BYTES, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, ConsoleBye, ConsoleFrame, ConsoleHello, type ConsoleOrder, type ConsoleOrderFault, type ConsoleOrderResult, ConsoleResize, ConsoleStdin, ConsoleStdout, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, FetchRequest, FetchResponse, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, Purpose, REFUSAL_MESSAGES, REFUSAL_TEXT, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, type StopReason, StopReasonSchema, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, UPGRADE_COMMAND, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, consoleEnvelope, consoleOrder, cryptoReady, declaredVersion, describeBytes, effectiveOfferScope, envelopeBytes, fingerprint, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mayOfferUpdate, mentionsWireField, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, tooLargeMessage, updateOfferFor, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession, withoutComments };
3198
+ /**
3199
+ * The envelope's FORMAT, with no primitives and no platform — B018c step 3.
3200
+ *
3201
+ * ## Why this file exists
3202
+ *
3203
+ * The browser is one of the two ends a console stream is encrypted between,
3204
+ * and it cannot run `envelope.ts`: the sealing is WASM and portable, but the
3205
+ * inner signature goes through `node:crypto`'s Ed25519. So the browser needs
3206
+ * its own primitives.
3207
+ *
3208
+ * What it must NOT have is its own *format*. Two implementations of "which
3209
+ * bytes get signed" is the defect class this codebase spends most of its
3210
+ * checks on, and here the two copies would diverge silently — a mismatched
3211
+ * signature is indistinguishable from an attack, so the first symptom would
3212
+ * be a console that refuses to open and a log line saying `bad-signature`.
3213
+ *
3214
+ * Everything here is therefore pure: no `node:` imports, no `Buffer`, no
3215
+ * `btoa`. `envelope-is-portable.test.ts` asserts that mechanically, because
3216
+ * "somebody will import Buffer for convenience one day" is a prediction, and
3217
+ * this project's rule is that a prediction ships with the test that catches it.
3218
+ *
3219
+ * ## Why base64url is written out by hand
3220
+ *
3221
+ * `Buffer` is Node's and `btoa` takes a binary string, so neither is both
3222
+ * portable and pleasant. Twenty lines of table lookup is: it agrees with
3223
+ * `Buffer` on every byte value, which the test proves rather than assumes.
3224
+ */
3225
+ /** Unpadded base64url, the encoding every key and signature on this wire uses. */
3226
+ declare function toBase64Url(bytes: Uint8Array): string;
3227
+ /** The inverse. Returns undefined rather than throwing — this parses input. */
3228
+ declare function fromBase64Url(text: string): Uint8Array | undefined;
3229
+ /** Domain tag for the bytes an envelope's signature covers. */
3230
+ declare const ENVELOPE_BODY_VERSION = "byollm/v1/envelope";
3231
+ /** What an envelope's signature is over — the fields, in this order. */
3232
+ interface EnvelopeBodyContext {
3233
+ readonly jobId: string;
3234
+ readonly senderKeyId: string;
3235
+ readonly recipientKeyId: string;
3236
+ readonly deadlineAt: number;
3237
+ readonly direction: string;
3238
+ }
3239
+ /**
3240
+ * The exact bytes an envelope's signature covers.
3241
+ *
3242
+ * **Field order is part of the format**, because `JSON.stringify` emits keys
3243
+ * in insertion order and the verifier hashes bytes, not meaning. Reordering
3244
+ * these lines is a wire change that no type would catch and every signature
3245
+ * would fail — which is why they are written out rather than spread from an
3246
+ * object somebody could reshape.
3247
+ */
3248
+ declare function envelopeSignedBody(context: EnvelopeBodyContext, plaintext: string): Uint8Array;
3249
+ /** The signed body and its signature, as they travel inside the sealed box. */
3250
+ declare function encodeEnvelopeInner(body: Uint8Array, signature: string): string;
3251
+ interface EnvelopeInner {
3252
+ readonly body: Uint8Array;
3253
+ readonly signature: string;
3254
+ }
3255
+ /**
3256
+ * Read the inner object back.
3257
+ *
3258
+ * Returns undefined for anything malformed rather than throwing, because
3259
+ * every byte reaching this has come out of a decryption and is therefore
3260
+ * input — `malformed` is one of the outcomes `open()` already distinguishes.
3261
+ */
3262
+ declare function decodeEnvelopeInner(text: string): EnvelopeInner | undefined;
3263
+
3264
+ export { ABOUT, ABOUT_SHORT, ABOUT_SHORT_LEDE, ABOUT_SHORT_TAIL, AUDIENCES, Audience, BACKENDS, BACKEND_CLASSES, BACKEND_IDS, BackendClass, BackendCost, type BackendDescriptor, type BackendId, BackendIdSchema, CLOCK_ATTRIBUTION_MS, CLOCK_SKEW_WARN_MS, CONSOLE_FRAME_VERSION, CONSOLE_MAX_DATA_BYTES, Capability, CapabilityMatrix, ChatMessage, ChatPayload, ClaimRequest, ClaimResponse, ClaimedJob, ClaimedStub, ConsoleBye, ConsoleFrame, ConsoleHello, type ConsoleOrder, type ConsoleOrderFault, type ConsoleOrderResult, ConsoleResize, ConsoleStdin, ConsoleStdout, DeliveredResult, ENCRYPTION_KEY_CONTEXT, ENDPOINTS, ENVELOPE_BODY_VERSION, ENVELOPE_MAX_AGE_MS, ERROR_STATUS, type Endpoint, type EnvelopeBodyContext, type EnvelopeContext, EnvelopeDirection, type EnvelopeFailure, type EnvelopeInner, FetchRequest, FetchResponse, type FloorRefusal, GRANT_CONTEXT, GRANT_MAX_AGE_MS, GRANT_SIGNED_FIELDS, GeneratePayload, type GrantClaims, GrantRef, type GrantRefusal, HeartbeatRequest, HeartbeatResponse, JOB_KINDS, JobKind, JobOutcome, JobPayload, JobRefused, JobResultCanceled, JobResultError, JobResultOk, JobState, JobStub, KindedPayload, Lease, MAX_CLOCK_SKEW_MS, MAX_ENVELOPE_BYTES, MAX_PURPOSES, MAX_SUCCESSION_CHAIN, MIN_PROTOCOL_VERSION, MUSTS, MUST_IDS, Manifest, type MatchDaemon, type MatchJob, MatchRefusal, type MatchResult, type Must, type MustEnforcer, type MustId, type MustVerification, type MustVerifiedBy, OFFER_SCOPES, OfferScope, type OpenResult, PAYLOAD_LIMITS, PROTOCOL_PREFIX, PROTOCOL_VERSION, PairPollRequest, PairPollResponse, PairRequest, PairStartRequest, PairStartResponse, type PayloadFor, PublicIdentity, Purpose, REFUSAL_MESSAGES, REFUSAL_TEXT, RESERVED_PURPOSE, RETIREMENT_WINDOW_MS, RefusalReason, ReleaseRequest, ReleaseResponse, RequestSignature, ResultDisposition, ResultProvenance, ResultRequest, ResultResponse, RunMetadata, SIZE_CLASSES, SIZE_CLASS_LIMITS, SUCCESSION_CONTEXT, SUPPORTED_PROTOCOL_VERSIONS, SealedEnvelope, SealedOutcome, type SignatureFailure, SignedGrant, SizeClass, type SpendConsent, type StopReason, StopReasonSchema, StoredKeys, Succession, type SuccessionFailure, type SuccessionWalk, TERMINAL_STATES, UPDATE_OFFER_SINCE, UPGRADE_COMMAND, type VersionRefusal, WireError, WireErrorCode, WithheldKind, backendDescriptor, backendName, canTransition, canonicalRequest, checkDaemonFloor, checkProtocolVersion, classifyCost, compareVersions, consoleEnvelope, consoleOrder, cryptoReady, declaredVersion, decodeEnvelopeInner, describeBytes, effectiveOfferScope, encodeEnvelopeInner, envelopeBytes, envelopeSignedBody, fingerprint, fromBase64Url, generateKeys, grantStatement, isBackendId, isCloudTaggedModel, isJobKind, isLocalHost, isTerminal, keyId, kindsOf, matchAudience, mayOfferUpdate, mentionsWireField, mustsVerifiedBy, open, payloadTextLength, provenanceFor, publicIdentityOf, resolveCost, seal, signGrant, signRequest, signSiteRequest, signSuccession, signWith, singlePurposeManifest, sizeClassCeiling, sizeClassOf, successionStatement, toBase64Url, tooLargeMessage, updateOfferFor, verifyGrant, verifyLink, verifyPublicIdentity, verifyRequest, verifySiteRequest, verifyWith, walkSuccession, withoutComments };
package/dist/index.js CHANGED
@@ -1163,6 +1163,85 @@ var ClaimedStub = JobStub.extend({
1163
1163
  import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2 } from "crypto";
1164
1164
  import sodium from "libsodium-wrappers";
1165
1165
  import { z as z8 } from "zod";
1166
+
1167
+ // src/envelope-format.ts
1168
+ var ALPHABET2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1169
+ var CHARS = ALPHABET2.split("");
1170
+ var VALUES = /* @__PURE__ */ (() => {
1171
+ const table = new Uint8Array(128).fill(255);
1172
+ for (let at = 0; at < ALPHABET2.length; at += 1) {
1173
+ table[ALPHABET2.charCodeAt(at)] = at;
1174
+ }
1175
+ return table;
1176
+ })();
1177
+ function toBase64Url(bytes) {
1178
+ let out = "";
1179
+ for (let at = 0; at < bytes.length; at += 3) {
1180
+ const a = bytes[at] ?? 0;
1181
+ const b = bytes[at + 1];
1182
+ const c = bytes[at + 2];
1183
+ const word = a << 16 | (b ?? 0) << 8 | (c ?? 0);
1184
+ out += CHARS[word >> 18 & 63] ?? "";
1185
+ out += CHARS[word >> 12 & 63] ?? "";
1186
+ if (b !== void 0) out += CHARS[word >> 6 & 63] ?? "";
1187
+ if (c !== void 0) out += CHARS[word & 63] ?? "";
1188
+ }
1189
+ return out;
1190
+ }
1191
+ function fromBase64Url(text) {
1192
+ const length = text.length;
1193
+ if (length % 4 === 1) return void 0;
1194
+ const bytes = new Uint8Array(Math.floor(length * 3 / 4));
1195
+ let written = 0;
1196
+ let word = 0;
1197
+ let bits = 0;
1198
+ for (let at = 0; at < length; at += 1) {
1199
+ const code = text.charCodeAt(at);
1200
+ const value = code < 128 ? VALUES[code] : 255;
1201
+ if (value === void 0 || value === 255) return void 0;
1202
+ word = word << 6 | value;
1203
+ bits += 6;
1204
+ if (bits >= 8) {
1205
+ bits -= 8;
1206
+ bytes[written] = word >> bits & 255;
1207
+ written += 1;
1208
+ }
1209
+ }
1210
+ return bytes.subarray(0, written);
1211
+ }
1212
+ var ENVELOPE_BODY_VERSION = "byollm/v1/envelope";
1213
+ function envelopeSignedBody(context, plaintext) {
1214
+ return new TextEncoder().encode(
1215
+ JSON.stringify({
1216
+ v: ENVELOPE_BODY_VERSION,
1217
+ jobId: context.jobId,
1218
+ senderKeyId: context.senderKeyId,
1219
+ recipientKeyId: context.recipientKeyId,
1220
+ deadlineAt: context.deadlineAt,
1221
+ direction: context.direction,
1222
+ plaintext
1223
+ })
1224
+ );
1225
+ }
1226
+ function encodeEnvelopeInner(body, signature) {
1227
+ return JSON.stringify({ body: toBase64Url(body), signature });
1228
+ }
1229
+ function decodeEnvelopeInner(text) {
1230
+ let parsed;
1231
+ try {
1232
+ parsed = JSON.parse(text);
1233
+ } catch {
1234
+ return void 0;
1235
+ }
1236
+ if (typeof parsed.body !== "string" || typeof parsed.signature !== "string") {
1237
+ return void 0;
1238
+ }
1239
+ const body = fromBase64Url(parsed.body);
1240
+ if (body === void 0) return void 0;
1241
+ return { body, signature: parsed.signature };
1242
+ }
1243
+
1244
+ // src/envelope.ts
1166
1245
  var readied;
1167
1246
  async function cryptoReady() {
1168
1247
  readied ??= sodium.ready;
@@ -1192,20 +1271,7 @@ var SealedEnvelope = z8.object({
1192
1271
  */
1193
1272
  deadlineAt: z8.number().int().positive()
1194
1273
  }).strict();
1195
- function signedBody(context, plaintext) {
1196
- return Buffer.from(
1197
- JSON.stringify({
1198
- v: "byollm/v1/envelope",
1199
- jobId: context.jobId,
1200
- senderKeyId: context.senderKeyId,
1201
- recipientKeyId: context.recipientKeyId,
1202
- deadlineAt: context.deadlineAt,
1203
- direction: context.direction,
1204
- plaintext
1205
- }),
1206
- "utf8"
1207
- );
1208
- }
1274
+ var signedBody = (context, plaintext) => envelopeSignedBody(context, plaintext);
1209
1275
  var rawX25519 = (key, part) => {
1210
1276
  const jwk = key.export({ format: "jwk" });
1211
1277
  const value = part === "x" ? jwk.x : jwk.d;
@@ -1216,7 +1282,7 @@ async function seal(input) {
1216
1282
  await cryptoReady();
1217
1283
  const body = signedBody(input.context, input.plaintext);
1218
1284
  const signature = signWith(input.senderKeys, body);
1219
- const inner = JSON.stringify({ body: body.toString("base64url"), signature });
1285
+ const inner = encodeEnvelopeInner(body, signature);
1220
1286
  const recipient = new Uint8Array(
1221
1287
  Buffer.from(input.recipientEncryptionPublic, "base64url")
1222
1288
  );
@@ -1255,22 +1321,15 @@ async function open(input) {
1255
1321
  } catch {
1256
1322
  return { ok: false, reason: "unopenable" };
1257
1323
  }
1258
- let parsed;
1259
- try {
1260
- parsed = JSON.parse(inner);
1261
- } catch {
1262
- return { ok: false, reason: "malformed" };
1263
- }
1264
- if (typeof parsed.body !== "string" || typeof parsed.signature !== "string") {
1265
- return { ok: false, reason: "malformed" };
1266
- }
1267
- const body = Buffer.from(parsed.body, "base64url");
1324
+ const parsed = decodeEnvelopeInner(inner);
1325
+ if (parsed === void 0) return { ok: false, reason: "malformed" };
1326
+ const body = parsed.body;
1268
1327
  if (!verifyWith(input.senderIdentityPublic, body, parsed.signature)) {
1269
1328
  return { ok: false, reason: "bad-signature" };
1270
1329
  }
1271
1330
  let claims;
1272
1331
  try {
1273
- claims = JSON.parse(body.toString("utf8"));
1332
+ claims = JSON.parse(new TextDecoder().decode(body));
1274
1333
  } catch {
1275
1334
  return { ok: false, reason: "malformed" };
1276
1335
  }
@@ -2809,6 +2868,7 @@ export {
2809
2868
  DeliveredResult,
2810
2869
  ENCRYPTION_KEY_CONTEXT,
2811
2870
  ENDPOINTS,
2871
+ ENVELOPE_BODY_VERSION,
2812
2872
  ENVELOPE_MAX_AGE_MS,
2813
2873
  ERROR_STATUS,
2814
2874
  EnvelopeDirection,
@@ -2896,10 +2956,14 @@ export {
2896
2956
  consoleOrder,
2897
2957
  cryptoReady,
2898
2958
  declaredVersion,
2959
+ decodeEnvelopeInner,
2899
2960
  describeBytes,
2900
2961
  effectiveOfferScope,
2962
+ encodeEnvelopeInner,
2901
2963
  envelopeBytes,
2964
+ envelopeSignedBody,
2902
2965
  fingerprint,
2966
+ fromBase64Url,
2903
2967
  generateKeys,
2904
2968
  grantStatement,
2905
2969
  isBackendId,
@@ -2928,6 +2992,7 @@ export {
2928
2992
  sizeClassCeiling,
2929
2993
  sizeClassOf,
2930
2994
  successionStatement,
2995
+ toBase64Url,
2931
2996
  tooLargeMessage,
2932
2997
  updateOfferFor,
2933
2998
  verifyGrant,