";\nresult = [a, b, `),
+ ).toBe(true);
+ });
+
+ it("maps rewritten positions back to the source", () => {
+ const source = `const x = 1;\nresult =
{x}
;\nconst y = 2;\n`;
+ const sf = parseSourceFile("/case.tsx", source, "tsx");
+ const lowered = lowerJsx(sf, { runtime: "classic", factory: "h", fragment: "Fragment" });
+ const after = lowered.text.indexOf("const y");
+ expect(lowered.originalPosition(after)).toBe(source.indexOf("const y"));
+ const copied = lowered.text.indexOf("{ a: x }") + "{ a: ".length;
+ expect(lowered.originalPosition(copied)).toBe(source.indexOf("{x}>") + 1);
+ expect(lowered.originalPosition(lowered.text.indexOf("h("))).toBe(source.indexOf("
+ list
+ .split(",")
+ .map((item) => {
+ const [name, local] = item.trim().split(/\s+as\s+/);
+ return `const ${local} = runtime.${name};`;
+ })
+ .join(" "),
+ )
+ .replace(/(^|\s)export /gm, "$1");
+ const record =
+ (kind: string) =>
+ (...args: unknown[]): unknown => ({ kind, args: args.map(named) });
+ const runtime = {
+ jsx: record("jsx"),
+ jsxs: record("jsxs"),
+ jsxDEV: record("jsxDEV"),
+ createElement: record("createElement"),
+ Fragment: "#fragment",
+ };
+ const run = new Function("runtime", `"use strict"; let result; ${body}; return result;`) as (
+ runtime: unknown,
+ ) => unknown;
+ return run(runtime);
+}
+
+/** Functions compare by name: each evaluation declares its own. */
+function named(value: unknown): unknown {
+ if (typeof value === "function") return `fn:${value.name}`;
+ if (Array.isArray(value)) return value.map(named);
+ if (value !== null && typeof value === "object")
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, named(item)]));
+ return value;
+}
+
+function typescriptAutomatic(source: string, development: boolean): string {
+ return ts5.transpileModule(source, {
+ fileName: "/case.tsx",
+ compilerOptions: {
+ target: ts5.ScriptTarget.ES2022,
+ module: ts5.ModuleKind.ESNext,
+ jsx: development ? ts5.JsxEmit.ReactJSXDev : ts5.JsxEmit.ReactJSX,
+ },
+ }).outputText;
+}
+
+function rewriteAutomatic(source: string, development: boolean): string {
+ const sf = parseSourceFile("/case.tsx", source, "tsx");
+ const plan = jsxEmitPlan({ jsx: development ? "react-jsxdev" : "react-jsx" }, jsxPragmas(source));
+ return lowerJsx(sf, plan).text;
+}
+
+const automaticCases: Record
= {
+ ...cases,
+ "keys": `const p = { id: 1 };
+result = [x, , , , ];`,
+ "children shapes": `const kids = [1, 2];
+result = [, {}
,
+
, one
, {1}{2}
, {...kids}
,
, <>>, <>>];`,
+ "names in use": `const _jsx = "taken"; const _jsxs = 1;
+result = [{_jsx}
, {_jsxs}{_jsx}
];`,
+};
+
+describe("lowerJsx with the automatic runtime", () => {
+ for (const [name, source] of Object.entries(automaticCases)) {
+ for (const development of [false, true]) {
+ it(`matches TypeScript's ${development ? "react-jsxdev" : "react-jsx"} emit: ${name}`, () => {
+ const lowered = rewriteAutomatic(`export {};\n${source}`, development);
+ const expected = typescriptAutomatic(`export {};\n${source}`, development);
+ expect(evaluateAutomatic(lowered)).toEqual(evaluateAutomatic(expected));
+ expect(lowered.split("\n").length).toBe(source.split("\n").length + 1);
+ });
+ }
+ }
+
+ it("imports the runtime functions on the first statement's line", () => {
+ const lowered = rewriteAutomatic(
+ `"use strict";\nexport const a = <>>;\n`,
+ false,
+ );
+ expect(lowered).toBe(
+ `"use strict";import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime"; \nexport const a = _jsx(_Fragment, { children: _jsx("b", {}, "k") });\n`,
+ );
+ });
+
+ it("reads @jsxImportSource and @jsxRuntime", () => {
+ const pragma = `/** @jsxImportSource preact */\nexport const a = ;\n`;
+ expect(rewriteAutomatic(pragma, false)).toContain(`from "preact/jsx-runtime"`);
+ const classic = `/** @jsxRuntime classic */\n/** @jsx h */\nexport const a = ;\n`;
+ expect(rewriteAutomatic(classic, false)).toContain(`h("b", null)`);
+ });
+});
+
+describe("JSX text", () => {
+ it("trims lines as TypeScript does", () => {
+ expect(jsxTextValue(" a \n b \n ")).toBe(" a b");
+ expect(jsxTextValue("\n \n")).toBeUndefined();
+ expect(jsxTextValue(" ")).toBe(" ");
+ });
+
+ it("decodes entities", () => {
+ expect(decodeEntities("<>& AB&bogus;")).toBe(
+ "<>& AB&bogus;",
+ );
+ });
+});
+
+describe("jsxPragmas", () => {
+ it("reads leading multi-line comments, first @jsx and last @jsxImportSource", () => {
+ expect(
+ jsxPragmas(
+ "#!/usr/bin/env node\n/** @jsx h\n * @jsx g */\n// @jsxFrag F\n/* @jsxImportSource a */ /* @jsxImportSource b */\nconst x = 1; /** @jsx late */",
+ ),
+ ).toEqual({ jsx: "h", jsxImportSource: "b" });
+ });
+});
diff --git a/packages/compiler/src/frontend/jsx-source.ts b/packages/compiler/src/frontend/jsx-source.ts
new file mode 100644
index 000000000..915a5d16f
--- /dev/null
+++ b/packages/compiler/src/frontend/jsx-source.ts
@@ -0,0 +1,856 @@
+/* JSX lowering at the source level, before IR lowering.
+ *
+ * A program that contains JSX is checked as written: the checker types JSX
+ * with the project's jsx settings and reports the errors tsc reports. Each
+ * file that contains JSX is then rewritten with every JSX expression
+ * replaced by the calls TypeScript emits for it, and the rewritten program is
+ * what the lowering compiles, through the same import, call and object
+ * literal lowering as ordinary code.
+ *
+ * The rewrite copies every byte outside JSX expressions, and copies the
+ * expressions inside them (attribute values, children, spreads) with their
+ * own JSX rewritten. It keeps every line break of a JSX expression, between
+ * the arguments it emits, so a position keeps its line in the rewritten
+ * text. */
+
+import * as ts from "./ts7/adapter.js";
+import { jsxOptionName } from "./ts7/enums.js";
+import { JSX_ENTITIES } from "./jsx-entities.js";
+
+/** The project's jsx compiler options, as the tsconfig spells them. */
+export interface JsxOptions {
+ jsx?: string | number | undefined;
+ jsxFactory?: string | undefined;
+ jsxFragmentFactory?: string | undefined;
+ jsxImportSource?: string | undefined;
+ reactNamespace?: string | undefined;
+}
+
+/** The tsconfig name of a `jsx` option value (TS7 options carry JsxEmit
+ * numbers, parsed tsconfigs may carry the name). */
+export function jsxEmitName(value: string | number | undefined): string | undefined {
+ if (typeof value === "number") return jsxOptionName(value);
+ return value?.toLowerCase();
+}
+
+/** The `@jsx`, `@jsxFrag`, `@jsxImportSource` and `@jsxRuntime` pragmas of
+ * a file's leading comments, as TypeScript reads them: multi-line comments
+ * only, the first `@jsx` and `@jsxFrag` and the last `@jsxImportSource` and
+ * `@jsxRuntime`. */
+export interface JsxPragmas {
+ jsx?: string;
+ jsxFrag?: string;
+ jsxImportSource?: string;
+ jsxRuntime?: string;
+}
+
+export function jsxPragmas(text: string): JsxPragmas {
+ const pragmas: JsxPragmas = {};
+ for (const comment of leadingMultiLineComments(text)) {
+ const pattern = /@(\S+)(\s+(?:\S.*)?)?$/gm;
+ for (let match = pattern.exec(comment); match !== null; match = pattern.exec(comment)) {
+ const argument = match[2]?.trim().split(/\s+/)[0];
+ if (!argument) continue;
+ switch (match[1]!.toLowerCase()) {
+ case "jsx":
+ pragmas.jsx ??= argument;
+ break;
+ case "jsxfrag":
+ pragmas.jsxFrag ??= argument;
+ break;
+ case "jsximportsource":
+ pragmas.jsxImportSource = argument;
+ break;
+ case "jsxruntime":
+ pragmas.jsxRuntime = argument;
+ break;
+ }
+ }
+ }
+ return pragmas;
+}
+
+/** The `/* ... *\/` comments before a file's first token (after a shebang). */
+function leadingMultiLineComments(text: string): string[] {
+ const comments: string[] = [];
+ let i = text.startsWith("#!") ? text.indexOf("\n") : 0;
+ if (i < 0) return comments;
+ while (i < text.length) {
+ const ch = text.charCodeAt(i);
+ if (isLineBreak(ch) || isWhiteSpaceSingleLine(ch)) {
+ i++;
+ } else if (text.startsWith("//", i)) {
+ while (i < text.length && !isLineBreak(text.charCodeAt(i))) i++;
+ } else if (text.startsWith("/*", i)) {
+ const end = text.indexOf("*/", i + 2);
+ const close = end < 0 ? text.length : end + 2;
+ comments.push(text.slice(i, close));
+ i = close;
+ } else {
+ break;
+ }
+ }
+ return comments;
+}
+
+function isLineBreak(ch: number): boolean {
+ return ch === 10 || ch === 13 || ch === 0x2028 || ch === 0x2029;
+}
+
+function isWhiteSpaceSingleLine(ch: number): boolean {
+ return (
+ ch === 32 ||
+ ch === 9 ||
+ ch === 11 ||
+ ch === 12 ||
+ ch === 160 ||
+ ch === 133 ||
+ ch === 5760 ||
+ (ch >= 8192 && ch <= 8203) ||
+ ch === 8239 ||
+ ch === 8287 ||
+ ch === 12288 ||
+ ch === 65279
+ );
+}
+
+/** How a file's JSX is emitted, after its pragmas. */
+export type JsxEmitPlan =
+ | {
+ runtime: "classic";
+ /** The factory call (`h`, `React.createElement`). */
+ factory: string;
+ /** The fragment tag (`Fragment`, `React.Fragment`). */
+ fragment: string;
+ }
+ | {
+ runtime: "automatic";
+ /** The module `jsx-runtime` (or `jsx-dev-runtime`) is under, and that
+ * a `createElement` fallback imports from (`react`). */
+ importSource: string;
+ /** jsx: react-jsxdev: `jsxDEV` calls with source locations. */
+ development: boolean;
+ };
+
+/** The plan of a file: the automatic runtime with jsx: react-jsx or
+ * react-jsxdev, an import source or an `@jsxRuntime automatic` pragma (an
+ * `@jsxRuntime classic` pragma overrides all of them), the classic runtime
+ * otherwise. */
+export function jsxEmitPlan(options: JsxOptions, pragmas: JsxPragmas): JsxEmitPlan {
+ const mode = jsxEmitName(options.jsx);
+ const automatic =
+ pragmas.jsxRuntime !== "classic" &&
+ (mode === "react-jsx" ||
+ mode === "react-jsxdev" ||
+ options.jsxImportSource !== undefined ||
+ pragmas.jsxImportSource !== undefined ||
+ pragmas.jsxRuntime === "automatic");
+ if (automatic) {
+ return {
+ runtime: "automatic",
+ importSource: pragmas.jsxImportSource ?? options.jsxImportSource ?? "react",
+ development: mode === "react-jsxdev",
+ };
+ }
+ const namespace = options.reactNamespace ?? "React";
+ return {
+ runtime: "classic",
+ factory: pragmas.jsx ?? options.jsxFactory ?? `${namespace}.createElement`,
+ fragment: pragmas.jsxFrag ?? options.jsxFragmentFactory ?? `${namespace}.Fragment`,
+ };
+}
+
+/** True when the file contains a JSX expression. */
+export function containsJsx(sf: ts.SourceFile): boolean {
+ let found = false;
+ const visit = (node: ts.Node): void => {
+ if (found) return;
+ if (isJsxExpressionRoot(node)) {
+ found = true;
+ return;
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(sf);
+ return found;
+}
+
+/** The first JSX expression of the file (for diagnostics), or null. */
+export function firstJsx(sf: ts.SourceFile): ts.Node | null {
+ let found: ts.Node | null = null;
+ const visit = (node: ts.Node): void => {
+ if (found !== null) return;
+ if (isJsxExpressionRoot(node)) {
+ found = node;
+ return;
+ }
+ ts.forEachChild(node, visit);
+ };
+ visit(sf);
+ return found;
+}
+
+type JsxRoot = ts.JsxElement | ts.JsxSelfClosingElement | ts.JsxFragment;
+
+function isJsxExpressionRoot(node: ts.Node): node is JsxRoot {
+ return ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node);
+}
+
+/** A file's text with its JSX lowered, and the way back from a position of
+ * that text to the source position it stands for. */
+export interface LoweredJsx {
+ text: string;
+ /** A rewritten-text offset as an offset of the original text: copied text
+ * keeps its offset; emitted call text maps to the JSX it stands for. */
+ originalPosition(pos: number): number;
+ /** An original-text offset in the rewritten text: copied text keeps its
+ * offset; a position inside replaced JSX maps to the call text emitted
+ * for the JSX at or before it (its end, for the `end` of a span). */
+ rewrittenPosition(pos: number, end: boolean): number;
+}
+
+/** The file's text with its JSX replaced by the calls TypeScript emits for
+ * the plan, and for the automatic runtime the imports of the runtime
+ * functions those calls use. */
+export function lowerJsx(sf: ts.SourceFile, plan: JsxEmitPlan): LoweredJsx {
+ return new JsxRewriter(sf, plan).file();
+}
+
+/** A run of rewritten text: `copied` text starts at `original`; emitted
+ * text stands for the source position `original`. */
+interface Segment {
+ rewritten: number;
+ original: number;
+ /** The copied length, 0 for emitted text. */
+ copied: number;
+}
+
+class JsxRewriter {
+ private readonly text: string;
+ /** Output pieces; `cursor` is the source position whose line breaks the
+ * output has reached. */
+ private readonly pieces: string[] = [];
+ private readonly segments: Segment[] = [];
+ private length = 0;
+ private cursor = 0;
+
+ constructor(
+ private readonly sf: ts.SourceFile,
+ private readonly plan: JsxEmitPlan,
+ ) {
+ this.text = sf.text;
+ }
+
+ file(): LoweredJsx {
+ const insertAt = this.plan.runtime === "automatic" ? this.prologueEnd() : -1;
+ this.copy(0, this.text.length);
+ if (insertAt >= 0) this.insertImports(insertAt);
+ const segments = this.segments;
+ const total = this.length;
+ return {
+ text: this.pieces.join(""),
+ originalPosition: (pos: number): number => {
+ let low = 0;
+ let high = segments.length - 1;
+ while (low < high) {
+ const mid = (low + high + 1) >> 1;
+ if (segments[mid]!.rewritten <= pos) low = mid;
+ else high = mid - 1;
+ }
+ const segment = segments[low];
+ if (segment === undefined) return pos;
+ return segment.copied > 0
+ ? segment.original + Math.min(pos - segment.rewritten, segment.copied)
+ : segment.original;
+ },
+ rewrittenPosition: (pos: number, end: boolean): number => {
+ // Segments are in source order: the last one starting at or before
+ // `pos` holds it, or is the emitted text for the JSX holding it.
+ let low = 0;
+ let high = segments.length - 1;
+ while (low < high) {
+ const mid = (low + high + 1) >> 1;
+ if (segments[mid]!.original <= pos) low = mid;
+ else high = mid - 1;
+ }
+ const segment = segments[low];
+ if (segment === undefined) return pos;
+ if (segment.copied > 0)
+ return segment.rewritten + Math.min(pos - segment.original, segment.copied);
+ return end ? (segments[low + 1]?.rewritten ?? total) : segment.rewritten;
+ },
+ };
+ }
+
+ /** Local names of the automatic runtime's functions, by module and
+ * export, in the order of first use (TypeScript's implicit imports). */
+ private readonly imports = new Map>();
+ private names: Set | null = null;
+ private fileNameLocal: string | null = null;
+
+ /** The local name bound to `name` of `module`: `_name`, or `_name_1`,
+ * `_name_2`, ... past an identifier the file already uses. */
+ private importName(name: string, module: string): string {
+ let names = this.imports.get(module);
+ if (names === undefined) {
+ names = new Map();
+ this.imports.set(module, names);
+ }
+ let local = names.get(name);
+ if (local === undefined) {
+ local = this.uniqueName(`_${name}`);
+ names.set(name, local);
+ }
+ return local;
+ }
+
+ private uniqueName(base: string): string {
+ if (this.names === null) {
+ const names = new Set();
+ const visit = (node: ts.Node): void => {
+ if (ts.isIdentifier(node)) names.add(node.text);
+ ts.forEachChild(node, visit);
+ };
+ visit(this.sf);
+ this.names = names;
+ }
+ let name = base;
+ for (let i = 1; this.names.has(name); i++) name = `${base}_${i}`;
+ this.names.add(name);
+ return name;
+ }
+
+ /** Where the runtime imports go: after the leading "use strict"-style
+ * directives, else before the first statement, so the inserted text
+ * shares that statement's line. */
+ private prologueEnd(): number {
+ const statements = this.sf.statements.nodes;
+ let end = -1;
+ for (const statement of statements) {
+ if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) break;
+ end = statement.end;
+ }
+ if (end >= 0) return end;
+ const first = statements[0];
+ return first === undefined ? this.text.length : first.getStart(this.sf);
+ }
+
+ /** Inserts the imports (or, in a CommonJS file, the requires) of the
+ * runtime functions the file's JSX used, and react-jsxdev's file name
+ * constant, at `at`: imports in the reverse order of their modules' first
+ * use, as TypeScript inserts each after the prologue. */
+ private insertImports(at: number): void {
+ if (this.imports.size === 0 && this.fileNameLocal === null) return;
+ const esm = ts.isExternalModule(this.sf);
+ const parts: string[] = [];
+ for (const [module, names] of [...this.imports].reverse()) {
+ const specifier = JSON.stringify(module);
+ if (esm) {
+ const list = [...names].map(([name, local]) => `${name} as ${local}`).join(", ");
+ parts.push(`import { ${list} } from ${specifier};`);
+ } else if (/\.[cm]?jsx?$/.test(this.sf.fileName)) {
+ const list = [...names].map(([name, local]) => `${name}: ${local}`).join(", ");
+ parts.push(`const { ${list} } = require(${specifier});`);
+ }
+ }
+ if (this.fileNameLocal !== null)
+ parts.push(`const ${this.fileNameLocal} = ${JSON.stringify(this.sf.fileName)};`);
+ if (parts.length === 0) return;
+ const text = `${parts.join(" ")} `;
+ let index = 0;
+ let rewritten = 0;
+ for (; index < this.segments.length; index++) {
+ const segment = this.segments[index]!;
+ if (segment.copied > 0 && segment.original <= at && at <= segment.original + segment.copied) {
+ rewritten = segment.rewritten + (at - segment.original);
+ break;
+ }
+ }
+ // Split the copied segment holding `at` and shift what follows.
+ const holder = this.segments[index];
+ const joined = this.pieces.join("");
+ this.pieces.length = 0;
+ this.pieces.push(joined.slice(0, rewritten), text, joined.slice(rewritten));
+ const following: Segment[] = [{ rewritten, original: at, copied: 0 }];
+ if (holder !== undefined && holder.copied > 0) {
+ const head = at - holder.original;
+ const tail = holder.copied - head;
+ holder.copied = head;
+ if (tail > 0)
+ following.push({ rewritten: rewritten + text.length, original: at, copied: tail });
+ }
+ for (let i = index + 1; i < this.segments.length; i++)
+ this.segments[i]!.rewritten += text.length;
+ this.segments.splice(index + 1, 0, ...following);
+ if (holder !== undefined && holder.copied === 0) this.segments.splice(index, 1);
+ this.length += text.length;
+ }
+
+ /** Emits source text [start, end) unchanged. */
+ private copied(start: number, end: number): void {
+ if (end <= start) return;
+ this.segments.push({ rewritten: this.length, original: start, copied: end - start });
+ this.pieces.push(this.text.slice(start, end));
+ this.length += end - start;
+ }
+
+ /** Emits call text standing for the source position the cursor reached. */
+ private emit(...texts: string[]): void {
+ for (const text of texts) {
+ if (text.length === 0) continue;
+ const last = this.segments[this.segments.length - 1];
+ if (last === undefined || last.copied > 0 || last.original !== this.cursor)
+ this.segments.push({ rewritten: this.length, original: this.cursor, copied: 0 });
+ this.pieces.push(text);
+ this.length += text.length;
+ }
+ }
+
+ /** Copies the source range [start, end), with its JSX expressions
+ * replaced. */
+ private copy(start: number, end: number): void {
+ let at = start;
+ const visit = (node: ts.Node): void => {
+ if (node.end <= at || (node !== this.sf && node.getStart(this.sf) >= end)) return;
+ if (isJsxExpressionRoot(node)) {
+ const nodeStart = node.getStart(this.sf);
+ this.copied(at, nodeStart);
+ if (nodeStart > this.cursor) this.cursor = nodeStart;
+ this.element(node);
+ this.sync(node.end);
+ at = node.end;
+ return;
+ }
+ ts.forEachChild(node, visit);
+ };
+ // The cursor never moves back: a range copied after source that follows
+ // it (jsx()'s key argument, after the children) emits no line break
+ // twice.
+ if (start > this.cursor) this.cursor = start;
+ visit(this.enclosing(start, end));
+ this.copied(at, end);
+ if (end > this.cursor) this.cursor = end;
+ }
+
+ /** The smallest node around [start, end) whose children cover it. */
+ private enclosing(start: number, end: number): ts.Node {
+ let node: ts.Node = this.sf;
+ for (;;) {
+ let next: ts.Node | undefined;
+ ts.forEachChild(node, (child) => {
+ if (next === undefined && child.pos <= start && child.end >= end) next = child;
+ });
+ if (next === undefined || next === node) return node;
+ node = next;
+ }
+ }
+
+ /** Emits the line breaks of the source between the cursor and `pos`. */
+ private sync(pos: number): void {
+ for (let i = this.cursor; i < pos; i++) {
+ const ch = this.text.charCodeAt(i);
+ if (ch === 13 && this.text.charCodeAt(i + 1) === 10) {
+ this.cursor = i;
+ this.emit("\r\n");
+ i++;
+ } else if (isLineBreak(ch)) {
+ this.cursor = i;
+ this.emit(this.text[i]!);
+ }
+ }
+ if (pos > this.cursor) this.cursor = pos;
+ }
+
+ /** Copies an expression in a JSX expression's place. */
+ private expression(node: ts.Node): void {
+ const start = node.getStart(this.sf);
+ this.sync(start);
+ this.copy(start, node.end);
+ }
+
+ private element(node: JsxRoot): void {
+ const plan = this.plan;
+ if (plan.runtime === "classic") {
+ this.createElement(node, plan.factory, plan.fragment);
+ return;
+ }
+ const opening = ts.isJsxFragment(node)
+ ? null
+ : ts.isJsxElement(node)
+ ? node.openingElement
+ : node;
+ if (opening !== null && hasKeyAfterPropsSpread(opening.attributes)) {
+ // A key after a spread of props is not jsx()'s key argument: the
+ // element is the import source's createElement call.
+ this.createElement(node, this.importName("createElement", plan.importSource), "");
+ return;
+ }
+ this.automaticElement(node, opening, plan.importSource, plan.development);
+ }
+
+ /** The classic-runtime call: `factory(tag, props or null, ...children)`. */
+ private createElement(node: JsxRoot, factory: string, fragment: string): void {
+ const children = ts.isJsxSelfClosingElement(node) ? [] : node.children.nodes;
+ if (ts.isJsxFragment(node)) {
+ this.emit(`${factory}(${fragment}, null`);
+ } else {
+ const opening = ts.isJsxElement(node) ? node.openingElement : node;
+ this.emit(`${factory}(`);
+ this.tagName(opening.tagName);
+ this.emit(", ");
+ this.attributes(opening.attributes.properties.nodes, null);
+ }
+ for (const child of children) {
+ if (ts.isJsxText(child)) {
+ const value = jsxTextValue(child.text);
+ if (value === undefined) continue;
+ this.emit(", ");
+ this.sync(child.getStart(this.sf));
+ this.emit(JSON.stringify(value));
+ } else if (ts.isJsxExpression(child)) {
+ if (child.expression === undefined) continue;
+ this.emit(", ");
+ if (child.dotDotDotToken !== undefined) this.emit("...");
+ this.expression(child.expression);
+ } else if (isJsxExpressionRoot(child)) {
+ this.emit(", ");
+ this.sync(child.getStart(this.sf));
+ this.element(child);
+ }
+ this.sync(child.end);
+ }
+ this.sync(node.end);
+ this.emit(")");
+ }
+
+ /** The automatic-runtime call: `jsx(tag, props[, key])` with the
+ * children in props, `jsxs` for more than one child or a spread child, and
+ * react-jsxdev's `jsxDEV(tag, props, key, isStatic, source, self)`. */
+ private automaticElement(
+ node: JsxRoot,
+ opening: ts.JsxOpeningElement | ts.JsxSelfClosingElement | null,
+ importSource: string,
+ development: boolean,
+ ): void {
+ const children: readonly ts.JsxChild[] = ts.isJsxSelfClosingElement(node)
+ ? []
+ : node.children.nodes;
+ const semantic = children.filter(isSemanticJsxChild);
+ const first = semantic[0];
+ const isStatic =
+ semantic.length > 1 ||
+ (first !== undefined && ts.isJsxExpression(first) && first.dotDotDotToken !== undefined);
+ const module = `${importSource}/${development ? "jsx-dev-runtime" : "jsx-runtime"}`;
+ this.emit(`${this.importName(development ? "jsxDEV" : isStatic ? "jsxs" : "jsx", module)}(`);
+ // TypeScript's children prop: the one semantic child itself, else an
+ // array of every child that emits a value.
+ const single = semantic.length === 1 && !isStatic ? first! : null;
+ const listed = single === null ? children.filter((child) => jsxChildEmits(child)) : [];
+ const childrenProp =
+ children.length > 0 && (single !== null ? jsxChildEmits(single) : listed.length > 0)
+ ? (): void => {
+ this.emit("children: ");
+ if (single !== null) {
+ this.child(single);
+ } else {
+ this.emit("[");
+ listed.forEach((child, index) => {
+ if (index > 0) this.emit(", ");
+ this.child(child);
+ this.sync(child.end);
+ });
+ this.emit("]");
+ }
+ }
+ : null;
+ let key: ts.JsxAttribute | undefined;
+ if (opening === null) {
+ this.emit(this.importName("Fragment", module), ", ");
+ if (childrenProp === null) this.emit("{}");
+ else this.attributes([], childrenProp);
+ } else {
+ this.tagName(opening.tagName);
+ this.emit(", ");
+ const properties = opening.attributes.properties.nodes;
+ key = properties.find(
+ (property): property is ts.JsxAttribute =>
+ ts.isJsxAttribute(property) &&
+ ts.isIdentifier(property.name) &&
+ property.name.text === "key",
+ );
+ const attributes = properties.filter((property) => property !== key);
+ if (attributes.length === 0 && childrenProp === null) this.emit("{}");
+ else this.attributes(attributes, childrenProp);
+ }
+ if (key !== undefined) {
+ this.emit(", ");
+ this.attributeValue(key.initializer);
+ }
+ if (development) {
+ if (key === undefined) this.emit(", void 0");
+ const { line, column } = lineAndColumn(this.text, node.pos);
+ this.fileNameLocal ??= this.uniqueName("_jsxFileName");
+ this.emit(
+ `, ${isStatic}, { fileName: ${this.fileNameLocal}, lineNumber: ${line}, columnNumber: ${column} }, `,
+ jsxSelf(node),
+ );
+ }
+ this.sync(node.end);
+ this.emit(")");
+ }
+
+ /** A child's value in the children prop (it emits one: jsxChildEmits). */
+ private child(child: ts.JsxChild): void {
+ if (ts.isJsxText(child)) {
+ this.sync(child.getStart(this.sf));
+ this.emit(JSON.stringify(jsxTextValue(child.text)!));
+ } else if (ts.isJsxExpression(child)) {
+ if (child.dotDotDotToken !== undefined) this.emit("...");
+ this.expression(child.expression!);
+ } else {
+ this.sync(child.getStart(this.sf));
+ this.element(child as JsxRoot);
+ }
+ }
+
+ private tagName(tag: ts.JsxTagNameExpression): void {
+ this.sync(tag.getStart(this.sf));
+ if (ts.isIdentifier(tag) && isIntrinsicJsxName(tag.text)) {
+ this.emit(JSON.stringify(tag.text));
+ } else if (ts.isJsxNamespacedName(tag)) {
+ this.emit(JSON.stringify(`${tag.namespace.text}:${tag.name.text}`));
+ } else {
+ this.emit(this.text.slice(tag.getStart(this.sf), tag.end));
+ }
+ this.cursor = tag.end;
+ }
+
+ /** The props object of `properties`, then `children` (the automatic
+ * runtime's children prop), or `null` for no properties and no children
+ * prop. */
+ private attributes(
+ properties: readonly ts.JsxAttributeLike[],
+ children: (() => void) | null,
+ ): void {
+ if (properties.length === 0 && children === null) {
+ this.emit("null");
+ return;
+ }
+ if (properties.length === 0) {
+ this.emit("{ ");
+ children!();
+ this.emit(" }");
+ return;
+ }
+ this.emit("{ ");
+ let first = true;
+ const separate = (): void => {
+ if (!first) this.emit(", ");
+ first = false;
+ };
+ for (const attribute of properties) {
+ if (ts.isJsxSpreadAttribute(attribute)) {
+ const value = attribute.expression;
+ if (ts.isObjectLiteralExpression(value) && !hasProtoProperty(value)) {
+ // TypeScript inlines the properties of a spread object literal.
+ for (const property of value.properties.nodes) {
+ separate();
+ this.expression(property);
+ }
+ } else {
+ separate();
+ this.emit("...");
+ this.expression(value);
+ }
+ } else {
+ separate();
+ this.sync(attribute.getStart(this.sf));
+ this.emit(attributeName(attribute.name), ": ");
+ this.attributeValue(attribute.initializer);
+ }
+ this.sync(attribute.end);
+ }
+ if (children !== null) {
+ this.emit(", ");
+ children();
+ }
+ this.emit(" }");
+ }
+
+ private attributeValue(value: ts.JsxAttributeValue | undefined): void {
+ if (value === undefined) {
+ this.emit("true");
+ } else if (ts.isStringLiteral(value)) {
+ this.sync(value.getStart(this.sf));
+ this.emit(JSON.stringify(decodeEntities(value.text)));
+ } else if (ts.isJsxExpression(value)) {
+ if (value.expression === undefined) this.emit("true");
+ else this.expression(value.expression);
+ } else {
+ this.sync(value.getStart(this.sf));
+ this.element(value as JsxRoot);
+ }
+ }
+}
+
+/** A child TypeScript counts: an expression container with an expression,
+ * text that is not only white space spanning lines, or an element. */
+function isSemanticJsxChild(child: ts.JsxChild): boolean {
+ if (ts.isJsxExpression(child)) return child.expression !== undefined;
+ if (ts.isJsxText(child)) return !child.containsOnlyTriviaWhiteSpaces;
+ return true;
+}
+
+/** True when the child emits a value (text that trims to nothing and empty
+ * expression containers emit none). */
+function jsxChildEmits(child: ts.JsxChild): boolean {
+ if (ts.isJsxText(child)) return jsxTextValue(child.text) !== undefined;
+ if (ts.isJsxExpression(child)) return child.expression !== undefined;
+ return true;
+}
+
+/** A `key` attribute after a spread that is not an object literal free of
+ * spreads: TypeScript emits createElement for such an element. */
+function hasKeyAfterPropsSpread(attributes: ts.JsxAttributes): boolean {
+ let spread = false;
+ for (const property of attributes.properties.nodes) {
+ if (
+ ts.isJsxSpreadAttribute(property) &&
+ (!ts.isObjectLiteralExpression(property.expression) ||
+ property.expression.properties.nodes.some((p: ts.ObjectLiteralElementLike) =>
+ ts.isSpreadAssignment(p),
+ ))
+ ) {
+ spread = true;
+ } else if (
+ spread &&
+ ts.isJsxAttribute(property) &&
+ ts.isIdentifier(property.name) &&
+ property.name.text === "key"
+ ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/** The 1-based line and column of `pos`, counting the line breaks
+ * TypeScript counts (\r\n, \n, \r, U+2028, U+2029) and UTF-16 units. */
+function lineAndColumn(text: string, pos: number): { line: number; column: number } {
+ let line = 1;
+ let start = 0;
+ for (let i = 0; i < pos; i++) {
+ const ch = text.charCodeAt(i);
+ if (ch === 13 && text.charCodeAt(i + 1) === 10) continue;
+ if (isLineBreak(ch)) {
+ line++;
+ start = i + 1;
+ }
+ }
+ return { line, column: pos - start + 1 };
+}
+
+/** react-jsxdev's `self` argument. TypeScript passes `this`; where `this`
+ * has no declared type (the top level of an ES module and a function that is
+ * not a method and declares no `this` parameter) the argument is the
+ * `undefined` such code observes when called without a receiver, since an
+ * untyped `this` does not type-check under noImplicitThis. */
+function jsxSelf(node: ts.Node): string {
+ for (let at = node.parent; at !== undefined; at = at.parent) {
+ if (ts.isArrowFunction(at)) continue;
+ if (
+ ts.isMethodDeclaration(at) ||
+ ts.isConstructorDeclaration(at) ||
+ ts.isGetAccessorDeclaration(at) ||
+ ts.isSetAccessorDeclaration(at) ||
+ ts.isPropertyDeclaration(at) ||
+ ts.isClassStaticBlockDeclaration(at)
+ )
+ return "this";
+ if (ts.isFunctionDeclaration(at) || ts.isFunctionExpression(at)) {
+ const declaresThis = at.parameters.nodes.some(
+ (parameter: ts.ParameterDeclaration) =>
+ ts.isIdentifier(parameter.name) && parameter.name.text === "this",
+ );
+ const objectMethod =
+ ts.isFunctionExpression(at) &&
+ at.parent !== undefined &&
+ ts.isPropertyAssignment(at.parent);
+ return declaresThis || objectMethod ? "this" : "undefined";
+ }
+ if (ts.isSourceFile(at)) return ts.isExternalModule(at) ? "undefined" : "this";
+ }
+ return "undefined";
+}
+
+/** TypeScript's intrinsic-element test: a lowercase first letter or a dash. */
+function isIntrinsicJsxName(name: string): boolean {
+ const ch = name.charCodeAt(0);
+ return (ch >= 97 && ch <= 122) || name.includes("-");
+}
+
+function attributeName(name: ts.JsxAttributeName): string {
+ if (ts.isJsxNamespacedName(name))
+ return JSON.stringify(`${name.namespace.text}:${name.name.text}`);
+ return /^[A-Z_]\w*$/i.test(name.text) ? name.text : JSON.stringify(name.text);
+}
+
+function hasProtoProperty(object: ts.ObjectLiteralExpression): boolean {
+ return object.properties.nodes.some(
+ (property: ts.ObjectLiteralElementLike) =>
+ ts.isPropertyAssignment(property) &&
+ ((ts.isIdentifier(property.name) && property.name.text === "__proto__") ||
+ (ts.isStringLiteral(property.name) && property.name.text === "__proto__")),
+ );
+}
+
+/** A JSX text child's string value: each line trimmed (the first line keeps
+ * its leading and the last line its trailing white space), lines without
+ * text dropped, the rest joined with one space and entities decoded.
+ * Undefined when nothing remains. */
+export function jsxTextValue(text: string): string | undefined {
+ let value: string | undefined;
+ let firstNonWhitespace = 0;
+ let lastNonWhitespace = -1;
+ const add = (line: string): void => {
+ const decoded = decodeEntities(line);
+ value = value === undefined ? decoded : `${value} ${decoded}`;
+ };
+ for (let i = 0; i < text.length; i++) {
+ const ch = text.charCodeAt(i);
+ if (isLineBreak(ch)) {
+ if (firstNonWhitespace !== -1 && lastNonWhitespace !== -1)
+ add(text.slice(firstNonWhitespace, lastNonWhitespace + 1));
+ firstNonWhitespace = -1;
+ } else if (!isWhiteSpaceSingleLine(ch)) {
+ lastNonWhitespace = i;
+ if (firstNonWhitespace === -1) firstNonWhitespace = i;
+ }
+ }
+ if (firstNonWhitespace !== -1) add(text.slice(firstNonWhitespace));
+ return value;
+}
+
+/** Decodes `&name;`, `ddd;` and `hh;` references. Unknown names and
+ * numbers past U+10FFFF stay as written. */
+export function decodeEntities(text: string): string {
+ return text.replace(
+ /&((#((\d+)|x([\da-fA-F]+)))|(\w+));/g,
+ (
+ match: string,
+ _all: string | undefined,
+ _number: string | undefined,
+ _digits: string | undefined,
+ decimal: string | undefined,
+ hex: string | undefined,
+ word: string | undefined,
+ ) => {
+ const code = decimal
+ ? parseInt(decimal, 10)
+ : hex
+ ? parseInt(hex, 16)
+ : JSX_ENTITIES.get(word!);
+ return code === undefined || code > 0x10ffff ? match : String.fromCodePoint(code);
+ },
+ );
+}
diff --git a/packages/compiler/src/frontend/lowering/builtins/crypto.ts b/packages/compiler/src/frontend/lowering/builtins/crypto.ts
index 93c8498ef..22ab1a5cc 100644
--- a/packages/compiler/src/frontend/lowering/builtins/crypto.ts
+++ b/packages/compiler/src/frontend/lowering/builtins/crypto.ts
@@ -696,6 +696,8 @@ export function lowerCryptoModuleCall(
loc,
};
}
+ const pki = lowerPkiCall(lowerer, expr, bi.member, args, loc);
+ if (pki) return pki;
if (bi.member === "createSecretKey") {
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement))
lowerer.noLowering(
@@ -1339,3 +1341,55 @@ export function isWebCryptoReceiver(lowerer: Lowerer, receiver: ts.Expression):
}
return false;
}
+
+/** The asymmetric-key and signature members (scr_pki.c): checked-dynamic
+ * arguments, and the result converted to the call site's type
+ * (KeyObject, Sign and Verify are checked-dynamic handles; key pairs,
+ * signatures and verdicts convert to their records, Buffers and
+ * booleans). Null for other members. */
+function lowerPkiCall(
+ lowerer: Lowerer,
+ expr: ts.CallExpression,
+ member: string,
+ args: readonly ts.Expression[],
+ loc: SrcLoc,
+): IrExpr | null {
+ const forms: Readonly> = {
+ createPrivateKey: ["crypto.createPrivateKey", 1, 1, "createPrivateKey(key)"],
+ createPublicKey: ["crypto.createPublicKey", 1, 1, "createPublicKey(key)"],
+ generateKeyPairSync: ["crypto.generateKeyPairSync", 1, 2, "generateKeyPairSync(type, options)"],
+ generateKeyPair: [
+ "crypto.generateKeyPairCb",
+ 2,
+ 3,
+ "generateKeyPair(type, [options,] callback)",
+ ],
+ sign: ["crypto.signOneshot", 3, 3, "sign(algorithm, data, key)"],
+ verify: ["crypto.verifyOneshot", 4, 4, "verify(algorithm, data, key, signature)"],
+ createSign: ["crypto.createSign", 1, 1, "createSign(algorithm)"],
+ createVerify: ["crypto.createVerify", 1, 1, "createVerify(algorithm)"],
+ };
+ const form = forms[member];
+ if (!form) return null;
+ const [fn, min, max, supported] = form;
+ if (args.length < min || args.length > max)
+ lowerer.noLowering(
+ `crypto.${member} with ${args.length} arguments`,
+ expr,
+ `${supported} is supported`,
+ );
+ // generateKeyPair(type, callback): Node reads a function in the options
+ // position as the callback.
+ const shifted = fn === "crypto.generateKeyPairCb" && args.length === 2;
+ const values: IrExpr[] = [];
+ for (let i = 0; i < max; i++) {
+ const node = shifted ? (i === 1 ? undefined : i === 2 ? args[1] : args[i]) : args[i];
+ values.push(dynArg(lowerer, node, loc));
+ }
+ if (fn === "crypto.generateKeyPairCb")
+ return { kind: "libCall", fn, args: values, type: VOID, loc };
+ const call: IrExpr = { kind: "libCall", fn, args: values, type: DYN, loc };
+ const result = lowerer.mapTypeOf(lowerer.typeOf(expr));
+ if (result === null || result.kind === "dyn") return call;
+ return lowerer.coerceInto(expr, call, result);
+}
diff --git a/packages/compiler/src/frontend/lowering/builtins/key-object.ts b/packages/compiler/src/frontend/lowering/builtins/key-object.ts
index 7e680fa20..ecb4f275a 100644
--- a/packages/compiler/src/frontend/lowering/builtins/key-object.ts
+++ b/packages/compiler/src/frontend/lowering/builtins/key-object.ts
@@ -4,10 +4,11 @@ import { locOf } from "../../program.js";
import { BOOL, DYN, type IrExpr } from "../../../ir/ir.js";
import { builtinImportOf } from "./module-bindings.js";
-/* node:crypto's secret KeyObject is a native checked-dynamic handle
- * (scr_lib.c): the KeyObject type maps to dyn, its getters and methods
- * dispatch through the handle, and these helpers lower the class's uses
- * that a dyn value cannot answer by itself. */
+/* node:crypto's KeyObject is a native checked-dynamic handle (secret keys
+ * in scr_lib.c, private and public keys in scr_pki.c), as are Sign and
+ * Verify: the types map to dyn, their getters and methods dispatch through
+ * the handle, and these helpers lower the uses that a dyn value cannot
+ * answer by itself. */
/** Whether `node` names node:crypto's KeyObject class. */
export function isKeyObjectClass(lowerer: Lowerer, node: ts.Expression): boolean {
@@ -37,15 +38,31 @@ export function lowerKeyObjectInstanceof(
}
const KEY_OBJECT_METHODS = new Set(["export", "equals", "toCryptoKey"]);
+const SIGNER_METHODS = new Set(["update", "sign", "verify"]);
-/** `key.export(...)`, `key.equals(...)` and `key.toCryptoKey(...)`: the
+/** Whether a value's checker type is node:crypto's Sign or Verify. */
+function isSignerValue(lowerer: Lowerer, node: ts.Expression): boolean {
+ const type = lowerer.typeOf(node);
+ const sym = type.getAliasSymbol() ?? type.getSymbol();
+ return (
+ !!sym &&
+ (ts.symbolName(sym) === "Sign" || ts.symbolName(sym) === "Verify") &&
+ lowerer.isStdlibSymbol(sym)
+ );
+}
+
+/** `key.export(...)`, `key.equals(...)` and `key.toCryptoKey(...)`, and
+ * the update/sign/verify methods of Sign and Verify (scr_pki.c): the
* handle answers them as methods, so they dispatch through dynInvoke
* rather than as a member read and a call. */
export function lowerKeyObjectCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null {
if (call.questionDotToken || !ts.isPropertyAccessExpression(call.expression)) return null;
const access = call.expression;
- if (access.questionDotToken || !KEY_OBJECT_METHODS.has(access.name.text)) return null;
- if (!isKeyObjectValue(lowerer, access.expression)) return null;
+ if (access.questionDotToken) return null;
+ const method = access.name.text;
+ const keyMethod = KEY_OBJECT_METHODS.has(method) && isKeyObjectValue(lowerer, access.expression);
+ if (!keyMethod && !(SIGNER_METHODS.has(method) && isSignerValue(lowerer, access.expression)))
+ return null;
if (call.arguments.nodes.some(ts.isSpreadElement)) return null;
return {
kind: "dynInvoke",
diff --git a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts
index e4f1f0955..8487e5cb7 100644
--- a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts
+++ b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts
@@ -7,8 +7,9 @@ import { stdlibGlobalNameOf } from "../surfaces.js";
import { WEBCRYPTO_DYN_TYPES } from "../../type-mapper.js";
import { isWebCryptoReceiver } from "./crypto.js";
-/* WebCrypto's secret-key slice (scr_webcrypto.c, with encrypt and
- * decrypt in scr_webcrypto_aes.c): `crypto.subtle`'s `importKey`,
+/* WebCrypto (scr_webcrypto.c, with encrypt and decrypt in
+ * scr_webcrypto_aes.c and the ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS
+ * algorithms in scr_webcrypto_pki.c): `crypto.subtle`'s `importKey`,
* `generateKey`, `exportKey`, `encrypt`, `decrypt`, `wrapKey`,
* `unwrapKey`, `deriveBits`, `deriveKey`, `sign`, `verify` and `digest`
* called on the crypto global (also through
@@ -20,8 +21,8 @@ import { isWebCryptoReceiver } from "./crypto.js";
* are handle reads. Other SubtleCrypto methods are refused by name. */
const SUBTLE_LOWERED: ReadonlyMap = new Map([
- ["importKey", "webcrypto.importKey"],
["generateKey", "webcrypto.generateKey"],
+ ["importKey", "webcrypto.importKey"],
["exportKey", "webcrypto.exportKey"],
["encrypt", "webcrypto.encrypt"],
["decrypt", "webcrypto.decrypt"],
@@ -35,7 +36,7 @@ const SUBTLE_LOWERED: ReadonlyMap = new Map([
]);
const SUBTLE_HINT =
- "the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, wrapKey and unwrapKey with AES-KW or those ciphers, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
+ "the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
/** Whether `node` is a SubtleCrypto object: `subtle` on a WebCrypto
* Crypto object (the global, globalThis.crypto or node:crypto's
diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts
index 81aa90a1f..76091b13e 100644
--- a/packages/compiler/src/frontend/lowering/class-protocols.ts
+++ b/packages/compiler/src/frontend/lowering/class-protocols.ts
@@ -101,6 +101,8 @@ export class ClassProtocols {
const sites: {
source: ts.Expression;
target: () => ts.Type | undefined;
+ /** The node whose contextual type `target` answers. */
+ contextual?: ts.Expression;
assertion?: true;
}[] = [];
/** Expressions whose values are observed as whole objects. */
@@ -122,7 +124,8 @@ export class ClassProtocols {
const callbackParams: { fn: ts.SignatureDeclaration; index: number; name: ts.Node }[] = [];
const contextual = (expr: ts.Expression) => () => this.checker.getContextualType(expr);
const flow = (expr: ts.Expression | undefined): void => {
- if (expr !== undefined) sites.push({ source: expr, target: contextual(expr) });
+ if (expr !== undefined)
+ sites.push({ source: expr, target: contextual(expr), contextual: expr });
};
const write = (target: ts.Expression): void => {
while (ts.isParenthesizedExpression(target) || ts.isNonNullExpression(target))
@@ -329,7 +332,11 @@ export class ClassProtocols {
facts.loose.push({ node: looseArguments[i]!, call: looseCalls[i]!, symbols });
}
this.facts = facts;
- const pending: { sourceType: ts.Type; target: () => ts.Type | undefined }[] = [];
+ const pending: {
+ sourceType: ts.Type;
+ target: () => ts.Type | undefined;
+ contextual?: ts.Expression;
+ }[] = [];
/** Views a value of unknown origin is asserted to and that expose a
* method of a class the view accepts (see methodViewSources). */
const methodViews = new Set();
@@ -352,7 +359,11 @@ export class ClassProtocols {
}
continue;
}
- pending.push({ sourceType, target: site.target });
+ pending.push(
+ site.contextual === undefined
+ ? { sourceType, target: site.target }
+ : { sourceType, target: site.target, contextual: site.contextual },
+ );
}
for (const { fn, index, name } of callbackParams) {
attempt(() => {
@@ -377,6 +388,19 @@ export class ClassProtocols {
// flows and origins are complete.
const before = new Set(marked);
const targets = new Map();
+ // The fixpoint asks the target of each site whose source carries
+ // identity: one batch asks those of the sites that carry it now, and a
+ // site that starts to as marks grow asks its own.
+ attempt(() =>
+ this.checker.prefetchContextualTypes(
+ pending.flatMap((entry) =>
+ entry.contextual !== undefined &&
+ attempt(() => this.carriesIdentity(entry.sourceType, marked)) === true
+ ? [entry.contextual]
+ : [],
+ ),
+ ),
+ );
let changed = true;
while (changed) {
changed = false;
diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts
index 2e3dd6140..786f3a337 100644
--- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts
+++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts
@@ -985,11 +985,41 @@ export function runtimeOptionalReceiverRead(
loc,
};
}
+ // An array hole in a slot with more than two arms fails the member
+ // access with Node's TypeError before the checked extraction runs.
+ const holeGuarded = (extracted: IrExpr): IrExpr => {
+ if (local.type.kind !== "union") return extracted;
+ const holeTag = lowerer.armTag(local.type.unionId, UNDEFINED_T);
+ if (holeTag < 0) return extracted;
+ return {
+ kind: "ternary",
+ cond: {
+ kind: "unionIsTag",
+ unionId: local.type.unionId,
+ tag: holeTag,
+ negated: false,
+ value: varRef(local.id, local.type, loc),
+ type: BOOL,
+ loc,
+ },
+ then: nodeThrowExpr(
+ 1,
+ "",
+ runtimeOptionalReceiverMessage(expr, property),
+ extracted.type,
+ loc,
+ ),
+ else_: extracted,
+ type: extracted.type,
+ loc,
+ };
+ };
if (narrowed.kind === "object") {
const view = checkedClassAssertion(lowerer, varRef(local.id, local.type, loc), narrowed, loc);
// Preserve the member-specific missing-value error on the two-arm
// path below; mixed storage also needs to validate its concrete class.
- if (view && (lowerer.unions.get(local.type.unionId)?.arms.length ?? 0) > 2) return view;
+ if (view && (lowerer.unions.get(local.type.unionId)?.arms.length ?? 0) > 2)
+ return holeGuarded(view);
}
if (narrowed.kind === "record" && lowerer.armTag(local.type.unionId, narrowed) < 0) {
const hosted = hostedStorageView(lowerer, varRef(local.id, local.type, loc), narrowed, loc);
@@ -1014,7 +1044,9 @@ export function runtimeOptionalReceiverRead(
"a direct read from a runtime-optional capture with multiple value arms",
);
}
- return lowerer.checkedNarrowCall(helper, varRef(local.id, local.type, loc), narrowed, loc);
+ return holeGuarded(
+ lowerer.checkedNarrowCall(helper, varRef(local.id, local.type, loc), narrowed, loc),
+ );
}
// A predicate can strengthen a record's fields or narrow a base class
// to a subclass without changing the slot's stored value arm. Extract
@@ -1029,10 +1061,7 @@ export function runtimeOptionalReceiverRead(
"a runtime-optional receiver without a representable value arm",
);
}
- const message =
- property === null
- ? `${expr.text} is not a function`
- : `Cannot read properties of undefined (reading '${property}')`;
+ const message = runtimeOptionalReceiverMessage(expr, property);
return lowerer.maybeNarrow(
{
kind: "ternary",
@@ -1061,6 +1090,14 @@ export function runtimeOptionalReceiverRead(
);
}
+/** Node's TypeError message for a member read (`property`) or a call
+ * (`property` null) through an undefined runtime-optional receiver. */
+function runtimeOptionalReceiverMessage(expr: ts.Identifier, property: string | null): string {
+ return property === null
+ ? `${expr.text} is not a function`
+ : `Cannot read properties of undefined (reading '${property}')`;
+}
+
export function runtimeOptionalTrueIds(lowerer: Lowerer, node: ts.Expression): IrLocal[] {
return runtimeOptionalGuardIds(lowerer, node, true);
}
diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts
index 015be7914..678b375fe 100644
--- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts
+++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts
@@ -36,6 +36,7 @@ import { tryLowerExpression } from "./try-lower-expression.js";
import { fenceSymbolFieldCopy } from "../symbol-fields.js";
import { planUnionRetag } from "../union-retag.js";
import { jsHoldsDynObject, runtimeOptionalStorageOperand } from "../lower-exprs.js";
+import { builtinGlobalValue } from "./property-access.js";
/** `{ a: 1, b: "x" }` → recordLit. The record type comes from the
* contextual type when tsc has one (annotated declarations, arguments,
@@ -394,9 +395,11 @@ export function lowerDynObjectLiteral(
const lowerValue = (): IrExpr =>
ts.isMethodDeclaration(prop)
? lowerer.lowerLambda(prop, true)
- : !boxValue && ts.isObjectLiteralExpression(valueExpr)
- ? lowerer.lowerExprExpecting(valueExpr, DYN)
- : lowerer.lowerExpr(valueExpr as ts.Expression);
+ : ts.isShorthandPropertyAssignment(prop) && shorthandBuiltinValue(lowerer, prop)
+ ? shorthandBuiltinValue(lowerer, prop)!
+ : !boxValue && ts.isObjectLiteralExpression(valueExpr)
+ ? lowerer.lowerExprExpecting(valueExpr, DYN)
+ : lowerer.lowerExpr(valueExpr as ts.Expression);
raw = fenceClosureProbe(lowerer, valueExpr, undefined, lowerValue) ?? lowerValue();
} catch (err) {
// A PURE member read a JS file cannot lower (a namespace object
@@ -3157,6 +3160,20 @@ function lowerDeclaredSpreadMerge(
/** The value of a shorthand property (`{ x }`): the binding `x` refers to.
* The property name's own symbol is the PROPERTY, so resolution goes
* through getShorthandAssignmentValueSymbol. */
+/** `{ TypeError }`: a stdlib global in shorthand position reads as the value
+ * its name reads as elsewhere (the interned constructor or function value),
+ * or null when the global has no such value. The shorthand's name carries
+ * the property's symbol, so the identifier path cannot resolve it. */
+function shorthandBuiltinValue(
+ lowerer: Lowerer,
+ prop: ts.ShorthandPropertyAssignment,
+): IrExpr | null {
+ const symbol = lowerer.checker.getShorthandAssignmentValueSymbol(prop);
+ if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
+ const js = isJsSourceFile(ts.getSourceFileOfNode(prop));
+ return builtinGlobalValue(ts.symbolName(symbol), locOf(prop.name), js);
+}
+
export function lowerShorthandValue(
lowerer: Lowerer,
prop: ts.ShorthandPropertyAssignment,
@@ -3173,6 +3190,8 @@ export function lowerShorthandValue(
const callable = lowerer.lowerBuiltinCallableValue(builtin, loc);
if (callable) return callable;
}
+ const builtinValue = shorthandBuiltinValue(lowerer, prop);
+ if (builtinValue) return builtinValue;
if (symbol) {
if (lowerer.ctx.selfSymbol === symbol) {
return { kind: "selfRef", type: lowerer.ctx.selfType!, loc };
diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts
index 957dd1ddf..7f3910c15 100644
--- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts
+++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts
@@ -4090,10 +4090,26 @@ function lowerStreamObjectProperty(
return lowerStreamProperty(lowerer, expr, info);
}
+/** The error constructors among GLOBAL_CONSTRUCTOR_VALUES. Their statics
+ * (`Error.stackTraceLimit`, `Error.captureStackTrace`) keep their own
+ * lowerings, so fenceBuiltinConstructorStaticRead leaves them alone. */
+const ERROR_CONSTRUCTOR_VALUES: ReadonlySet = new Set([
+ "Error",
+ "TypeError",
+ "RangeError",
+ "SyntaxError",
+ "ReferenceError",
+ "EvalError",
+ "URIError",
+ "AggregateError",
+]);
+
/** The builtin constructors JavaScript sources read as real function
* values rather than identity tokens: the runtime interns one function per
* constructor, and dynamic values answer `.constructor` with the same one,
- * so `o.constructor === Object` is an exact identity test. */
+ * so `o.constructor === Object` and `err.constructor === TypeError` are
+ * exact identity tests, and `err.constructor.name` is the constructor's
+ * name. */
const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([
"Object",
"Function",
@@ -4104,6 +4120,7 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([
"Promise",
"WeakMap",
"WeakSet",
+ ...ERROR_CONSTRUCTOR_VALUES,
]);
/** The builtin global functions JavaScript sources read as interned
@@ -4154,8 +4171,18 @@ export function builtinGlobalValue(canonical: string, loc: SrcLoc, js = false):
const BUILTIN_VALUE_MEMBERS: ReadonlySet = new Set(["name", "length", "constructor"]);
/** The Object statics whose answers for an interned builtin function value
- * match Node's (identity, and the empty enumerable own-key lists). */
-const BUILTIN_VALUE_REFLECTION: ReadonlySet = new Set(["is", "keys", "values", "entries"]);
+ * match Node's (identity, the empty enumerable own-key lists, and
+ * extensibility: freezing, sealing and preventExtensions are fenced, so the
+ * values stay extensible). */
+const BUILTIN_VALUE_REFLECTION: ReadonlySet = new Set([
+ "is",
+ "keys",
+ "values",
+ "entries",
+ "isExtensible",
+ "isFrozen",
+ "isSealed",
+]);
/** The value `builtinGlobalValue` gives the stdlib global `expr` read at its
* use site: null when the use reads the global's members (they keep their
@@ -4246,6 +4273,7 @@ function fenceBuiltinConstructorStaticRead(
symbol !== undefined &&
ts.symbolName(symbol).endsWith("Constructor") &&
GLOBAL_CONSTRUCTOR_VALUES.has(ts.symbolName(symbol).slice(0, -"Constructor".length)) &&
+ !ERROR_CONSTRUCTOR_VALUES.has(ts.symbolName(symbol).slice(0, -"Constructor".length)) &&
lowerer.isStdlibSymbol(symbol)
)
lowerer.stdlibMemberFence(access);
diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts
index 1fff26585..e27ab7f5f 100644
--- a/packages/compiler/src/frontend/lowering/generic-functions.ts
+++ b/packages/compiler/src/frontend/lowering/generic-functions.ts
@@ -5,15 +5,20 @@ import * as ts from "../ts7/adapter.js";
import { bodyReadsArguments } from "../arguments-usage.js";
import type { Lowerer } from "./lowerer.js";
import {
+ BOOL,
DYN,
type IrExpr,
type IrFunction,
type IrParam,
type IrStmt,
type IrType,
+ type SrcLoc,
+ STRING,
+ VOID,
isUnitType,
typeEquals,
} from "../../ir/ir.js";
+import { helperLoc, nodeThrowExpr } from "../../ir/build.js";
import { isJsSourceFile, locOf } from "../program.js";
import { typeKey } from "../type-mapper.js";
import { functionCanReturnUndefined } from "../function-completion.js";
@@ -24,7 +29,7 @@ import { nsPathPrefix } from "./lower-namespaces.js";
import { declSymbolOf } from "./lower-modules.js";
import { npmStaticPackageOfPath } from "../npm-static.js";
import { rejectStaticThis } from "./static-this.js";
-import { jsBindingHasOpenWrites } from "./lower-stmts.js";
+import { isVarDeclared, jsBindingHasOpenWrites } from "./lower-stmts.js";
import {
type ParamShape,
paramShapes,
@@ -93,6 +98,17 @@ export interface GenericFnInfo {
* declaration makes a new function object in JavaScript, while the
* compiled instances are shared module functions, so only calls lower. */
functionScoped?: true;
+ /** The declaration of a generic arrow/function-expression binding. */
+ bindingDecl?: ts.VariableDeclaration;
+ /** A module-scope binding that code can use before its declaration
+ * runs: the BOOL global its declaration sets, and whether it is a
+ * `let`/`const` (a temporal dead zone) or a `var` (undefined). */
+ presence?: { flagId: string; lexical: boolean };
+ /** genericValueForwarder's lifted functions, by instance name. */
+ valueForwarders?: Map;
+ /** runsAfterDeclaration's answers for the function declarations whose
+ * calls it placed. */
+ ordering?: Map;
/** IMPLICIT-ANY monomorphization (npm-static JS): parallel to
* decl.parameters — the param's own symbol when the slot is a BINDABLE
* implicit-any parameter (untyped, identifier-named, never written in
@@ -266,7 +282,14 @@ export function lowerGenericCall(
? implicitCallInstance(lowerer, expr, info)
: genericCallInstance(lowerer, expr, info);
const args = lowerer.completeArgs(expr.arguments.nodes, instance.params, loc, expr);
- return { kind: "call", callee: instance.name, args, type: instance.returnType, loc };
+ const call: IrExpr & { kind: "call" } = {
+ kind: "call",
+ callee: instance.name,
+ args,
+ type: instance.returnType,
+ loc,
+ };
+ return guardEarlyBindingCall(lowerer, info, expr, call);
}
/** The instance a CALL of a generic function-like names: resolved
@@ -1361,6 +1384,110 @@ export function lowerGenericFnValue(
}
}
if (!target) fenceUnpinned();
+ return guardEarlyBindingValue(
+ lowerer,
+ info,
+ ref,
+ pinnedGenericClosure(lowerer, ref, info, target, fenceUnpinned),
+ );
+}
+
+/** The value a generic binding declared inside a function stores when a
+ * nested function read it before the declaration lowered (`const read =
+ * () => id; const id: (x: number) => number = (x: T): T => x`): such
+ * a read resolves to an ordinary TDZ local, so the declaration assigns it
+ * the instance its concrete declared signature pins. Each evaluation of
+ * the declaration makes a new function object, as in JavaScript: the
+ * value is a closure over a forwarder to the instance, not the instance's
+ * interned closure. */
+export function genericBindingStoredValue(
+ lowerer: Lowerer,
+ decl: ts.VariableDeclaration,
+ info: GenericFnInfo,
+): IrExpr {
+ const name = decl.name as ts.Identifier;
+ const fenceUnpinned = (): never =>
+ lowerer.unsupported(
+ "SC1090",
+ name,
+ `generic function bindings read before their declaration without a concrete declared signature (annotate '${info.baseName}' with one, or declare it before its uses)`,
+ );
+ const sigs = lowerer.checker.getCallSignatures(lowerer.typeOf(name));
+ if (sigs.length !== 1) fenceUnpinned();
+ const pinned = pinnedGenericClosure(lowerer, name, info, sigs[0]!, fenceUnpinned);
+ const async = ts.getModifiers(info.decl)?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword);
+ if (pinned.kind !== "closure" || pinned.type.kind !== "func" || pinned.type.rest || async)
+ return lowerer.unsupported(
+ "SC1090",
+ name,
+ `async or rest-parameter generic function bindings read before their declaration (each evaluation of '${info.baseName}' makes a new function object)`,
+ );
+ return {
+ ...pinned,
+ fnName: genericValueForwarder(lowerer, info, pinned.fnName, pinned.type, pinned.loc),
+ };
+}
+
+/** A lifted function that calls the instance `instance` (of type `type`)
+ * with its own arguments. Lifted functions get a new closure object per
+ * evaluation; module functions, which instances are, get one interned
+ * closure. */
+function genericValueForwarder(
+ lowerer: Lowerer,
+ info: GenericFnInfo,
+ instance: string,
+ type: IrType & { kind: "func" },
+ at: SrcLoc,
+): string {
+ info.valueForwarders ??= new Map();
+ const existing = info.valueForwarders.get(instance);
+ if (existing !== undefined) return existing;
+ const name = `${instance}%value`;
+ const loc = helperLoc(at);
+ const params: IrParam[] = type.params.map((paramType, i) => ({
+ localId: `%arg${i}.0`,
+ name: `%arg${i}`,
+ type: paramType,
+ }));
+ const call: IrExpr = {
+ kind: "call",
+ callee: instance,
+ args: params.map((p): IrExpr => ({ kind: "varRef", localId: p.localId, type: p.type, loc })),
+ type: type.ret,
+ loc,
+ };
+ lowerer.liftedFns.push({
+ name,
+ params,
+ returnType: type.ret,
+ locals: params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })),
+ captures: [],
+ ...(ts.isFunctionExpression(info.decl) ? { ownsPrototype: true } : {}),
+ body:
+ type.ret.kind === "void"
+ ? [
+ { kind: "exprStmt", expr: call, loc },
+ { kind: "return", value: null, loc },
+ ]
+ : [{ kind: "return", value: call, loc }],
+ loc,
+ });
+ lowerer.noteEdge(name);
+ info.valueForwarders.set(instance, name);
+ return name;
+}
+
+/** The closure of the instance of `info` that `target` pins, for a value
+ * read at `ref`. `fenceUnpinned` reports a signature that leaves a type
+ * parameter unbound. */
+function pinnedGenericClosure(
+ lowerer: Lowerer,
+ ref: ts.Expression,
+ info: GenericFnInfo,
+ target: ts.Signature,
+ fenceUnpinned: () => never,
+): IrExpr {
+ const loc = locOf(ref);
const bindings = new Map();
unifySignatureBindings(lowerer, info, target, bindings);
const hofBinding = runtimeOptionalHofGenericBinding(lowerer, ref, info);
@@ -2693,9 +2820,18 @@ export function bindingGenericFnInfoOf(
typeParams,
instances: new Map(),
...(functionScoped ? { functionScoped: true as const } : {}),
+ bindingDecl: decl,
};
lowerer.bindingGenericFns.set(fnNode, info);
lowerer.genericFnsBySymbol.set(sym, info);
+ // Module-scope uses resolve to the instances from the start, so those
+ // that can run first need the flag. Inside a function the declaration
+ // registers before any later use lowers, and earlier uses read a TDZ
+ // box (genericBindingStoredValue).
+ if (!functionScoped) {
+ const presence = bindingPresenceOf(lowerer, info, decl, sym);
+ if (presence !== undefined) info.presence = presence;
+ }
if (ts.isFunctionExpression(fnNode) && fnNode.name !== undefined) {
const inner = lowerer.checker.getSymbolAtLocation(fnNode.name);
if (inner) lowerer.genericFnsBySymbol.set(inner, info);
@@ -2703,6 +2839,261 @@ export function bindingGenericFnInfoOf(
return info;
}
+/** Whether `use`, a reference to the binding `info` declares, can only
+ * run after the declaration has run: it follows the declaration in its
+ * statement list (closures count where they are created), sits inside the
+ * initializer itself, or is inside a function declaration whose every
+ * reference does. References from other modules run after this module's
+ * body, and export specifiers only export. A use the check cannot place
+ * answers false. */
+export function runsAfterDeclaration(lowerer: Lowerer, info: GenericFnInfo, use: ts.Node): boolean {
+ const decl = info.bindingDecl;
+ if (decl === undefined) return true;
+ info.ordering ??= new Map();
+ return useRunsAfter(lowerer, decl, use, info.ordering, new Set());
+}
+
+function useRunsAfter(
+ lowerer: Lowerer,
+ decl: ts.VariableDeclaration,
+ use: ts.Node,
+ memo: Map,
+ inProgress: Set,
+): boolean {
+ if (ts.getSourceFileOfNode(use) !== ts.getSourceFileOfNode(decl)) return true;
+ const init = decl.initializer;
+ if (init !== undefined && use.pos >= init.pos && use.end <= init.end) return true;
+ const stmt = decl.parent?.parent;
+ const list = stmt?.parent;
+ if (stmt === undefined || list === undefined) return false;
+ for (let n: ts.Node = use; n.parent !== undefined; n = n.parent) {
+ if (n.parent === list) return n === stmt ? use.pos >= decl.end : n.pos >= stmt.end;
+ if (ts.isFunctionDeclaration(n)) return functionRunsAfter(lowerer, decl, n, memo, inProgress);
+ }
+ return false;
+}
+
+/** Whether every call of the function declaration `fn` runs after `decl`
+ * has run. A function in progress counts as running after: its calls of
+ * itself, directly or through other functions, add no earlier caller.
+ * Only answers that depend on no function in progress are memoized. */
+function functionRunsAfter(
+ lowerer: Lowerer,
+ decl: ts.VariableDeclaration,
+ fn: ts.FunctionDeclaration,
+ memo: Map,
+ inProgress: Set,
+): boolean {
+ const known = memo.get(fn);
+ if (known !== undefined) return known;
+ if (inProgress.has(fn)) return true;
+ // `export default function () {}`: only importers call it, after this
+ // module's body has run.
+ if (fn.name === undefined) return true;
+ inProgress.add(fn);
+ let result = true;
+ try {
+ const symbol = lowerer.checker.getSymbolAtLocation(fn.name);
+ if (symbol === undefined) result = false;
+ else {
+ for (const ref of bindingReferences(lowerer, ts.getSourceFileOfNode(fn), symbol, fn.name)) {
+ if (ref.pos >= fn.pos && ref.end <= fn.end) continue;
+ if (!useRunsAfter(lowerer, decl, ref, memo, inProgress)) {
+ result = false;
+ break;
+ }
+ }
+ }
+ } finally {
+ inProgress.delete(fn);
+ }
+ if (inProgress.size === 0) memo.set(fn, result);
+ return result;
+}
+
+/** The value references of `symbol` in `file` other than `name` (its
+ * declaration name): identifiers spelled like it that resolve to it,
+ * outside type positions and export specifiers. */
+function bindingReferences(
+ lowerer: Lowerer,
+ file: ts.SourceFile,
+ symbol: ts.Symbol,
+ name: ts.Identifier,
+): ts.Identifier[] {
+ let byName = lowerer.bindingIdentifiers.get(file);
+ if (byName === undefined) {
+ byName = new Map();
+ lowerer.bindingIdentifiers.set(file, byName);
+ }
+ let idents = byName.get(name.text);
+ if (idents === undefined) {
+ const found: ts.Identifier[] = [];
+ ts.walkPreorder(file, (node) => {
+ if (ts.isIdentifier(node) && node.text === name.text) found.push(node);
+ return undefined;
+ });
+ lowerer.checker.prefetchSymbolNodesExact(found);
+ byName.set(name.text, found);
+ idents = found;
+ }
+ return idents.filter((ident) => {
+ if (ident === name) return false;
+ const parent = ident.parent;
+ if (parent === undefined || ts.isExportSpecifier(parent) || ts.isExportAssignment(parent))
+ return false;
+ let resolved = lowerer.checker.getSymbolAtLocation(ident);
+ if (ts.isShorthandPropertyAssignment(parent) && parent.name === ident)
+ resolved = lowerer.checker.getShorthandAssignmentValueSymbol(parent) ?? resolved;
+ if (resolved !== symbol) return false;
+ for (let p: ts.Node | undefined = parent; p !== undefined; p = p.parent) {
+ if (ts.isTypeNode(p) && p.kind !== ts.SyntaxKind.ExpressionWithTypeArguments) return false;
+ if (ts.isStatement(p) || ts.isFunctionLike(p)) break;
+ }
+ return true;
+ });
+}
+
+/** The presence flag of a module-scope generic binding that code can use
+ * before its declaration runs (a hoisted function or an earlier closure
+ * called during module evaluation), or undefined when every use runs
+ * after it. The declaration statement sets the flag. */
+function bindingPresenceOf(
+ lowerer: Lowerer,
+ info: GenericFnInfo,
+ decl: ts.VariableDeclaration,
+ symbol: ts.Symbol,
+): GenericFnInfo["presence"] {
+ const name = decl.name as ts.Identifier;
+ const early = bindingReferences(lowerer, ts.getSourceFileOfNode(decl), symbol, name).some(
+ (ref) => !runsAfterDeclaration(lowerer, info, ref),
+ );
+ if (!early) return undefined;
+ const flagId = `%g.${info.qualifiedName}%initialized`;
+ lowerer.globalsList.push({ id: flagId, name: "%initialized", type: BOOL, mutable: true });
+ return { flagId, lexical: !isVarDeclared(decl) };
+}
+
+/** The statement that records a generic binding's declaration as run,
+ * for a binding with a presence flag. */
+export function genericBindingPresenceStore(
+ info: GenericFnInfo,
+ decl: ts.VariableDeclaration,
+): IrStmt | null {
+ if (info.presence === undefined) return null;
+ const loc = locOf(decl);
+ return {
+ kind: "assign",
+ localId: info.presence.flagId,
+ value: { kind: "boolLit", value: true, type: BOOL, loc },
+ loc,
+ };
+}
+
+/** A call of a generic binding where the binding may not be initialized
+ * yet. A `let`/`const` binding in its temporal dead zone throws Node's
+ * ReferenceError before the arguments run; an unassigned `var` is
+ * undefined, so the arguments run and the call throws "is not a
+ * function". Calls that run after the declaration stay direct. */
+function guardEarlyBindingCall(
+ lowerer: Lowerer,
+ info: GenericFnInfo,
+ expr: ts.CallExpression,
+ call: IrExpr & { kind: "call" },
+): IrExpr {
+ if (info.bindingDecl === undefined || runsAfterDeclaration(lowerer, info, expr)) return call;
+ const loc = call.loc;
+ const presence = requirePresence(lowerer, info, expr.expression);
+ const flag: IrExpr = { kind: "varRef", localId: presence.flagId, type: BOOL, loc };
+ if (presence.lexical)
+ return {
+ kind: "seqExpr",
+ stmts: [tdzCheck(flag, info.baseName, loc)],
+ result: call,
+ type: call.type,
+ loc,
+ };
+ // Arguments first, in order, then the check: Node evaluates them
+ // before it finds the callee is not callable.
+ const stmts: IrStmt[] = [];
+ const args = call.args.map((arg): IrExpr => {
+ const id = `%earlyCallArg.${lowerer.ctx.locals.length}`;
+ lowerer.ctx.locals.push({ id, name: "%earlyCallArg", type: arg.type, mutable: false });
+ stmts.push({ kind: "varDecl", localId: id, init: arg, loc });
+ return { kind: "varRef", localId: id, type: arg.type, loc };
+ });
+ return {
+ kind: "seqExpr",
+ stmts,
+ result: {
+ kind: "ternary",
+ cond: flag,
+ then: { ...call, args },
+ else_: nodeThrowExpr(1, "", `${info.baseName} is not a function`, call.type, loc),
+ type: call.type,
+ loc,
+ },
+ type: call.type,
+ loc,
+ };
+}
+
+/** A generic binding read as a value where it may not be initialized
+ * yet: a `let`/`const` binding checks its temporal dead zone first. */
+function guardEarlyBindingValue(
+ lowerer: Lowerer,
+ info: GenericFnInfo,
+ ref: ts.Expression,
+ value: IrExpr,
+): IrExpr {
+ if (info.bindingDecl === undefined || runsAfterDeclaration(lowerer, info, ref)) return value;
+ const presence = requirePresence(lowerer, info, ref);
+ if (!presence.lexical)
+ lowerer.unsupported(
+ "SC1090",
+ ref,
+ `generic 'var' bindings read as values before their declaration runs (Node reads undefined there; declare '${info.baseName}' with const)`,
+ );
+ const loc = value.loc;
+ const flag: IrExpr = { kind: "varRef", localId: presence.flagId, type: BOOL, loc };
+ return {
+ kind: "seqExpr",
+ stmts: [tdzCheck(flag, info.baseName, loc)],
+ result: value,
+ type: value.type,
+ loc,
+ };
+}
+
+/** The presence flag a use that can run before the declaration checks.
+ * Only module-scope bindings have one: a binding inside a function would
+ * need a per-call flag that its closures share. */
+function requirePresence(
+ lowerer: Lowerer,
+ info: GenericFnInfo,
+ blame: ts.Node,
+): NonNullable {
+ if (info.presence !== undefined) return info.presence;
+ return lowerer.unsupported(
+ "SC1090",
+ blame,
+ `generic arrow/function-expression bindings declared inside functions that code can call before their declaration runs (the binding '${info.baseName}' is a 'var' in a branch or block, or is called from a place that can run first — declare it with const before its uses)`,
+ );
+}
+
+function tdzCheck(flag: IrExpr, name: string, loc: SrcLoc): IrStmt {
+ return {
+ kind: "exprStmt",
+ expr: {
+ kind: "intrinsic",
+ name: "module.tdzCheck",
+ args: [flag, { kind: "strLit", value: name, type: STRING, loc }],
+ type: VOID,
+ loc,
+ },
+ loc,
+ };
+}
+
/** `const h = id` — a binding ALIASING a generic function (a top-level
* declaration, a registered generic binding, or another alias — resolved
* left to right in declaration order). The alias registers the SAME info
diff --git a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts
index 0e48f2040..5b427a750 100644
--- a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts
+++ b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts
@@ -428,7 +428,9 @@ function lowerNodeReadable(
},
{
kind: "if",
- cond: { kind: "dynTest", test: "undefined", value: chunkRef, type: BOOL, loc },
+ // the end is the done marker: an object-mode stream can yield
+ // undefined
+ cond: { kind: "libCall", fn: "stream.chunkDone", args: [chunkRef], type: BOOL, loc },
then: [{ kind: "break", loc }],
else_: null,
loc,
diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts
index bd5068d05..39b7207c0 100644
--- a/packages/compiler/src/frontend/lowering/lower-calls.ts
+++ b/packages/compiler/src/frontend/lowering/lower-calls.ts
@@ -7439,6 +7439,35 @@ function lowerObjectStaticCall(
if (call.questionDotToken || access.questionDotToken) return null;
if (!lowerer.isStdlibGlobal(access.expression, "Object")) return null;
const member = access.name.text;
+ // `Object.getOwnPropertyDescriptor(Error, "stackTraceLimit")` (Effect's
+ // isStackTraceLimitWritable): Error's own writable, enumerable,
+ // configurable data property holding the current limit. The interned
+ // Error value models no other statics (builtinGlobalValueAt).
+ const [first, second] = call.arguments.nodes;
+ if (
+ member === "getOwnPropertyDescriptor" &&
+ call.arguments.nodes.length === 2 &&
+ lowerer.isStdlibGlobal(first!, "Error") &&
+ ts.isStringLiteralLike(second!) &&
+ second.text === "stackTraceLimit"
+ ) {
+ const loc = locOf(call);
+ const dyn = (value: IrExpr): IrExpr => ({ kind: "dynFrom", value, type: DYN, loc });
+ const yes = dyn({ kind: "boolLit", value: true, type: BOOL, loc });
+ const limit: IrExpr = { kind: "libCall", fn: "error.stackLimitGet", args: [], type: F64, loc };
+ return {
+ kind: "dynObjLit",
+ fields: [
+ { key: { kind: "strLit", value: "value", type: STRING, loc }, value: dyn(limit) },
+ ...["writable", "enumerable", "configurable"].map((key) => ({
+ key: { kind: "strLit" as const, value: key, type: STRING, loc },
+ value: yes,
+ })),
+ ],
+ type: DYN,
+ loc,
+ };
+ }
if (
(member === "getOwnPropertyNames" ||
member === "getOwnPropertySymbols" ||
diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts
index 2a772ae25..d8ccc15b7 100644
--- a/packages/compiler/src/frontend/lowering/lower-classes.ts
+++ b/packages/compiler/src/frontend/lowering/lower-classes.ts
@@ -158,7 +158,7 @@ import {
classSymbolKeyOfSymbol,
symbolFieldDisplayName,
} from "./symbol-fields.js";
-import { lowerHttpAgentNew, lowerHttpServerNew } from "./lower-server.js";
+import { lowerHttpAgentNew, lowerHttpServerNew, lowerTlsSocketNew } from "./lower-server.js";
import {
ambientNsRootOf,
ambientUndefReadType,
@@ -9836,6 +9836,12 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
const httpServer = lowerHttpServerNew(lowerer, expr);
if (httpServer) return httpServer;
}
+ // `new tls.TLSSocket(socket, options)` — the server-mode wrap of an open
+ // socket (lower-server).
+ {
+ const tlsSocket = lowerTlsSocketNew(lowerer, expr);
+ if (tlsSocket) return tlsSocket;
+ }
// `new http.Agent(opts?)` / `new https.Agent(opts?)` — the Agent
// handle (lower-server): getName/destroy/counters through the dyn
// handle ops, requests thread it via the agent option.
diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts
index f105af786..f44a3d6d8 100644
--- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts
+++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts
@@ -1458,10 +1458,11 @@ export function lowerEmitterMethodCall(
const receiver = lowerReceiver();
const once = member === "once" || member === "prependOnceListener";
const prepend = member === "prependListener" || member === "prependOnceListener";
- // Stream 'data' rides its own two-slot payload ABI (bytes + string —
- // encoded streams deliver strings; scr_stream_emit_data): listeners
- // on readable-sided receivers register through DATA thunks that
- // unwrap the declared side (typed) or box by tag (dyn).
+ // Stream 'data' rides its own three-slot payload ABI (bytes, string —
+ // encoded streams deliver strings — and an object-mode value;
+ // scr_stream_emit_data): listeners on readable-sided receivers
+ // register through DATA thunks that unwrap the declared side, convert
+ // to the declared type, or take the value (dyn).
const sides = streamSidesOf(lowerer, info);
if (name === "data" && (sides === "r" || sides === "rw")) {
const dynFlavor = dynListenerFlavor(lowerer, args[1]!);
@@ -1500,16 +1501,25 @@ export function lowerEmitterMethodCall(
(lowerer.unions.get(p.unionId)?.arms ?? []).every(
(arm) => (arm.kind === "bytes" && arm.elem === "u8") || arm.kind === "string",
);
+ // Any other chunk type converts from the chunk's value (an
+ // object-mode stream's value, or the Buffer or string) when the
+ // listener runs, or throws a TypeError there.
if (
p !== undefined &&
!(p.kind === "bytes" && p.elem === "u8") &&
p.kind !== "string" &&
- !chunkUnion
+ !chunkUnion &&
+ (p.kind === "func" ||
+ !canConvertToDyn(
+ p,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ ))
) {
lowerer.noLowering(
`a 'data' listener whose chunk parameter is '${lowerer.fmt(p)}'`,
args[1]!,
- "chunks are Buffers — or strings once setEncoding/the encoding option applies",
+ "declare the chunk as a type that converts from a checked-dynamic value",
);
}
if (!registering) {
@@ -1654,8 +1664,10 @@ export function lowerEmitterMethodCall(
loc,
);
}
- // Stream 'data' rides the two-slot payload ABI: a user emit fills
- // the chunk's slot (bytes or string), NULLs the other.
+ // Stream 'data' rides the three-slot payload ABI: a user emit fills
+ // the chunk's slot (bytes or string) and NULLs the others; any other
+ // value is emitted as a checked-dynamic value, filling the slots by
+ // its tag.
const emitSides = streamSidesOf(lowerer, info);
if (name === "data" && (emitSides === "r" || emitSides === "rw")) {
if (args.length !== 2) {
@@ -1665,16 +1677,27 @@ export function lowerEmitterMethodCall(
"the event carries one chunk (a Buffer or string)",
);
}
- const chunk = lowerer.lowerExpr(args[1]!);
+ let chunk = lowerer.lowerExpr(args[1]!);
if (
!(chunk.type.kind === "bytes" && chunk.type.elem === "u8") &&
chunk.type.kind !== "string"
) {
- lowerer.noLowering(
- `emit('data') with a '${lowerer.fmt(chunk.type)}' chunk`,
- args[1]!,
- "chunks are Buffers or strings",
- );
+ if (
+ chunk.type.kind === "func" ||
+ (chunk.type.kind !== "dyn" &&
+ !canConvertToDyn(
+ chunk.type,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ ))
+ ) {
+ lowerer.noLowering(
+ `emit('data') with a '${lowerer.fmt(chunk.type)}' chunk`,
+ args[1]!,
+ "the chunk is a value that converts to a checked-dynamic value",
+ );
+ }
+ chunk = lowerer.coerceToExpected(chunk, DYN);
}
return {
kind: "libCall",
diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts
index 94d49d8b3..d9746e1f6 100644
--- a/packages/compiler/src/frontend/lowering/lower-server.ts
+++ b/packages/compiler/src/frontend/lowering/lower-server.ts
@@ -2939,6 +2939,26 @@ function lowerNetSocketMethodCall(
loc,
};
}
+ if (event === "secure") {
+ // The handshake's completion on a TLS socket (after secureConnect on
+ // a client); plain sockets never fire it.
+ const { cb } = lowerCallbackArg(
+ lowerer,
+ args[1]!,
+ "secure listeners",
+ 0,
+ () => false,
+ "use ()",
+ [],
+ );
+ return {
+ kind: "libCall",
+ fn: "tls.sockOnSecure",
+ args: [receiver, cb, once],
+ type: VOID,
+ loc,
+ };
+ }
if (event === "session") {
// The received-ticket event: fires once per TLS client socket with
// the serialized session (a Buffer); plain sockets never fire it.
@@ -2962,7 +2982,7 @@ function lowerNetSocketMethodCall(
lowerer.noLowering(
`socket.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
args[0]!,
- '"data", "end", "close", "error", "connect", "lookup", "ready", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)',
+ '"data", "end", "close", "error", "connect", "lookup", "ready", "timeout", "readable", "secureConnect", "secure", and "session" are the supported socket events (as literals)',
);
}
lowerer.noLowering(
@@ -4676,6 +4696,125 @@ export function lowerHttpServerNew(lowerer: Lowerer, expr: ts.NewExpression): Ir
return lowerHttpCreateServerForms(lowerer, expr, "new http.Server", locOf(expr));
}
+/** `new tls.TLSSocket(socket, options)` in server mode: a new TLS socket
+ * takes over the socket's connection and runs the server handshake
+ * (scr_tls_wrap_socket). The options record reads at runtime, the
+ * tls.connect precedent. A literal's secureContext member is a
+ * SecureContext handle, which the checked-dynamic record cannot hold, so
+ * it passes as its own argument; members after it must be free of side
+ * effects, because it is read after them. Node never starts the handshake
+ * of a client-mode TLSSocket, so a literal without isServer: true fences
+ * here. */
+export function lowerTlsSocketNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
+ const callee = expr.expression;
+ const isTlsSocket =
+ (ts.isPropertyAccessExpression(callee) &&
+ callee.name.text === "TLSSocket" &&
+ lowerer.builtinMemberOf(callee)?.module === "tls") ||
+ (ts.isIdentifier(callee) &&
+ lowerer.builtinImportOf(callee)?.module === "tls" &&
+ lowerer.builtinImportOf(callee)?.member === "TLSSocket");
+ if (!isTlsSocket) return null;
+ const loc = locOf(expr);
+ const HINT =
+ "the supported forms are new tls.TLSSocket(socket, { isServer: true, cert, key }) and new tls.TLSSocket(socket, { isServer: true, secureContext })";
+ const args = expr.arguments?.nodes ?? [];
+ if (args.length !== 2 || args.some(ts.isSpreadElement)) {
+ lowerer.noLowering(`new tls.TLSSocket with ${args.length} argument(s)`, expr, HINT);
+ }
+ const socket = lowerer.lowerExpr(args[0]!);
+ if (socket.type.kind !== "netSocket") {
+ lowerer.noLowering(
+ `new tls.TLSSocket over a '${lowerer.fmt(socket.type)}' value`,
+ args[0]!,
+ "the wrapped value must be a net.Socket (a server connection or an http CONNECT socket)",
+ );
+ }
+ let optsNode: ts.Expression = args[1]!;
+ while (ts.isParenthesizedExpression(optsNode)) optsNode = optsNode.expression;
+ if (!ts.isObjectLiteralExpression(optsNode)) {
+ const o = lowerer.lowerExpr(optsNode);
+ const opts: IrExpr =
+ o.type.kind === "dyn"
+ ? o
+ : canConvertToDyn(
+ o.type,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ ? { kind: "dynFrom", value: o, type: DYN, loc }
+ : lowerer.noLowering(
+ `new tls.TLSSocket with a '${lowerer.fmt(o.type)}' options record`,
+ optsNode,
+ `${HINT} — a secureContext member lowers in an options object literal`,
+ );
+ return { kind: "libCall", fn: "tls.wrapSocket", args: [socket, opts], type: NETSOCKET_T, loc };
+ }
+ const props = optsNode.properties.nodes;
+ const nameOf = (p: ts.ObjectLiteralElementLike): string | null =>
+ (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))
+ ? p.name.text
+ : null;
+ const isServer = props.find((p) => nameOf(p) === "isServer");
+ if (
+ isServer === undefined ||
+ (ts.isPropertyAssignment(isServer) && isServer.initializer.kind === ts.SyntaxKind.FalseKeyword)
+ ) {
+ lowerer.noLowering(
+ "new tls.TLSSocket in client mode",
+ optsNode,
+ "Node starts no handshake for a client-mode TLSSocket — pass isServer: true for the server side; tls.connect makes client sockets",
+ );
+ }
+ const ctxIndex = props.findIndex((p) => nameOf(p) === "secureContext");
+ if (ctxIndex < 0) {
+ const opts = lowerDynObjectLiteral(lowerer, optsNode);
+ return { kind: "libCall", fn: "tls.wrapSocket", args: [socket, opts], type: NETSOCKET_T, loc };
+ }
+ const ctxProp = props[ctxIndex]!;
+ const ctxNode = ts.isShorthandPropertyAssignment(ctxProp)
+ ? ctxProp.name
+ : (ctxProp as ts.PropertyAssignment).initializer;
+ for (const p of props.slice(ctxIndex + 1)) {
+ const pure =
+ ts.isShorthandPropertyAssignment(p) ||
+ (ts.isPropertyAssignment(p) && sideEffectFreeOptionValue(p.initializer));
+ if (!pure) {
+ lowerer.noLowering(
+ "a new tls.TLSSocket options literal with side effects after secureContext",
+ p,
+ "secureContext is read after the other members — move it to the end of the object",
+ );
+ }
+ }
+ let ctx: IrExpr | null = null;
+ const opts = lowerDynObjectLiteral(lowerer, optsNode, (node, value) => {
+ if (node !== ctxNode) return lowerer.coerceToExpected(value, DYN);
+ if (value.type.kind !== "secureCtx") {
+ lowerer.noLowering(
+ `a '${lowerer.fmt(value.type)}' secureContext`,
+ node,
+ "pass a tls.createSecureContext({ cert, key }) result",
+ );
+ }
+ ctx = value;
+ return {
+ kind: "dynFrom",
+ value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
+ type: DYN,
+ loc,
+ };
+ });
+ return {
+ kind: "libCall",
+ fn: "tls.wrapSocketCtx",
+ args: [socket, opts, ctx!],
+ type: NETSOCKET_T,
+ loc,
+ };
+}
+
/** Module-function calls on http import bindings: createServer([options][,
* handler]) and the call-form Server (Node's constructor works without
* `new` — test/parallel's http.Server(fn) spelling). The handler takes
@@ -4985,14 +5124,14 @@ function lowerTlsServerOptionsOrDyn(
* connect(options[, cb]) and connect(port[, host][, options][, cb]) with
* a RUNTIME options record (the checked-dynamic JS lane's shape — a dyn
* value, or a record that converts): the runtime walk implements
- * port/host/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout and throws the catchable
+ * port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout and throws the catchable
* fence for other documented members (the divergence-66 stance). The
* callback (and 'secureConnect'/'connect' listeners) fires
* post-handshake — Node's secureConnect timing. */
function lowerTlsConnectCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
const args = expr.arguments.nodes;
const FORMS_HINT =
- "the supported forms are connect(options[, cb]) and connect(port[, host][, options][, cb]) — options implement port/host/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout";
+ "the supported forms are connect(options[, cb]) and connect(port[, host][, options][, cb]) — options implement port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout";
if (args.length < 1 || args.length > 4 || args.some(ts.isSpreadElement)) {
lowerer.noLowering(`tls.connect with ${args.length} arguments`, expr, FORMS_HINT);
}
@@ -5043,7 +5182,22 @@ function lowerTlsConnectCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Src
// The options value rides whole to the runtime walk: dyn passes
// directly; a typed record that converts boxes into the checked-dynamic tree.
let opts: IrExpr;
- if (optsNode !== null) {
+ let literal: ts.Expression | null = optsNode;
+ while (literal !== null && ts.isParenthesizedExpression(literal)) literal = literal.expression;
+ if (
+ literal !== null &&
+ ts.isObjectLiteralExpression(literal) &&
+ literal.properties.nodes.some(
+ (p) =>
+ (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
+ ts.isIdentifier(p.name) &&
+ p.name.text === "socket",
+ )
+ ) {
+ // A socket member is a handle the record form cannot box: the literal
+ // builds the checked-dynamic object member by member.
+ opts = lowerDynObjectLiteral(lowerer, literal);
+ } else if (optsNode !== null) {
const o = lowerer.lowerExpr(optsNode);
if (o.type.kind === "dyn") {
opts = o;
diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts
index 687420baf..213abfec6 100644
--- a/packages/compiler/src/frontend/lowering/lower-stmts.ts
+++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts
@@ -139,6 +139,8 @@ import {
bindingGenericFnAliasInfoOf,
bindingGenericFnInfoOf,
bindingGenericFnNodeOf,
+ genericBindingPresenceStore,
+ genericBindingStoredValue,
implicitLocalFnInfoOf,
implicitLocalFnNodeOf,
implicitMethodCallInfersReturn,
@@ -3889,6 +3891,9 @@ function stdlibGlobalTokenDestructure(
if (decl.initializer === undefined || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return null;
const globalName = stdlibGlobalNameOf(lowerer, decl.initializer);
if (globalName === null) return null;
+ // `workerData` is a value (the data the worker was started with), not an
+ // API surface: its members destructure as any untyped value's do.
+ if (globalName === "workerData") return null;
if (!ts.isObjectBindingPattern(decl.name)) {
lowerer.unsupported(
"SC1031",
@@ -5113,8 +5118,19 @@ export function lowerVarDecl(
{
const gfnNode = bindingGenericFnNodeOf(decl) ?? bindingContextualGenericFnNodeOf(lowerer, decl);
if (gfnNode) {
- bindingGenericFnInfoOf(lowerer, decl, gfnNode);
- return null;
+ const info = bindingGenericFnInfoOf(lowerer, decl, gfnNode);
+ // A nested function that lowered before this declaration read the
+ // binding as an ordinary TDZ box (predeclareForwardCapture); the
+ // declaration initializes it, so those reads see the function once
+ // it runs and a ReferenceError before.
+ const symbol = lowerer.checker.getSymbolAtLocation(decl.name);
+ const pre = symbol ? lowerer.ctx.tdzPredeclared.get(symbol) : undefined;
+ if (pre) {
+ lowerer.ctx.tdzPredeclared.delete(symbol!);
+ const value = genericBindingStoredValue(lowerer, decl, info);
+ return { kind: "assign", localId: pre.id, value, initializes: true, loc: locOf(decl) };
+ }
+ return genericBindingPresenceStore(info, decl);
}
}
diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts
index d6f89dc11..14062051f 100644
--- a/packages/compiler/src/frontend/lowering/lower-stream.ts
+++ b/packages/compiler/src/frontend/lowering/lower-stream.ts
@@ -308,6 +308,21 @@ function lowerStreamCallback(
// boxes each position by kind (the completion callback arrives as a
// callable dyn whose glue dispatches to the runtime's *_done).
if (funcType.params[i]!.kind === "dyn") continue;
+ // A write or transform chunk of another type converts from the
+ // chunk's value when the callback runs (an object-mode side's value,
+ // or the Buffer), or throws a TypeError there; an encoding of another
+ // type (`string | null | undefined`) from the encoding argument's.
+ if (
+ (i === 0 || i === 1) &&
+ (which === "write" || which === "transform") &&
+ funcType.params[i]!.kind !== "func" &&
+ canConvertToDyn(
+ funcType.params[i]!,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ )
+ continue;
if (!typeEquals(funcType.params[i]!, fullTuple[i]!)) {
lowerer.noLowering(
`a '${which}' callback whose parameter ${i} is '${lowerer.fmt(funcType.params[i]!)}' where Node passes '${lowerer.fmt(fullTuple[i]!)}'`,
@@ -404,11 +419,52 @@ interface StreamOptions {
* right after construction (Node's constructors call addAbortSignal
* once the state exists), or null when absent. */
signal: IrExpr | null;
+ /** The objectMode option's sides (a Duplex's readableObjectMode and
+ * writableObjectMode set one side): applied with stream.objectMode
+ * right after construction. */
+ readableObject: boolean;
+ writableObject: boolean;
/** Present callbacks in canonical order with their flag bits. */
flags: number;
cbs: IrExpr[];
}
+/** The objectMode, readableObjectMode and writableObjectMode members of a
+ * stream options literal (boolean literals; the sided keys count only on
+ * a Duplex, Transform or PassThrough, as in Node). */
+function streamObjectModes(
+ lowerer: Lowerer,
+ ctorName: string,
+ optsLit: ts.ObjectLiteralExpression | undefined,
+): { readable: boolean; writable: boolean } {
+ const out = { readable: false, writable: false };
+ const duplexish = ctorName !== "Readable" && ctorName !== "Writable";
+ for (const prop of optsLit?.properties?.nodes ?? []) {
+ if (!ts.isPropertyAssignment(prop)) continue;
+ const name =
+ ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
+ if (name !== "objectMode" && name !== "readableObjectMode" && name !== "writableObjectMode")
+ continue;
+ const value = prop.initializer;
+ if (value.kind === ts.SyntaxKind.FalseKeyword) continue;
+ if (value.kind !== ts.SyntaxKind.TrueKeyword) {
+ lowerer.noLowering(
+ `${ctorName} with a runtime '${name}' value`,
+ prop,
+ `write '${name}: true' or '${name}: false'`,
+ );
+ }
+ if (name === "objectMode") {
+ out.readable = true;
+ out.writable = true;
+ } else if (duplexish) {
+ if (name === "readableObjectMode") out.readable = true;
+ else out.writable = true;
+ }
+ }
+ return out;
+}
+
/** The full Node parameter tuples of the option callbacks, given the
* concrete stream's `this` type. The trailing completion callbacks keep
* whatever func type the checker contextually gave the user's parameter
@@ -657,6 +713,8 @@ function parseStreamOptions(
badEncoding: null,
pushEncoding: null,
signal: null,
+ readableObject: false,
+ writableObject: false,
flags: 0,
cbs: [],
};
@@ -669,6 +727,18 @@ function parseStreamOptions(
"the options must be an object literal, inline or through const-initialized bindings (the callbacks lower as compile-time closures)",
);
}
+ // Read before the callbacks lower: an object-mode readable side keeps a
+ // completion callback's `data: any` (its values are not only strings and
+ // Buffers). An object-mode side's high-water mark defaults to 16.
+ const objectModes = streamObjectModes(
+ lowerer,
+ ctorName,
+ optsLit !== undefined && ts.isObjectLiteralExpression(optsLit) ? optsLit : undefined,
+ );
+ out.readableObject = objectModes.readable;
+ out.writableObject = objectModes.writable;
+ if (out.readableObject) out.hwmR = numLit(16, loc);
+ if (out.writableObject) out.hwmW = numLit(16, loc);
const tuples = cbTuples(lowerer);
const present = new Map();
for (const prop of optsLit?.properties?.nodes ?? []) {
@@ -715,7 +785,7 @@ function parseStreamOptions(
continue;
}
const full = tuples[name]!.map((t) => (t === "fn" ? null : t));
- const pinned = pinAnyDataCompletion(lowerer, name, fnNode);
+ const pinned = out.readableObject ? null : pinAnyDataCompletion(lowerer, name, fnNode);
try {
// The completion-callback tail: typed by the checker contextually;
// accept the user's declared func type as the tuple's entry so the
@@ -776,16 +846,9 @@ function parseStreamOptions(
break;
case "objectMode":
case "readableObjectMode":
- case "writableObjectMode": {
- // `objectMode: false` is the byte default — admit the literal.
- if (value.kind === ts.SyntaxKind.FalseKeyword) break;
- lowerer.noLowering(
- `${ctorName} in objectMode`,
- prop,
- "object-mode streams have no static chunk type yet — chunks are Buffers (utf8 strings convert at push/write)",
- );
+ case "writableObjectMode":
+ // read up front (streamObjectModes)
break;
- }
case "decodeStrings": {
if (value.kind === ts.SyntaxKind.TrueKeyword) break; // Node's default
lowerer.noLowering(
@@ -942,6 +1005,8 @@ function streamCtorArgs(
badEncoding: string | null;
pushEncoding: string | null;
signal: IrExpr | null;
+ readableObject: boolean;
+ writableObject: boolean;
} {
const shape = streamCtorShape(cls);
const o = parseStreamOptions(
@@ -971,6 +1036,8 @@ function streamCtorArgs(
badEncoding: o.badEncoding,
pushEncoding: o.pushEncoding,
signal: o.signal,
+ readableObject: o.readableObject,
+ writableObject: o.writableObject,
};
}
@@ -1015,6 +1082,17 @@ export function lowerStreamNew(lowerer: Lowerer, expr: ts.NewExpression, info: C
);
}
let built: IrExpr = { kind: "libCall", fn, args: parsed.args, type: thisType, loc };
+ if (parsed.readableObject || parsed.writableObject) {
+ // { objectMode }: applied before any other follow-up (answers the
+ // receiver +1).
+ built = {
+ kind: "libCall",
+ fn: "stream.objectMode",
+ args: [built, boolLit(parsed.readableObject, loc), boolLit(parsed.writableObject, loc)],
+ type: thisType,
+ loc,
+ };
+ }
if (parsed.pushEncoding !== null) {
// { defaultEncoding }: chain the push-side decode default (the
// setEncoding chaining shape — answers the receiver +1).
@@ -1133,6 +1211,22 @@ function streamMethodWrapper(
);
}
} else if (!typeEquals(got.type, want)) {
+ // A write or transform encoding of another type (`string | null |
+ // undefined`) converts from the encoding argument's value: an
+ // object-mode write passes undefined, null or the name.
+ if (
+ i === 1 &&
+ (which === "write" || which === "transform") &&
+ got.type.kind !== "func" &&
+ canConvertToDyn(
+ got.type,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ ) {
+ paramTypes.push(got.type);
+ continue;
+ }
// A declared supertype of what Node passes (`chunk: Buffer | string`)
// is sound: the wrapper takes Node's value and widens it into the
// declared parameter.
@@ -1140,6 +1234,22 @@ function streamMethodWrapper(
paramTypes.push(want);
continue;
}
+ // A write or transform chunk of another type converts from the
+ // chunk's value when the method runs (an object-mode side's value,
+ // or the Buffer), or throws a TypeError there.
+ if (
+ i === 0 &&
+ (which === "write" || which === "transform") &&
+ got.type.kind !== "func" &&
+ canConvertToDyn(
+ got.type,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ ) {
+ paramTypes.push(got.type);
+ continue;
+ }
lowerer.noLowering(
`a '${methodName}' method whose parameter ${i} is '${lowerer.fmt(got.type)}' where Node passes '${lowerer.fmt(want)}'`,
blame,
@@ -1301,6 +1411,19 @@ export function lowerStreamSuperCall(
loc,
},
];
+ if (parsed.readableObject || parsed.writableObject) {
+ out.push({
+ kind: "exprStmt",
+ expr: {
+ kind: "libCall",
+ fn: "stream.objectMode",
+ args: [thisRef(), boolLit(parsed.readableObject, loc), boolLit(parsed.writableObject, loc)],
+ type: thisType,
+ loc,
+ },
+ loc,
+ });
+ }
if (parsed.pushEncoding !== null) {
out.push({
kind: "exprStmt",
@@ -1602,7 +1725,17 @@ export function lowerStreamStaticCall(
}
const anySrc =
srcT?.kind === "dyn" ||
- (srcT?.kind === "set" && typedElem(srcT.elem) !== null) ||
+ // a Set or Map (entry arrays) of other values: the checked-dynamic
+ // view iterates the collection itself
+ ((srcT?.kind === "set" || srcT?.kind === "map") &&
+ canConvertToDyn(
+ srcT,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )) ||
+ // an array of other values: the object-mode entries come from its
+ // checked-dynamic copy
+ (srcT?.kind === "array" && fromGenChunk(lowerer, srcT.elem) && typedElem(srcT.elem) === null) ||
(srcNode !== undefined &&
srcT === null &&
(lowerer.typeOf(srcNode).flags & ts.TypeFlags.Any) !== 0);
@@ -1623,20 +1756,24 @@ export function lowerStreamStaticCall(
lowerer.noLowering(
`Readable.from over a '${srcT ? lowerer.fmt(srcT) : "?"}' source`,
srcNode ?? call,
- "the supported sources are a string or Buffer, and arrays, Sets, generators and class iterables of strings or Buffers",
+ "the supported sources are a string or Buffer, and arrays, Sets, Maps, generators and class iterables of values that convert to checked-dynamic values",
);
}
-/** Whether a generator's yields are stream chunks: strings, Buffers, a
- * union of them (null included: the runtime destroys the stream with
- * ERR_STREAM_NULL_VALUES), or checked-dynamic values (checked when
- * pushed). */
+/** Whether a generator's yields can be stream chunks: values that box
+ * into checked-dynamic values (null included: the runtime destroys the
+ * stream with ERR_STREAM_NULL_VALUES). An object-mode stream takes any of
+ * them; a byte-mode one checks them when they are pushed or written. */
function fromGenChunk(lowerer: Lowerer, t: IrType): boolean {
- const chunk = (a: IrType): boolean =>
- a.kind === "string" || (a.kind === "bytes" && a.elem === "u8") || a.kind === "nullT";
- if (t.kind === "dyn" || (chunk(t) && t.kind !== "nullT")) return true;
- if (t.kind !== "union") return false;
- return lowerer.unions.get(t.unionId)?.arms.every(chunk) ?? false;
+ if (t.kind === "nullT" || t.kind === "void" || t.kind === "func") return false;
+ return (
+ t.kind === "dyn" ||
+ canConvertToDyn(
+ t,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ );
}
/** The helpers Readable.from pulls a compiled generator through, as
@@ -3587,14 +3724,6 @@ function lowerStreamOperator(
throw new InternalCompilerError(`unknown stream operator ${member}`);
}
-/** The literal 'buffer' encoding on write()/end(): Node 24 takes it for a
- * Buffer chunk and throws ERR_UNKNOWN_ENCODING for a string chunk. */
-function isBufferEncoding(lowerer: Lowerer, arg: ts.Expression | undefined): boolean {
- if (arg === undefined) return false;
- const t = lowerer.typeOf(arg);
- return t.isStringLiteralType() && t.value === "buffer";
-}
-
/** The one supported encoding argument: utf8 (Node's default), as a
* compile-time literal or absent. */
function checkUtf8Encoding(lowerer: Lowerer, member: string, arg: ts.Expression | undefined): void {
@@ -3925,10 +4054,24 @@ export function lowerStreamMethodCall(
if (probed?.type.kind === "dyn") return "dyn";
if (probed?.type.kind === "nullT") return "null";
}
+ // Any other value is a checked-dynamic chunk: one entry on an
+ // object-mode side, Node's chunk type error on a byte-mode one
+ // (undefined, which a byte-mode push ignores, included).
+ if (t?.kind === "void" || t?.kind === "undefinedT") return "dyn";
+ if (
+ t !== null &&
+ t.kind !== "func" &&
+ canConvertToDyn(
+ t,
+ (id) => lowerer.shapes.get(id),
+ (id) => lowerer.unions.get(id),
+ )
+ )
+ return "dyn";
lowerer.noLowering(
`${member} with a '${t ? lowerer.fmt(t) : "?"}' chunk`,
node,
- "chunks are Buffers or utf8 strings — or unions of those with null (objectMode is not lowered)",
+ "chunks are values that convert to checked-dynamic values: Buffers, strings, numbers, records, arrays and class instances",
);
};
@@ -3973,11 +4116,17 @@ export function lowerStreamMethodCall(
checkUtf8Encoding(lowerer, member, args[1]);
const kind = chunkKind(args[0]!);
if ((kind === "null" || kind === "union" || kind === "dyn") && member === "unshift") {
- lowerer.noLowering(
- kind === "null" ? "unshift(null)" : "unshift with a nullable or dynamic chunk",
- call,
- "EOF signals through push(null)",
- );
+ // unshift(value): an object-mode stream's value at the front, null
+ // the end of the stream, as Node's readableAddChunk takes them
+ const receiver = lowerer.lowerExpr(access.expression);
+ const chunk = lowerer.lowerExprExpecting(args[0]!, DYN);
+ return {
+ kind: "libCall",
+ fn: "readable.unshiftDyn",
+ args: [receiver, chunk],
+ type: VOID,
+ loc,
+ };
}
const receiver = lowerer.lowerExpr(access.expression);
if (kind === "null") {
@@ -4237,9 +4386,54 @@ export function lowerStreamMethodCall(
}
const kind = chunkKind(args[0]!);
if (kind === "null") lowerer.noLowering("write(null)", call, "end() finishes a writable");
+ const lowerWriteCb = (cbArg: ts.Expression): IrExpr => {
+ const cb = callbackDeclaresParameters(cbArg)
+ ? null
+ : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
+ if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) {
+ lowerer.noLowering(
+ "write completion callbacks taking the error argument",
+ cbArg,
+ "() => void callbacks are supported (listen for 'error' to observe failures)",
+ );
+ }
+ return cb!;
+ };
+ // write(chunk, encoding[, cb]), and write(value, cb): the encoding
+ // argument reaches an object-mode side's _write as passed, null when
+ // the callback took its place (a byte-mode side encodes a string
+ // chunk with it).
+ const writeEnc = (encArg: ts.Expression | undefined, cbArg: ts.Expression | undefined) => {
+ const receiver = lowerer.lowerExpr(access.expression);
+ const chunk = lowerer.lowerExprExpecting(args[0]!, DYN);
+ const tail: IrExpr[] = [];
+ if (encArg !== undefined) tail.push(lowerer.lowerExprExpecting(encArg, DYN));
+ if (cbArg !== undefined) tail.push(lowerWriteCb(cbArg));
+ const flags = (encArg !== undefined ? 1 : 0) | (cbArg !== undefined ? 2 : 0);
+ return {
+ kind: "libCall",
+ fn: "writable.writeEnc",
+ args: [receiver, numLit(flags, loc), chunk, ...tail],
+ type: BOOL,
+ loc,
+ } satisfies IrExpr;
+ };
if (kind === "dyn") {
- if (args.length > 1)
- lowerer.noLowering("write with a dynamic chunk and more arguments", call);
+ if (args.length === 3) return writeEnc(args[1], args[2]);
+ if (args.length === 2) {
+ // Node's write takes a function there as the callback, anything
+ // else as the encoding
+ const t = lowerer.mapTypeOf(lowerer.typeOf(args[1]!));
+ if (t?.kind === "func") return writeEnc(undefined, args[1]);
+ if (t === null || t === undefined || t.kind === "dyn") {
+ lowerer.noLowering(
+ "write with a dynamic second argument",
+ args[1]!,
+ "pass an encoding string or a completion callback",
+ );
+ }
+ return writeEnc(args[1], undefined);
+ }
const receiver = lowerer.lowerExpr(access.expression);
const chunk = lowerer.lowerExprExpecting(args[0]!, DYN);
return { kind: "libCall", fn: "writable.writeDyn", args: [receiver, chunk], type: BOOL, loc };
@@ -4263,19 +4457,15 @@ export function lowerStreamMethodCall(
return { kind: "libCall", fn, args: [receiver, chunk], type: BOOL, loc };
}
// Disambiguate (chunk, cb?) vs (chunk, encoding, cb?) by the second
- // argument's static type.
+ // argument's static type. A write with an encoding runs through
+ // writeEnc, whose runtime checks and applies it as Node's _write does.
let cbArg: ts.Expression | undefined;
- if (args.length >= 2 && isBufferEncoding(lowerer, args[1])) {
- if (kind !== "bytes")
- return nodeThrowExpr(1, "ERR_UNKNOWN_ENCODING", "Unknown encoding: buffer", BOOL, loc);
- cbArg = args[2];
- } else if (args.length === 2) {
+ if (args.length === 3) return writeEnc(args[1], args[2]);
+ if (args.length === 2) {
const t = lowerer.mapTypeOf(lowerer.typeOf(args[1]!));
- if (t?.kind === "string") checkUtf8Encoding(lowerer, "write", args[1]);
- else cbArg = args[1];
- } else if (args.length === 3) {
- checkUtf8Encoding(lowerer, "write", args[1]);
- cbArg = args[2];
+ if (t?.kind === "string" || t?.kind === "nullT" || t?.kind === "undefinedT")
+ return writeEnc(args[1], undefined);
+ cbArg = args[1];
}
const receiver = lowerer.lowerExpr(access.expression);
const chunk =
@@ -4284,16 +4474,7 @@ export function lowerStreamMethodCall(
: lowerer.lowerExprExpecting(args[0]!, STRING);
const fn = kind === "bytes" ? "writable.write" : "writable.writeStr";
if (cbArg !== undefined) {
- const cb = callbackDeclaresParameters(cbArg)
- ? null
- : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID));
- if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) {
- lowerer.noLowering(
- "write completion callbacks taking the error argument",
- cbArg,
- "() => void callbacks are supported (listen for 'error' to observe failures)",
- );
- }
+ const cb = lowerWriteCb(cbArg);
return { kind: "libCall", fn, args: [receiver, chunk, cb], type: BOOL, loc };
}
return { kind: "libCall", fn, args: [receiver, chunk], type: BOOL, loc };
@@ -4305,34 +4486,29 @@ export function lowerStreamMethodCall(
// Forms: (), (cb), (chunk), (chunk, cb), (chunk, encoding, cb).
let chunkNode: ts.Expression | undefined;
let cbArg: ts.Expression | undefined;
+ let encNode: ts.Expression | undefined;
if (args.length === 1) {
const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!));
if (t?.kind === "func") cbArg = args[0];
else chunkNode = args[0];
- } else if (args.length >= 2 && isBufferEncoding(lowerer, args[1])) {
- chunkNode = args[0];
- const endKind = chunkKind(args[0]!);
- if (endKind === "string") {
- const endT = lowerer.mapTypeOf(lowerer.typeOf(call)) ?? DYN;
- return nodeThrowExpr(1, "ERR_UNKNOWN_ENCODING", "Unknown encoding: buffer", endT, loc);
- }
- if (endKind !== "bytes") checkUtf8Encoding(lowerer, "end", args[1]);
- cbArg = args[2];
} else if (args.length === 2) {
chunkNode = args[0];
const t = lowerer.mapTypeOf(lowerer.typeOf(args[1]!));
- if (t?.kind === "string") checkUtf8Encoding(lowerer, "end", args[1]);
+ if (t?.kind === "string" || t?.kind === "nullT" || t?.kind === "undefinedT")
+ encNode = args[1];
else cbArg = args[1];
} else if (args.length === 3) {
chunkNode = args[0];
- checkUtf8Encoding(lowerer, "end", args[1]);
+ encNode = args[1];
cbArg = args[2];
}
const receiver = lowerer.lowerExpr(access.expression);
let flags = 0;
const tail: IrExpr[] = [];
if (chunkNode !== undefined) {
- const kind = chunkKind(chunkNode);
+ // end(chunk, encoding[, cb]) writes the chunk with the encoding
+ // argument as passed (flags 16), through the dyn write
+ const kind = encNode !== undefined ? "dyn" : chunkKind(chunkNode);
if (kind === "null" || kind === "union") lowerer.noLowering("end(null)", call);
flags |= kind === "bytes" ? 1 : kind === "dyn" ? 8 : 2;
tail.push(
@@ -4342,6 +4518,10 @@ export function lowerStreamMethodCall(
? lowerer.lowerExprExpecting(chunkNode, DYN)
: lowerer.lowerExprExpecting(chunkNode, STRING),
);
+ if (encNode !== undefined) {
+ flags |= 16;
+ tail.push(lowerer.lowerExprExpecting(encNode, DYN));
+ }
}
if (cbArg !== undefined) {
const cb = callbackDeclaresParameters(cbArg)
@@ -4604,8 +4784,7 @@ export function lowerStreamProperty(
return propCall(BOOL);
}
if (name === "writableObjectMode" && writableSide) {
- // objectMode construction fences, so the answer is compile-time false.
- return boolLit(false, loc);
+ return propCall(BOOL);
}
if (name === "allowHalfOpen" && sides === "rw") {
return propCall(BOOL);
diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts
index edf0a694b..c95543c61 100644
--- a/packages/compiler/src/frontend/lowering/lowerer.ts
+++ b/packages/compiler/src/frontend/lowering/lowerer.ts
@@ -1914,6 +1914,9 @@ export class Lowerer {
* pinned values resolve through genericFnOf exactly like top-level
* generic function declarations (bindingGenericFnInfoOf). */
readonly bindingGenericFns = new Map();
+ /** Identifiers by spelling, per file, for the generic bindings' use
+ * ordering (runsAfterDeclaration). */
+ readonly bindingIdentifiers = new Map>();
/** Per-symbol result of the never-reassigned file scan
* (bindingNeverReassigned — object-literal generic-method receivers). */
readonly neverReassignedCache = new Map();
diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts
index 6179f8c78..49b1c2d32 100644
--- a/packages/compiler/src/frontend/lowering/surfaces.ts
+++ b/packages/compiler/src/frontend/lowering/surfaces.ts
@@ -1553,6 +1553,16 @@ export const BUILTIN_MODULE_FNS: Record<
createSecretKey: { fn: "crypto.createSecretKey", params: [DYN, DYN], result: DYN },
generateKeySync: { fn: "crypto.generateKeySync", params: [DYN, DYN], result: DYN },
generateKey: { fn: "crypto.generateKeyCb", params: [], result: VOID },
+ // Asymmetric keys and signatures (lowerPkiCall): checked-dynamic
+ // arguments, results converted to the call site's type.
+ createPrivateKey: { fn: "crypto.createPrivateKey", params: [DYN], result: DYN },
+ createPublicKey: { fn: "crypto.createPublicKey", params: [DYN], result: DYN },
+ createSign: { fn: "crypto.createSign", params: [DYN], result: DYN },
+ createVerify: { fn: "crypto.createVerify", params: [DYN], result: DYN },
+ generateKeyPairSync: { fn: "crypto.generateKeyPairSync", params: [], result: DYN },
+ generateKeyPair: { fn: "crypto.generateKeyPairCb", params: [], result: VOID },
+ sign: { fn: "crypto.signOneshot", params: [], result: DYN },
+ verify: { fn: "crypto.verifyOneshot", params: [], result: DYN },
pbkdf2Sync: {
fn: "crypto.pbkdf2",
params: [BYTES_U8, BYTES_U8, F64, F64, STRING],
@@ -2211,8 +2221,8 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
kind: "stdlib",
name: "globalThis.crypto.subtle",
fns: [
- "webcrypto.importKey",
"webcrypto.generateKey",
+ "webcrypto.importKey",
"webcrypto.exportKey",
"webcrypto.encrypt",
"webcrypto.decrypt",
@@ -2224,7 +2234,7 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
"webcrypto.verify",
"webcrypto.digest",
],
- note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused",
+ note: "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused",
},
{
id: "stdlib.webcrypto.CryptoKey",
@@ -3070,14 +3080,6 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<
crypto: {
...Object.fromEntries(
[
- "generateKeyPair",
- "generateKeyPairSync",
- "createPrivateKey",
- "createPublicKey",
- "createSign",
- "createVerify",
- "sign",
- "verify",
"createDiffieHellman",
"createDiffieHellmanGroup",
"getDiffieHellman",
@@ -3088,9 +3090,9 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<
"privateDecrypt",
].map((m) => [
m,
- "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and " +
- "public and private KeyObjects — neither exists in the static runtime, so no faithful lowering " +
- "can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics",
+ "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is " +
+ "RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, " +
+ "createSign, createVerify, and createECDH",
]),
),
...Object.fromEntries(
diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts
index e398e59df..507d3392d 100644
--- a/packages/compiler/src/frontend/program.ts
+++ b/packages/compiler/src/frontend/program.ts
@@ -140,6 +140,15 @@ import {
} from "./type-only-imports.js";
import { moduleSourceCandidates } from "./module-source-candidates.js";
import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js";
+import {
+ containsJsx,
+ firstJsx,
+ jsxEmitName,
+ jsxEmitPlan,
+ jsxPragmas,
+ lowerJsx,
+ type LoweredJsx,
+} from "./jsx-source.js";
const BASE_OPTIONS: ts.Ts7CompilerOptions = {
strict: true,
@@ -377,6 +386,18 @@ export interface LoadResult {
* checkPreflight. */
startupCrash?: StartupCrash | null;
configDiags: ScrDiagnostic[];
+ /** The files whose JSX the program lowers (jsx-source.ts), by file name.
+ * The program holds their rewritten text; positions in diagnostics refer
+ * to it. */
+ jsxSources?: ReadonlyMap;
+ /** With JSX sources: the program over the original text, whose TypeScript
+ * errors are the program's, and its project world when it has errors. */
+ tscWorld?: {
+ program: ts.Program;
+ /** The program's pre-emit diagnostics, computed once. */
+ diagnostics: readonly ts.Diagnostic[];
+ projectWorld: ts.Program | null;
+ };
/** How scriptc's type world differs from the project's own tsc setup —
* used only to label the TypeScript errors those differences cause. */
typeWorld?: TypeWorldDifferences;
@@ -778,6 +799,47 @@ function moduleSpecifierKind7(specifier: ts.StringLiteralLike): "import" | "requ
: "import";
}
+/** The program's files with JSX, lowered with the project's jsx settings
+ * and each file's pragmas, or the refusal of a setting that emits no calls.
+ * TypeScript files (.ts, .mts, .cts) cannot hold JSX; without a jsx setting
+ * the checker already rejects any JSX (TS17004). */
+function lowerProgramJsx7(
+ program: ts.Program,
+ options: ts.Ts7CompilerOptions,
+): { sources: Map; refusal: ScrDiagnostic | null } {
+ const sources = new Map();
+ const mode = jsxEmitName(options.jsx as string | number | undefined);
+ if (mode === undefined) return { sources, refusal: null };
+ for (const sf of program.getImplementationSourceFiles()) {
+ if (sf.isDeclarationFile || /\.[mc]?ts$/.test(sf.fileName) || !containsJsx(sf)) continue;
+ const at = firstJsx(sf)!;
+ const loc = { file: sf.fileName, start: ts.tokenStartOfNode(at), end: at.end };
+ if (mode === "preserve" || mode === "react-native") {
+ return {
+ sources,
+ refusal: {
+ code: "SC1090",
+ message: `JSX with "jsx": "${mode}" is not supported: that setting keeps JSX in the output instead of lowering it to calls`,
+ loc,
+ hint: 'set "jsx" to "react", "react-jsx" or "react-jsxdev" in the project tsconfig',
+ },
+ };
+ }
+ const plan = jsxEmitPlan(
+ {
+ jsx: mode,
+ jsxFactory: options.jsxFactory as string | undefined,
+ jsxFragmentFactory: options.jsxFragmentFactory as string | undefined,
+ jsxImportSource: options.jsxImportSource as string | undefined,
+ reactNamespace: options.reactNamespace as string | undefined,
+ },
+ jsxPragmas(sf.text),
+ );
+ sources.set(sf.fileName, lowerJsx(sf, plan));
+ }
+ return { sources, refusal: null };
+}
+
function loadProgram7(
host: ts.Ts7Host,
entryPath: string,
@@ -859,6 +921,26 @@ function loadProgram7(
};
program = host.createProgram([...programRoots, overridesDtsPath()], options);
}
+ // JSX: the program above checks the files as written; the program the
+ // lowering compiles holds them with their JSX lowered.
+ const jsx = lowerProgramJsx7(program, options);
+ let tscWorld: LoadResult["tscWorld"];
+ if (jsx.refusal !== null) {
+ config.diags.push(jsx.refusal);
+ } else if (jsx.sources.size > 0) {
+ const original = program;
+ // The project world must read the original text too, so it is built
+ // before the rewritten files are served, and only when it is needed.
+ const diagnostics = ts.getPreEmitDiagnostics(original);
+ const errors = diagnostics.some((d) => d.category === ts.DiagnosticCategory.Error);
+ tscWorld = {
+ program: original,
+ diagnostics,
+ projectWorld: errors ? host.createProgram(programRoots, options) : null,
+ };
+ for (const [file, lowered] of jsx.sources) host.addVirtualFile(file, lowered.text);
+ program = host.createProgram([...programRoots, overridesDtsPath()], options);
+ }
const entry = program.getSourceFile(entryPath);
if (!entry) throw new Error(`could not load ${entryPath}`);
const externalTypeSpecifiersByFile = externalTypeFileClosure7(program, externalTypes);
@@ -876,10 +958,14 @@ function loadProgram7(
},
externalTypes,
externalTypeSpecifiersByFile,
+ ...(tscWorld === undefined ? {} : { jsxSources: jsx.sources }),
+ ...(tscWorld === undefined ? {} : { tscWorld }),
projectWorld: () => (projectWorld ??= host.createProgram(programRoots, options)),
disposeAll: () => {
try {
projectWorld?.dispose();
+ tscWorld?.projectWorld?.dispose();
+ tscWorld?.program.dispose();
} finally {
program.dispose();
}
@@ -4665,8 +4751,8 @@ function preflight7(load: LoadResult): {
const sf = p.getSourceFile(d.fileName);
return sf !== undefined && isCheckedJsSourceFile(sf, projectCheckJs);
};
- const errorsOf = (p: ts.Program): ts.Diagnostic[] => {
- const all = ts.getPreEmitDiagnostics(p);
+ const errorsOf = (p: ts.Program): ts.Diagnostic[] => keptErrors(p, ts.getPreEmitDiagnostics(p));
+ const keptErrors = (p: ts.Program, all: readonly ts.Diagnostic[]): ts.Diagnostic[] => {
// First pass: every comment-side 2300's (file, name) — the partners
// the second pass forgives.
const commentDup = new Set();
@@ -4706,9 +4792,39 @@ function preflight7(load: LoadResult): {
// instead (the SC1100 checked-cast family). Dirty there → the project-
// world errors are the ones reported: they are reproducible with the
// project's own tsc, which is what "fix type errors first" asks for.
- const tscErrors = errorsOf(program);
+ // With JSX sources, the program over the original text answers the tsc
+ // gate; its positions move into the rewritten text the program holds.
+ // Errors only the rewritten text has come from the calls the JSX lowers
+ // to (a factory whose signature does not accept them).
+ const tscWorld = load.tscWorld;
+ const tscErrors =
+ tscWorld === undefined ? errorsOf(program) : keptErrors(tscWorld.program, tscWorld.diagnostics);
if (tscErrors.length > 0) {
- for (const d of errorsOf(load.projectWorld())) diags.push(toPassthrough(d));
+ const world =
+ tscWorld === undefined ? load.projectWorld() : (tscWorld.projectWorld ?? tscWorld.program);
+ for (const d of errorsOf(world)) {
+ const diag = toPassthrough(d);
+ const lowered = tscWorld === undefined ? undefined : load.jsxSources?.get(diag.loc.file);
+ if (lowered !== undefined) {
+ diag.loc = {
+ ...diag.loc,
+ start: lowered.rewrittenPosition(diag.loc.start, false),
+ end: lowered.rewrittenPosition(diag.loc.end, true),
+ };
+ }
+ diags.push(diag);
+ }
+ } else if (load.jsxSources !== undefined) {
+ for (const file of load.jsxSources.keys()) {
+ const sf = program.getSourceFile(file);
+ if (sf === undefined) continue;
+ const own = [...program.getSyntacticDiagnostics(sf), ...program.getSemanticDiagnostics(sf)];
+ for (const d of keptErrors(program, own)) {
+ const diag = toPassthrough(d);
+ diag.message = `${diag.message} (in the call this JSX lowers to)`;
+ diags.push(diag);
+ }
+ }
}
// Preflight and lowering share this CheckerFacade. Claim executable
diff --git a/packages/compiler/src/frontend/ts7/checker.ts b/packages/compiler/src/frontend/ts7/checker.ts
index 4f2d60294..cd43636af 100644
--- a/packages/compiler/src/frontend/ts7/checker.ts
+++ b/packages/compiler/src/frontend/ts7/checker.ts
@@ -45,7 +45,7 @@ import type {
TypePredicate,
TypeReference,
} from "./semantic-types.js";
-import type { SemanticChecker as Checker } from "./semantic-checker.js";
+import { NODE_BATCH_QUERIES, type SemanticChecker as Checker } from "./semantic-checker.js";
import { symbolNameOf, type SemanticProject as Project } from "./semantic-model.js";
import { isTypeNode, walkPreorder } from "./ast.js";
import { SignatureKind, SyntaxKind, TypeFlags, type SymbolFlags } from "./enums.js";
@@ -79,6 +79,30 @@ function withPanicFence(
}
}
+/** Answers for `items` in chunks, each answer given to `store`. A chunk
+ * that throws is split until the throwing items are alone; those get no
+ * answer, so their direct query throws again where it is made. */
+function batchAnswers(
+ items: readonly I[],
+ fetch: (chunk: readonly I[]) => readonly (O | undefined)[],
+ store: (item: I, answer: O | undefined) => void,
+): void {
+ const answer = (chunk: readonly I[]): void => {
+ let answers: readonly (O | undefined)[];
+ try {
+ answers = fetch(chunk);
+ } catch {
+ if (chunk.length === 1) return;
+ const mid = chunk.length >> 1;
+ answer(chunk.slice(0, mid));
+ answer(chunk.slice(mid));
+ return;
+ }
+ chunk.forEach((item, index) => store(item, answers[index]));
+ };
+ for (let i = 0; i < items.length; i += BATCH_CHUNK) answer(items.slice(i, i + BATCH_CHUNK));
+}
+
function chunked(items: readonly T[], fetch: (chunk: readonly T[]) => readonly R[]): R[] {
const out: R[] = [];
for (let i = 0; i < items.length; i += BATCH_CHUNK) {
@@ -322,7 +346,51 @@ export class CheckerFacade {
prefetchRoots(roots: readonly Node[]): void {
this.ensureActive();
this.markManaged(roots);
- this.prefetchNodes(collectNodes(roots, "reachable"));
+ const nodes = collectNodes(roots, "reachable");
+ this.prefetchNodes(nodes);
+ if (!NODE_BATCH_QUERIES) return;
+ // Lowering a call resolves its signature (reconcileOverloadReturn).
+ this.prefetchResolvedSignatures(
+ nodes.filter(
+ (node) =>
+ node.kind === SyntaxKind.CallExpression ||
+ node.kind === SyntaxKind.TaggedTemplateExpression,
+ ),
+ );
+ // Lowering an array literal or a conditional asks its contextual type.
+ this.prefetchContextualTypes(
+ nodes.filter(
+ (node) =>
+ node.kind === SyntaxKind.ArrayLiteralExpression ||
+ node.kind === SyntaxKind.ConditionalExpression,
+ ),
+ );
+ }
+
+ /** Batches the contextual type of each node, for an analysis that asks it
+ * of many nodes (class-protocols.ts). A query that fails stays out of the
+ * memo, so a later getContextualType asks again as it would have. */
+ prefetchContextualTypes(nodes: readonly Node[]): void {
+ if (!NODE_BATCH_QUERIES) return;
+ this.ensureActive();
+ const missing = [...new Set(nodes)].filter((node) => !this.cache.contextualType.has(node));
+ batchAnswers(
+ missing,
+ (chunk) => this.raw.getContextualTypes(chunk),
+ (node, type) => this.cache.contextualType.set(node, type),
+ );
+ }
+
+ /** Batches the resolved signature of each call (getResolvedSignature). */
+ prefetchResolvedSignatures(nodes: readonly Node[]): void {
+ if (!NODE_BATCH_QUERIES) return;
+ this.ensureActive();
+ const missing = [...new Set(nodes)].filter((node) => !this.cache.resolvedSignature.has(node));
+ batchAnswers(
+ missing,
+ (chunk) => this.raw.getResolvedSignatures(chunk),
+ (node, signature) => this.cache.resolvedSignature.set(node, signature),
+ );
}
/** Batches symbol queries for every identifier under roots without the
diff --git a/packages/compiler/src/frontend/ts7/enums.generated.ts b/packages/compiler/src/frontend/ts7/enums.generated.ts
index f21134bb6..4551ab428 100644
--- a/packages/compiler/src/frontend/ts7/enums.generated.ts
+++ b/packages/compiler/src/frontend/ts7/enums.generated.ts
@@ -1315,3 +1315,25 @@ export enum LanguageVariant {
Standard = 0,
JSX = 1
}
+
+export enum JsxEmit {
+ None = 0,
+ Preserve = 1,
+ ReactNative = 2,
+ React = 3,
+ ReactJSX = 4,
+ ReactJSXDev = 5
+}
+
+/** Numeric lookup with the SDK's aliases and undefined for unknown values. */
+export function jsxEmitName(value: number): string | undefined {
+ switch (value) {
+ case 0: return "None";
+ case 1: return "Preserve";
+ case 2: return "ReactNative";
+ case 3: return "React";
+ case 4: return "ReactJSX";
+ case 5: return "ReactJSXDev";
+ default: return undefined;
+ }
+}
diff --git a/packages/compiler/src/frontend/ts7/enums.ts b/packages/compiler/src/frontend/ts7/enums.ts
index 6eba358f2..844643e80 100644
--- a/packages/compiler/src/frontend/ts7/enums.ts
+++ b/packages/compiler/src/frontend/ts7/enums.ts
@@ -1,4 +1,25 @@
/** Runtime enums generated from the pinned TypeScript 7 declarations.
* Their nominal names and values match the SDK; TypeScript 5 remains a
* separate world. No package loader runs on the compiler's native path. */
+import { jsxEmitName } from "./enums.generated.js";
+
export * from "./enums.generated.js";
+
+/** The tsconfig spelling of a `jsx` option value: "preserve", "react-native",
+ * "react", "react-jsx" or "react-jsxdev". */
+export function jsxOptionName(value: number): string | undefined {
+ switch (jsxEmitName(value)) {
+ case "Preserve":
+ return "preserve";
+ case "ReactNative":
+ return "react-native";
+ case "React":
+ return "react";
+ case "ReactJSX":
+ return "react-jsx";
+ case "ReactJSXDev":
+ return "react-jsxdev";
+ default:
+ return undefined;
+ }
+}
diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts
index cc867d208..c87da0bfe 100644
--- a/packages/compiler/src/frontend/ts7/program-host.ts
+++ b/packages/compiler/src/frontend/ts7/program-host.ts
@@ -30,6 +30,7 @@ import type {
} from "./session.js";
import { CheckerFacade } from "./checker.js";
import {
+ jsxOptionName,
moduleDetectionKindName,
moduleKindName,
moduleResolutionKindName,
@@ -82,6 +83,9 @@ function serializeOptions(options: Ts7CompilerOptions): Record
case "moduleDetection":
out[key] = moduleDetectionKindName(value as number)?.toLowerCase() ?? value;
break;
+ case "jsx":
+ out[key] = typeof value === "number" ? (jsxOptionName(value) ?? value) : value;
+ break;
case "lib":
// 5.9.3 spells lib entries "lib.es2025.d.ts"; tsconfig wants "es2025".
out[key] = (value as string[]).map((lib) =>
diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.ts b/packages/compiler/src/frontend/ts7/semantic-checker.ts
index 5e7f8339a..d1d42c6e1 100644
--- a/packages/compiler/src/frontend/ts7/semantic-checker.ts
+++ b/packages/compiler/src/frontend/ts7/semantic-checker.ts
@@ -96,6 +96,13 @@ export interface SemanticCompletion {
symbol: SemanticSymbol | undefined;
}
+/** Whether lowering asks the checker for contextual types and resolved
+ * signatures in batches (`getContextualTypes`, `getResolvedSignatures`).
+ * Off until scriptc depends on a tsc-ts release that answers them: the
+ * batch prefetches (checker.ts) then do nothing and lowering asks node by
+ * node. */
+export const NODE_BATCH_QUERIES = false;
+
/** The pinned checker's request surface over native objects. Every query
* converges on the same snapshot/project registries, including batch and
* lazy-object queries. No SDK object is constructed or consulted here. */
@@ -201,6 +208,10 @@ export class SemanticChecker {
getContextualType(node: AstNode): SemanticType | undefined {
return this.type("getContextualType", { location: node.id });
}
+ /** getContextualType for each node. */
+ getContextualTypes(nodes: readonly AstNode[]): (SemanticType | undefined)[] {
+ return this.optionalTypes("getContextualTypes", { locations: nodes.map((node) => node.id) });
+ }
getTypeFromTypeNode(node: AstNode): SemanticType | undefined {
return this.type("getTypeFromTypeNode", { location: node.id });
}
@@ -213,6 +224,12 @@ export class SemanticChecker {
getResolvedSignature(node: AstNode): SemanticSignature | undefined {
return this.signature("getResolvedSignature", { location: node.id });
}
+ /** getResolvedSignature for each node. */
+ getResolvedSignatures(nodes: readonly AstNode[]): (SemanticSignature | undefined)[] {
+ return this.project.requestSignatures("getResolvedSignatures", {
+ locations: nodes.map((node) => node.id),
+ });
+ }
getSignatureFromDeclaration(node: AstNode): SemanticSignature | undefined {
return this.signature("getSignatureFromDeclaration", { location: node.id });
}
diff --git a/packages/compiler/src/frontend/tsc-codes.ts b/packages/compiler/src/frontend/tsc-codes.ts
index fe48c8855..e6c0be7cd 100644
--- a/packages/compiler/src/frontend/tsc-codes.ts
+++ b/packages/compiler/src/frontend/tsc-codes.ts
@@ -27,6 +27,13 @@ export const ADOPTED_OPTIONS = [
// SC1012 "not supported yet" beats a raw TS1259 at the same site.
"esModuleInterop",
"allowSyntheticDefaultImports",
+ // JSX: the checker types JSX as the project's tsc does, and the JSX
+ // lowering (jsx-source.ts) emits the calls that setting selects.
+ "jsx",
+ "jsxFactory",
+ "jsxFragmentFactory",
+ "jsxImportSource",
+ "reactNamespace",
] as const;
export const JS_RELAXED_TSC_CODES: ReadonlySet = new Set([
diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts
index 9abd5a0b0..e6c5c4c93 100644
--- a/packages/compiler/src/frontend/type-mapper.ts
+++ b/packages/compiler/src/frontend/type-mapper.ts
@@ -147,6 +147,15 @@ export const WEBCRYPTO_DYN_TYPES: ReadonlySet = new Set([
"KeyAlgorithm",
"HmacImportParams",
"HmacKeyAlgorithm",
+ "CryptoKeyPair",
+ "EcKeyAlgorithm",
+ "RsaHashedKeyAlgorithm",
+ "EcKeyGenParams",
+ "EcKeyImportParams",
+ "EcdsaParams",
+ "RsaHashedKeyGenParams",
+ "RsaHashedImportParams",
+ "RsaPssParams",
"HmacKeyGenParams",
"AesKeyGenParams",
"AesKeyAlgorithm",
@@ -159,6 +168,28 @@ export const WEBCRYPTO_DYN_TYPES: ReadonlySet = new Set([
"AesDerivedKeyParams",
]);
+/** node:crypto's key and signature option interfaces: the key functions,
+ * KeyObject.export and sign/verify take them as checked-dynamic arguments
+ * (scr_lib.c, scr_pki.c). */
+export const CRYPTO_KEY_OPTION_DYN_TYPES: ReadonlySet = new Set([
+ "AsymmetricKeyExportOptions",
+ "ECKeyPairKeyObjectOptions",
+ "ECKeyPairOptions",
+ "ED25519KeyPairOptions",
+ "JsonWebKeyInput",
+ "JwkKeyExportOptions",
+ "KeyExportOptions",
+ "PrivateKeyInput",
+ "PublicKeyInput",
+ "RSAKeyPairKeyObjectOptions",
+ "RSAKeyPairOptions",
+ "SignKeyObjectInput",
+ "SignPrivateKeyInput",
+ "SigningOptions",
+ "VerifyKeyObjectInput",
+ "VerifyPublicKeyInput",
+]);
+
export const FETCH_AMBIENT_TYPES = [
"Response",
"Request",
@@ -2987,9 +3018,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
) {
return DYN;
}
- // node:crypto's KeyObject: secret keys are native handles (scr_lib.c)
- // whose getters and methods dispatch through the handle. The JsonWebKey
- // that export() returns is a plain checked-dynamic object.
+ // node:crypto's KeyObject: secret keys (scr_lib.c) and private and
+ // public keys (scr_pki.c) are native handles whose getters and methods
+ // dispatch through the handle. The JsonWebKey that export() returns is a
+ // plain checked-dynamic object.
if (
((psym !== undefined && ts.symbolName(psym) === "KeyObject") ||
(psym !== undefined && ts.symbolName(psym) === "JsonWebKey")) &&
@@ -3001,6 +3033,35 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
) {
return DYN;
}
+ // node:crypto's key and signature options are plain checked-dynamic
+ // objects, so a value asserted to one (`opts as
+ // crypto.AsymmetricKeyExportOptions<"pem">`) reaches the call unchanged.
+ if (
+ psym !== undefined &&
+ CRYPTO_KEY_OPTION_DYN_TYPES.has(ts.symbolName(psym)) &&
+ psym.declarations.some(
+ (d) =>
+ ts.isInterfaceDeclaration(d) &&
+ ctx.isStdlibFile(ts.getSourceFileOfNode(d)) &&
+ isDeclaredInAmbientModule(d, "crypto"),
+ )
+ ) {
+ return DYN;
+ }
+ // node:crypto's Sign and Verify (createSign/createVerify): native
+ // handles over the digest state (scr_pki.c).
+ if (
+ psym !== undefined &&
+ (ts.symbolName(psym) === "Sign" || ts.symbolName(psym) === "Verify") &&
+ psym.declarations.some(
+ (d) =>
+ ts.isClassDeclaration(d) &&
+ ctx.isStdlibFile(ts.getSourceFileOfNode(d)) &&
+ isDeclaredInAmbientModule(d, "crypto"),
+ )
+ ) {
+ return DYN;
+ }
if (
psym &&
(FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(psym)) &&
@@ -4267,6 +4328,41 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
};
}
}
+ // crypto.generateKeyPairSync's results: KeyPairKeyObjectResult and
+ // KeyPairSyncResult as { publicKey, privateKey } records over the
+ // mapped property types (KeyObjects, PEM strings or DER Buffers), in
+ // Node's key order.
+ if (
+ psym !== undefined &&
+ (ts.symbolName(psym) === "KeyPairKeyObjectResult" ||
+ ts.symbolName(psym) === "KeyPairSyncResult") &&
+ psym.declarations.some(
+ (d) =>
+ ts.isInterfaceDeclaration(d) &&
+ ctx.isStdlibFile(ts.getSourceFileOfNode(d)) &&
+ isDeclaredInAmbientModule(d, "crypto"),
+ )
+ ) {
+ const field = (name: string): IrType | null => {
+ const prop = ctx.checker.getPropertyOfType(type, name);
+ return prop ? mapType(ctx.checker.getTypeOfSymbol(prop), ctx) : null;
+ };
+ const publicKey = field("publicKey");
+ const privateKey = field("privateKey");
+ if (!publicKey || !privateKey) return null;
+ return {
+ kind: "record",
+ shapeId: ctx.shapes.intern(
+ [
+ { name: "privateKey", type: privateKey },
+ { name: "publicKey", type: publicKey },
+ ],
+ false,
+ undefined,
+ ["publicKey", "privateKey"],
+ ),
+ };
+ }
// crypto.X509Certificate — the Dirent-style data record: THREE visible
// fields (fingerprint and the validFrom/validTo validity window, all
// computed at construction by the new-expression lowering). Module-
diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts
index 2b216c77d..73879efb4 100644
--- a/packages/compiler/src/index.ts
+++ b/packages/compiler/src/index.ts
@@ -49,7 +49,8 @@ import { frontendProcessExecArgv } from "./frontend-process-args.js";
import { type ChildProcess, spawn } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync } from "node:fs";
-import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
+import { copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
import { basename, dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
@@ -524,6 +525,9 @@ async function compileExecutableNative(
onArtifactReady?: NonNullable[0]["onArtifactReady"]>,
programPartitions: readonly string[] = [],
profileGenerate = false,
+ /** The kept LLVM text's shared name, when `llvmPath` is the build's private copy of it
+ * (BuildSources): the spelling clang sees and native cache keys name. */
+ llvmVisiblePath?: string,
): Promise {
const programIsObject = /\.(?:o|obj)$/.test(llvmPath);
const runtimePackTarget = programIsObject && !sanitize ? nativeCodegenTarget() : null;
@@ -563,7 +567,9 @@ async function compileExecutableNative(
if (programIsObject)
throw new InternalCompilerError("program objects must link against a precompiled runtime pack");
await compileExternalC({
- cPath: llvmPath,
+ ...(llvmVisiblePath === undefined || llvmVisiblePath === llvmPath
+ ? { cPath: llvmPath }
+ : { cPath: llvmVisiblePath, cSourcePath: llvmPath }),
outPath,
cacheIdentity: "scriptc-generated-v1",
...optimizationField(features.optimization),
@@ -624,6 +630,8 @@ async function compileExecutableNative(
tlsCa: features.tlsCa,
cipher: features.cipher,
ecdh: features.ecdh,
+ pki: features.pki,
+ ed25519: features.ed25519,
webcrypto: features.webcrypto,
perfLoop: features.perfLoop,
...(onArtifactReady === undefined ? {} : { onArtifactReady }),
@@ -729,8 +737,12 @@ function runtimePackDiagnostic(error: RuntimePackError, entryPath: string): ScrD
interface PreparedExecutable {
llvmSource: LlvmInput;
+ /** The kept LLVM text beside the executable, installed when the build ends. */
llvmPath: string;
+ /** This build's private copy of the LLVM text (BuildSources). */
+ llvmSourcePath: string;
irPath: string | undefined;
+ irSourcePath: string | undefined;
nativeFeatures: EarlyExecutableNativeFeatures;
programSplit: ReturnType;
sourceTexts: Map;
@@ -841,6 +853,8 @@ async function prepareExecutableInput(
await mkdir(opts.outDir, { recursive: true });
const llvmPath = defaultSourcePaths.llvm;
+ // The extension keeps clang reading the copy as LLVM IR rather than as a linker input.
+ const llvmSourcePath = `${privateSiblingPath(llvmPath, "llvm")}.ll`;
const backend = "llvm" as const;
const useRuntimePack =
opts.nativeProgramObject === true || usesPrecompiledRuntimePack(opts, backend);
@@ -863,12 +877,14 @@ async function prepareExecutableInput(
return { ok: false, diagnostics: [llvmRefusalDiag(err, entryPath)], sourceTexts };
}
timing("llvm-emit");
- await writeLlvmSource(llvmPath, llvmSource);
+ await writeLlvmSource(llvmSourcePath, llvmSource);
timing("llvm-write");
let irPath: string | undefined;
+ let irSourcePath: string | undefined;
if (opts.emitIr) {
irPath = defaultSourcePaths.ir;
- await writeTextParts(irPath, serializeModuleParts(mod));
+ irSourcePath = privateSiblingPath(irPath, "ir");
+ await writeTextParts(irSourcePath, serializeModuleParts(mod));
}
const nativeFeatures = executableNativeFeatures(mod, backend, opts.optimization ?? "release");
@@ -882,7 +898,87 @@ async function prepareExecutableInput(
? splitLlvmProgram(llvmSource)
: null;
timing("llvm-split");
- return { llvmSource, llvmPath, irPath, nativeFeatures, programSplit, sourceTexts };
+ return {
+ llvmSource,
+ llvmPath,
+ llvmSourcePath,
+ irPath,
+ irSourcePath,
+ nativeFeatures,
+ programSplit,
+ sourceTexts,
+ };
+}
+
+/**
+ * The kept LLVM text (`.ll`) and IR (`.ir.json`) sit beside the executable,
+ * so programs whose entries share a name and are built into one output directory share them. Each
+ * build therefore writes, compiles and caches private copies (privateSiblingPath) and moves them to
+ * the shared names only when it is done (rename replaces a file in one step): a concurrent build of
+ * another program cannot replace the text a build compiles or the translation unit it caches.
+ * Builds that compile with the C driver install them before the native build instead
+ * (stageDriverBuildSources).
+ */
+interface BuildSources {
+ llvmPath: string;
+ llvmSourcePath: string;
+ irPath?: string | undefined;
+ irSourcePath?: string | undefined;
+ /** The shared name already holds the private copy's bytes (a cache hit's restored files): it is
+ * left as it is, with its time stamps. */
+ llvmPathCurrent?: boolean | undefined;
+ irPathCurrent?: boolean | undefined;
+}
+
+/** Moves a build's private copies to the shared names, or drops them when that fails. */
+async function installBuildSources(sources: BuildSources): Promise {
+ const install = async (
+ source: string | undefined,
+ destination: string | undefined,
+ current: boolean | undefined,
+ ) => {
+ if (source === undefined || destination === undefined || source === destination) return;
+ if (current === true) await rm(source, { force: true });
+ else await rename(source, destination).catch(() => rm(source, { force: true }));
+ };
+ await Promise.all([
+ install(sources.llvmSourcePath, sources.llvmPath, sources.llvmPathCurrent),
+ install(sources.irSourcePath, sources.irPath, sources.irPathCurrent),
+ ]);
+}
+
+/**
+ * A build that compiles the LLVM text with the C driver (no precompiled runtime pack) records the
+ * output directory in its native cache proofs, so that directory has to be in its final state before
+ * the native build starts. The build therefore installs the shared names first and compiles and
+ * caches copies in a private directory outside the output directory. Returns that directory and the
+ * copies.
+ */
+async function stageDriverBuildSources(
+ sources: BuildSources,
+): Promise<{ dir: string; sources: BuildSources }> {
+ const dir = await mkdtemp(join(tmpdir(), "scriptc-program-"));
+ const llvmSourcePath = join(dir, basename(sources.llvmPath));
+ const irSourcePath =
+ sources.irPath === undefined || sources.irSourcePath === undefined
+ ? undefined
+ : join(dir, basename(sources.irPath));
+ try {
+ await Promise.all([
+ copyFile(sources.llvmSourcePath, llvmSourcePath),
+ irSourcePath === undefined || sources.irSourcePath === undefined
+ ? undefined
+ : copyFile(sources.irSourcePath, irSourcePath),
+ ]);
+ } catch (err) {
+ await rm(dir, { recursive: true, force: true });
+ throw err;
+ }
+ await installBuildSources(sources);
+ return {
+ dir,
+ sources: { llvmPath: sources.llvmPath, llvmSourcePath, irPath: sources.irPath, irSourcePath },
+ };
}
/**
@@ -976,10 +1072,11 @@ type FrontendProcessResult =
| {
kind: "prepared";
llvmPath: string;
- /** A private copy of the LLVM text, which the native helper reads (`nativeInputPath`). */
+ /** The build's private copy of the LLVM text (BuildSources), which the native helper reads. */
nativeInputPath: string;
llvmBytes: number;
irPath: string | undefined;
+ irSourcePath: string | undefined;
nativeFeatures: EarlyExecutableNativeFeatures;
sourceTexts: [string, string][];
warnings: ScrDiagnostic[];
@@ -1047,7 +1144,9 @@ async function prepareExecutableInputInChild(
return {
llvmSource: { file: result.nativeInputPath, bytes: result.llvmBytes },
llvmPath: result.llvmPath,
+ llvmSourcePath: result.nativeInputPath,
irPath: result.irPath,
+ irSourcePath: result.irSourcePath,
nativeFeatures: result.nativeFeatures,
programSplit: null,
sourceTexts,
@@ -1085,18 +1184,15 @@ export async function runFrontendProcess(requestPath: string, resultPath: string
);
const frontend = frontendInputs.snapshot();
if (!("ok" in prepared)) {
- // The public .ll file may be rewritten by a concurrent build of another entry with the
- // same output stem before the native helper reads it (see emitNativeArtifact).
if (isLlvmFile(prepared.llvmSource))
throw new InternalCompilerError("in-process preparation returned LLVM text as a file");
- const nativeInputPath = privateSiblingPath(prepared.llvmPath, "frontend-llvm");
- await writeLlvmSource(nativeInputPath, prepared.llvmSource, 0o600);
result = {
kind: "prepared",
llvmPath: prepared.llvmPath,
- nativeInputPath,
+ nativeInputPath: prepared.llvmSourcePath,
llvmBytes: llvmInputBytes(prepared.llvmSource),
irPath: prepared.irPath,
+ irSourcePath: prepared.irSourcePath,
nativeFeatures: prepared.nativeFeatures,
sourceTexts: [...prepared.sourceTexts],
warnings: warnings.warnings,
@@ -1450,37 +1546,54 @@ async function compileTracked(
...(earlyHit.irPath === undefined ? {} : { irPath: earlyHit.irPath }),
};
}
- let nativeInputPath = earlyHit.llvmPath;
+ // The build's private copies of the cached LLVM text and IR (BuildSources).
+ const hitSources: BuildSources = {
+ llvmPath: earlyHit.llvmPath,
+ llvmSourcePath: earlyHit.llvmSourcePath ?? earlyHit.llvmPath,
+ irPath: earlyHit.irPath,
+ irSourcePath: earlyHit.irSourcePath,
+ llvmPathCurrent: earlyHit.llvmPathCurrent,
+ irPathCurrent: earlyHit.irPathCurrent,
+ };
+ // What the native build compiles and the cache publishes: hitSources, or their staged copies.
+ let nativeSources = hitSources;
+ let stagedDir: string | null = null;
+ let nativeInputPath = hitSources.llvmSourcePath;
let nativeProgramObject: NativeProgramObject | null = null;
const useRuntimePack =
opts.nativeProgramObject === true ||
usesPrecompiledRuntimePack(opts, earlyHit.native.backend);
- if (useRuntimePack) {
- if (earlyHit.native.backend !== "llvm") {
- throw new InternalCompilerError(
- "native program-object cache hit restored a non-LLVM translation unit",
- );
- }
- try {
- nativeProgramObject = await emitNativeProgramObject(
- entryPath,
- opts,
- // A large program's LLVM exceeds the engine's string limit as one
- // string; the emitter takes it in parts, as on a cold build.
- await readTextParts(earlyHit.llvmPath),
- profile,
- );
- nativeInputPath = nativeProgramObject.linkPath;
- } catch (err) {
- if (!(err instanceof NativeCodegenError)) throw err;
- return {
- ok: false,
- diagnostics: [nativeCodegenDiag(err.diagnosticCode, err.message, entryPath)],
- sourceTexts: new Map(),
- };
- }
- }
try {
+ if (useRuntimePack) {
+ if (earlyHit.native.backend !== "llvm") {
+ throw new InternalCompilerError(
+ "native program-object cache hit restored a non-LLVM translation unit",
+ );
+ }
+ try {
+ nativeProgramObject = await emitNativeProgramObject(
+ entryPath,
+ opts,
+ // A large program's LLVM exceeds the engine's string limit as one
+ // string; the emitter takes it in parts, as on a cold build.
+ await readTextParts(hitSources.llvmSourcePath),
+ profile,
+ );
+ nativeInputPath = nativeProgramObject.linkPath;
+ } catch (err) {
+ if (!(err instanceof NativeCodegenError)) throw err;
+ return {
+ ok: false,
+ diagnostics: [nativeCodegenDiag(err.diagnosticCode, err.message, entryPath)],
+ sourceTexts: new Map(),
+ };
+ }
+ } else {
+ const staged = await stageDriverBuildSources(hitSources);
+ stagedDir = staged.dir;
+ nativeSources = staged.sources;
+ nativeInputPath = nativeSources.llvmSourcePath;
+ }
await compileExecutableNative(
earlyHit.native,
nativeInputPath,
@@ -1496,6 +1609,10 @@ async function compileTracked(
: async ({ dependencies }) => {
await publishEarlyExecutableCache(cacheRoot, executableCacheOptions, {
...earlyHit,
+ llvmPath: nativeSources.llvmSourcePath,
+ ...(nativeSources.irSourcePath === undefined
+ ? {}
+ : { irPath: nativeSources.irSourcePath }),
executableRestored: true,
nativeDependencies: dependencies,
frontend: earlyHit.frontend,
@@ -1503,6 +1620,7 @@ async function compileTracked(
},
nativeProgramObject?.partitionPaths,
profile?.generate !== undefined,
+ hitSources.llvmPath,
);
if (nativeProgramObject !== null && opts.nativeProgramObject === true) {
await rename(nativeProgramObject.linkPath, nativeProgramObject.artifactPath);
@@ -1532,6 +1650,8 @@ async function compileTracked(
nativeProgramObject.partitionPaths,
);
}
+ if (stagedDir === null) await installBuildSources(hitSources);
+ else await rm(stagedDir, { recursive: true, force: true });
}
await pruneBuildCache(cacheRoot);
return {
@@ -1564,7 +1684,17 @@ async function compileTracked(
)
: await prepareExecutableInput(entryPath, opts, ffi, buildPlatform, timing, warningSink);
if ("ok" in prepared) return prepared;
- const { llvmSource, llvmPath, irPath, nativeFeatures, programSplit, sourceTexts } = prepared;
+ const buildSources: BuildSources = {
+ llvmPath: prepared.llvmPath,
+ llvmSourcePath: prepared.llvmSourcePath,
+ irPath: prepared.irPath,
+ irSourcePath: prepared.irSourcePath,
+ };
+ const { llvmSource, llvmPath, irPath } = prepared;
+ const { nativeFeatures, programSplit, sourceTexts } = prepared;
+ // What the native build compiles and the cache publishes: buildSources, or their staged copies.
+ let { llvmSourcePath, irSourcePath } = buildSources;
+ let stagedDir: string | null = null;
const backend = "llvm" as const;
await mkdir(dirname(opts.outPath), { recursive: true });
if (earlyCacheOptions === null) {
@@ -1596,10 +1726,14 @@ async function compileTracked(
sourceTexts,
};
}
+ } else {
+ const staged = await stageDriverBuildSources(buildSources);
+ stagedDir = staged.dir;
+ ({ llvmSourcePath, irSourcePath } = staged.sources);
}
await compileExecutableNative(
nativeFeatures,
- nativeProgramObject?.linkPath ?? llvmPath,
+ nativeProgramObject?.linkPath ?? llvmSourcePath,
opts.outPath,
opts.sanitize ?? false,
ffi,
@@ -1611,23 +1745,34 @@ async function compileTracked(
? undefined
: async ({ dependencies }) => {
await publishEarlyExecutableCache(cacheRoot, executableCacheOptions, {
- llvmPath,
+ llvmPath: llvmSourcePath,
native: nativeFeatures,
executableRestored: true,
nativeDependencies: dependencies,
frontend: frontendInputs.snapshot(),
- ...(irPath === undefined ? {} : { irPath }),
+ ...(irSourcePath === undefined ? {} : { irPath: irSourcePath }),
...performanceWarnings(warningSink),
});
publishedExecutable = true;
},
nativeProgramObject?.partitionPaths,
profile?.generate !== undefined,
+ llvmPath,
);
timing("native-link");
if (nativeProgramObject !== null && opts.nativeProgramObject === true) {
await rename(nativeProgramObject.linkPath, nativeProgramObject.artifactPath);
}
+ if (!publishedExecutable) {
+ await publishEarlyExecutableCache(cacheRoot, executableCacheOptions, {
+ llvmPath: llvmSourcePath,
+ native: nativeFeatures,
+ executableRestored: false,
+ frontend: frontendInputs.snapshot(),
+ ...(irSourcePath === undefined ? {} : { irPath: irSourcePath }),
+ ...performanceWarnings(warningSink),
+ }).catch(() => undefined);
+ }
} catch (err) {
if (err instanceof RuntimePackError) {
return { ok: false, diagnostics: [runtimePackDiagnostic(err, entryPath)], sourceTexts };
@@ -1649,18 +1794,8 @@ async function compileTracked(
nativeProgramObject.partitionPaths,
);
}
- // The frontend process's private copy of the LLVM text.
- if (isLlvmFile(llvmSource)) await rm(llvmSource.file, { force: true });
- }
- if (!publishedExecutable) {
- await publishEarlyExecutableCache(cacheRoot, executableCacheOptions, {
- llvmPath,
- native: nativeFeatures,
- executableRestored: false,
- frontend: frontendInputs.snapshot(),
- ...(irPath === undefined ? {} : { irPath }),
- ...performanceWarnings(warningSink),
- }).catch(() => undefined);
+ if (stagedDir === null) await installBuildSources(buildSources);
+ else await rm(stagedDir, { recursive: true, force: true });
}
await pruneBuildCache(cacheRoot);
timing("complete");
diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts
index dd82d06c2..c202b8538 100644
--- a/packages/compiler/src/ir/builtin-effects.ts
+++ b/packages/compiler/src/ir/builtin-effects.ts
@@ -230,6 +230,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([
"http2.createSecureServerDynCb",
"tls.connect",
"tls.connectCb",
+ "tls.wrapSocket",
+ "tls.wrapSocketCtx",
// The CA-store surface: an unknown getCACertificates type throws
// Node's ERR_INVALID_ARG_VALUE TypeError; setDefaultCACertificates
// throws Node's ERR_CRYPTO_OPERATION_FAILED on a certificate-free
@@ -358,6 +360,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([
"readable.pushU",
"readable.pushDyn",
"readable.unshift",
+ "readable.unshiftDyn",
"readable.unshiftStr",
"readable.read",
"readable.readDyn",
@@ -370,6 +373,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([
"writable.writeStr",
"writable.writeU",
"writable.writeDyn",
+ "writable.writeEnc",
"writable.end",
"writable.uncork",
"stream.destroy",
@@ -985,4 +989,12 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([
"crypto.x509ValidFromStr",
"crypto.x509ValidTo",
"crypto.x509ValidToStr",
+ "crypto.createPrivateKey",
+ "crypto.createPublicKey",
+ "crypto.generateKeyPairSync",
+ "crypto.generateKeyPairCb",
+ "crypto.signOneshot",
+ "crypto.verifyOneshot",
+ "crypto.createSign",
+ "crypto.createVerify",
]);
diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts
index 380c7cb7c..b3c0389af 100644
--- a/packages/compiler/src/ir/builtin-signatures.ts
+++ b/packages/compiler/src/ir/builtin-signatures.ts
@@ -2182,6 +2182,20 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
result: VOID,
},
"tls.sockOnSession": { argTypes: [NETSOCKET_T, null, BOOL], result: VOID },
+ /** The 'secure' event: the handshake completed (after secureConnect on
+ * a client). Plain sockets never fire it. */
+ "tls.sockOnSecure": {
+ argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }, BOOL],
+ result: VOID,
+ },
+ /** new tls.TLSSocket(socket, options) in server mode: a new TLS socket
+ * takes over the socket's connection (wrapSocketCtx passes a literal's
+ * secureContext member apart from the runtime options record). The
+ * record reads at runtime: isServer, cert, key, ALPNProtocols,
+ * minVersion and maxVersion are implemented, the client mode and other
+ * documented members throw the runtime fence. MAY THROW. */
+ "tls.wrapSocket": { argTypes: [NETSOCKET_T, DYN], result: NETSOCKET_T },
+ "tls.wrapSocketCtx": { argTypes: [NETSOCKET_T, DYN, SECURECTX_T], result: NETSOCKET_T },
// createSecureContext({ cert, key }) mints the opaque SNI-answer handle.
/** tls.createSecureContext({ cert, key }) — parses the PEM pair into an
* opaque SecureContext handle (secureCtx kind) for SNI callbacks to
@@ -2857,8 +2871,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
/** WebCrypto (scr_webcrypto.c): each method takes its argument vector as
* one dyn array and answers a promise that argument and validation
* failures reject. CryptoKey values are native handles. */
- "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
"webcrypto.generateKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
+ "webcrypto.importKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
"webcrypto.exportKey": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
"webcrypto.encrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
"webcrypto.decrypt": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
@@ -3877,7 +3891,11 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* readable.unpipe (recv[, dst]) → recv +1.
* writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb
* MOVES when present, called after the user write completes).
- * writable.end: (recv, flags[, chunk][, cb]) → recv +1. cork/uncork are
+ * writable.writeEnc: (recv, flags, chunk[, encoding][, cb]) — flags 1
+ * an encoding argument, 2 a callback (the encoding is then null, as
+ * Node's write shifts it).
+ * writable.end: (recv, flags[, chunk][, encoding][, cb]) → recv +1;
+ * flags 16: an encoding argument follows the dyn chunk. cork/uncork are
* void. stream.destroy/destroyErr: (recv[, err]) → recv +1.
* stream.prop: (recv, name-literal) → the flag/number the name asks
* for; stream.errored → Error|null union; readable.flowing →
@@ -4016,6 +4034,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"readable.pushU": { argTypes: [null, null], result: BOOL },
"readable.pushDyn": { argTypes: [null, DYN], result: BOOL },
"readable.unshift": { argTypes: [null, BYTES_U8], result: VOID },
+ "readable.unshiftDyn": { argTypes: [null, DYN], result: VOID },
"readable.unshiftStr": { argTypes: [null, STRING], result: VOID },
"stream.onDyn": { argTypes: [null, STRING, DYN, BOOL, BOOL], result: VOID },
"readable.readDyn": { argTypes: [null, DYN], result: DYN },
@@ -4027,6 +4046,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* Buffer.from(chunk, enc)); both carry the CANONICAL literal. */
"readable.pushStrEnc": { argTypes: [null, STRING, STRING], result: BOOL },
"readable.pushEncoding": { argTypes: [null, STRING], result: VOID },
+ /** The objectMode option's sides (readable, writable), applied right
+ * after construction; answers the receiver (+1, the chaining shape). */
+ "stream.objectMode": { argTypes: [null, BOOL, BOOL], result: VOID },
+ /** Whether a readable.nextChunkDyn step answered the end (the done
+ * marker; an object-mode stream can yield undefined). */
+ "stream.chunkDone": { argTypes: [DYN], result: BOOL },
/** for-await over a readable (the desugared loop's per-pass promise):
* +1 promise of the next chunk — buffered content, the EOF sentinel
* (empty Buffer / dyn undefined), or a rejection with the stream's
@@ -4080,6 +4105,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"writable.writeStr": { argTypes: [null, STRING], result: BOOL },
"writable.writeU": { argTypes: [null, null], result: BOOL },
"writable.writeDyn": { argTypes: [null, DYN], result: BOOL },
+ "writable.writeEnc": { argTypes: [null, F64, DYN], result: BOOL },
"writable.end": { argTypes: [null, F64], result: VOID },
"writable.cork": { argTypes: [null], result: VOID },
"writable.uncork": { argTypes: [null], result: VOID },
@@ -4665,6 +4691,19 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* body passes its undefined result on into a Buffer slot. Owned (+1);
* never throws. */
"bytes.absent": { argTypes: [], result: BYTES_U8 },
+ /** node:crypto asymmetric keys and signatures (scr_pki.c): checked-
+ * dynamic arguments, borrowed; KeyObject, Sign and Verify handles,
+ * Buffers and booleans as checked-dynamic results (+1). Node's argument
+ * and decoder errors throw (may-throw); generateKeyPairCb's callback
+ * receives (null, publicKey, privateKey) in a later macrotask. */
+ "crypto.createPrivateKey": { argTypes: [DYN], result: DYN },
+ "crypto.createPublicKey": { argTypes: [DYN], result: DYN },
+ "crypto.generateKeyPairSync": { argTypes: [DYN, DYN], result: DYN },
+ "crypto.generateKeyPairCb": { argTypes: [DYN, DYN, DYN], result: VOID },
+ "crypto.signOneshot": { argTypes: [DYN, DYN, DYN], result: DYN },
+ "crypto.verifyOneshot": { argTypes: [DYN, DYN, DYN, DYN], result: DYN },
+ "crypto.createSign": { argTypes: [DYN], result: DYN },
+ "crypto.createVerify": { argTypes: [DYN], result: DYN },
/** The Buffer brand of a Uint8Array source onto a fresh Uint8Array
* result (map and filter of a Buffer return a Buffer). */
"bytes.copyBrand": { argTypes: [BYTES_U8, BYTES_U8], result: VOID },
diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts
index 5c60eb54f..83d382d34 100644
--- a/packages/compiler/src/ir/runtime-features.ts
+++ b/packages/compiler/src/ir/runtime-features.ts
@@ -216,6 +216,8 @@ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([
/** Entry points that link the stream unit (with STREAM_LIB_PREFIXES). */
const STREAM_LIB_FNS: ReadonlySet = new Set([
"child.stdioStream",
+ "stream.chunkDone",
+ "stream.objectMode",
"console.bind",
"crypto.hashStream",
"crypto.hashStreamPiped",
@@ -624,6 +626,63 @@ const rule = (r: RuntimeFeatureRule): RuntimeFeatureRule => r;
/* The HTTP, HTTPS, HTTP/2 and TLS surfaces, which link the net unit. */
const NET_PREFIXES: readonly string[] = ["net.", "http.", "tls.", "https.", "http2."];
+/** The scr_pki.c entry points (the pki unit). */
+export const PKI_LIB_FNS: ReadonlySet = new Set([
+ "crypto.createPrivateKey",
+ "crypto.createPublicKey",
+ "crypto.createSign",
+ "crypto.createVerify",
+ "crypto.generateKeyPairCb",
+ "crypto.generateKeyPairSync",
+ "crypto.signOneshot",
+ "crypto.verifyOneshot",
+]);
+
+/** The WebCrypto algorithms scr_webcrypto_pki.c implements over the pki
+ * unit (lowercased). */
+const WEBCRYPTO_ASYMMETRIC_NAMES: ReadonlySet = new Set([
+ "ecdsa",
+ "ed25519",
+ "rsa-pss",
+ "rsassa-pkcs1-v1_5",
+]);
+
+/** Whether a string literal in the module names Ed25519, without regard
+ * to case ("ed25519" for node:crypto, "Ed25519" for WebCrypto): the
+ * Ed25519 unit links then, alongside the pki unit. */
+export function moduleNamesEd25519(mod: IrModule): boolean {
+ let named = false;
+ everyModuleNode(mod, {
+ expr: (node) => {
+ if (node.kind === "strLit" && node.value.toLowerCase() === "ed25519") named = true;
+ return !named;
+ },
+ stmt: () => !named,
+ type: () => true,
+ });
+ return named;
+}
+
+/** Whether the module calls WebCrypto and a string literal in it names one
+ * of the asymmetric algorithms (matched without regard to case, as
+ * normalizeAlgorithm matches names): scr_webcrypto_pki.c and the pki unit
+ * link then. */
+export function moduleUsesWebCryptoAsymmetric(mod: IrModule): boolean {
+ let webcrypto = false;
+ let named = false;
+ everyModuleNode(mod, {
+ expr: (node) => {
+ if (node.kind === "libCall" && node.fn.startsWith("webcrypto.")) webcrypto = true;
+ if (node.kind === "strLit" && WEBCRYPTO_ASYMMETRIC_NAMES.has(node.value.toLowerCase()))
+ named = true;
+ return !(webcrypto && named);
+ },
+ stmt: () => !(webcrypto && named),
+ type: () => true,
+ });
+ return webcrypto && named;
+}
+
/** One rule per runtime feature, sorted by name. A new feature is an
* entry here and its line in noRuntimeFeatures; RuntimeFeatures, the scan
* and its early exit derive from this table. The lists above keep one
@@ -840,6 +899,12 @@ const RUNTIME_FEATURE_RULES = {
/** perf_hooks event-loop metrics (eventLoopUtilization,
* monitorEventLoopDelay): scr_perf_loop.c. */
perfLoop: rule({ fns: ["perf.idleTime"], prefixes: ["perf.loop", "perf.eld"] }),
+ /** scr_pki.c over the mbedTLS archive: asymmetric KeyObjects and
+ * signatures. */
+ pki: rule({ initial: moduleUsesWebCryptoAsymmetric, fns: [...PKI_LIB_FNS] }),
+ /** scr_ed25519.c (the vendored libsodium Ed25519), linked with pki: a
+ * string literal in the module names Ed25519. */
+ ed25519: rule({ initial: moduleNamesEd25519 }),
processEvents: rule({ fns: [...PROCESS_EVENT_LIB_FNS, "util.styleText"], types: ["procStream"] }),
/** scr_url_legacy.c parses and stringifies queries. */
qs: rule({ fns: [...QS_LIB_FNS, ...LEGACY_URL_LIB_FNS] }),
@@ -947,6 +1012,8 @@ function noRuntimeFeatures(): RuntimeFeatures {
nodeTest: false,
parseArgs: false,
perfLoop: false,
+ pki: false,
+ ed25519: false,
processEvents: false,
qs: false,
regex: false,
diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts
index 9de1b5b62..bb36c7007 100644
--- a/packages/compiler/src/ir/validate.ts
+++ b/packages/compiler/src/ir/validate.ts
@@ -4109,6 +4109,7 @@ function validateFunction(
e.fn === "writable.write" ||
e.fn === "writable.writeStr" ||
e.fn === "writable.writeU" ||
+ e.fn === "writable.writeEnc" ||
e.fn === "writable.end" ||
e.fn === "readable.unpipe" ||
e.fn === "rl.create" ||
@@ -5492,6 +5493,7 @@ function validateFunction(
e.fn.startsWith("passthrough.") ||
e.fn === "stream.destroy" ||
e.fn === "stream.destroyErr" ||
+ e.fn === "stream.objectMode" ||
e.fn === "stream.iteratorClose" ||
e.fn === "stream.prop" ||
e.fn === "stream.errored" ||
@@ -5782,6 +5784,7 @@ function validateFunction(
e.fn === "writable.end" ||
e.fn === "readable.setEncoding" ||
e.fn === "readable.pushEncoding" ||
+ e.fn === "stream.objectMode" ||
e.fn === "stream.destroy" ||
e.fn === "stream.destroyErr" ||
e.fn === "stream.iteratorClose"
@@ -5898,8 +5901,10 @@ function validateFunction(
const chunkT = e.args[2]?.type;
const ok =
chunkT !== undefined &&
- ((chunkT.kind === "bytes" && chunkT.elem === "u8") || chunkT.kind === "string");
- if (!ok) err(`libCall emitter.emitData chunk must be bytes or string`, e.loc);
+ ((chunkT.kind === "bytes" && chunkT.elem === "u8") ||
+ chunkT.kind === "string" ||
+ chunkT.kind === "dyn");
+ if (!ok) err(`libCall emitter.emitData chunk must be bytes, string or dyn`, e.loc);
if (e.type.kind !== "bool") err(`libCall emitter.emitData must be bool`, e.loc);
return true;
}
diff --git a/packages/compiler/src/library/ir-policy.ts b/packages/compiler/src/library/ir-policy.ts
index 51aeb6146..f16ae3d0c 100644
--- a/packages/compiler/src/library/ir-policy.ts
+++ b/packages/compiler/src/library/ir-policy.ts
@@ -52,6 +52,13 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [
["tls.", "the node:tls surface"],
["crypto.cipherNew", "crypto.createCipheriv and createDecipheriv"],
["crypto.ecdh", "crypto.createECDH and ECDH.convertKey"],
+ ["crypto.createPrivateKey", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.createPublicKey", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.createSign", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.createVerify", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.generateKeyPair", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.signOneshot", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
+ ["crypto.verifyOneshot", "asymmetric KeyObjects, generateKeyPair, sign and verify"],
["webcrypto.", "WebCrypto (crypto.subtle)"],
["fs.watch", "fs.watch"],
["watcher.", "fs.watch"],
@@ -159,6 +166,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr
[features.fsWatch, "fs.watch"],
[features.stream, "the node:stream surface"],
[features.tls, "the node:tls surface"],
+ [features.pki, "asymmetric KeyObjects, generateKeyPair, sign and verify"],
[features.fetch, "fetch"],
[features.eventTarget, "the EventTarget surface"],
[features.nodeTest, "the node:test surface"],
diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts
index ed8d52293..7679338bb 100644
--- a/packages/compiler/src/native/cli.ts
+++ b/packages/compiler/src/native/cli.ts
@@ -110,6 +110,8 @@ async function main(): Promise {
tls: false,
cipher: false,
ecdh: false,
+ pki: false,
+ ed25519: false,
webcrypto: false,
perfLoop: false,
tlsCa: false,
diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts
index 6b0a2fea5..f470b614e 100644
--- a/packages/compiler/src/native/prepare.ts
+++ b/packages/compiler/src/native/prepare.ts
@@ -161,6 +161,8 @@ function validFeatures(value: unknown): value is NativeLinkFeatures {
"tlsCa",
"cipher",
"ecdh",
+ "pki",
+ "ed25519",
"webcrypto",
"perfLoop",
].every((name) => typeof features[name] === "boolean");
diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts
index e6beb24cc..a626cd78c 100644
--- a/packages/compiler/src/native/sanitizer.test.ts
+++ b/packages/compiler/src/native/sanitizer.test.ts
@@ -120,6 +120,8 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
tlsCa: false,
cipher: false,
ecdh: false,
+ pki: false,
+ ed25519: false,
webcrypto: false,
perfLoop: false,
nativeFetch: false,
diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json
index 02b1181b5..a677eacc3 100644
--- a/packages/compiler/surface-manifest.json
+++ b/packages/compiler/surface-manifest.json
@@ -498,7 +498,7 @@
"name": "crypto.createDiffieHellman",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.createDiffieHellmanGroup",
@@ -506,7 +506,7 @@
"name": "crypto.createDiffieHellmanGroup",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.createECDH",
@@ -530,17 +530,13 @@
"id": "node-builtin.crypto.createPrivateKey",
"kind": "node-builtin",
"name": "crypto.createPrivateKey",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.createPublicKey",
"kind": "node-builtin",
"name": "crypto.createPublicKey",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.createSecretKey",
@@ -552,17 +548,13 @@
"id": "node-builtin.crypto.createSign",
"kind": "node-builtin",
"name": "crypto.createSign",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.createVerify",
"kind": "node-builtin",
"name": "crypto.createVerify",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.diffieHellman",
@@ -570,7 +562,7 @@
"name": "crypto.diffieHellman",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.generateKey",
@@ -582,17 +574,13 @@
"id": "node-builtin.crypto.generateKeyPair",
"kind": "node-builtin",
"name": "crypto.generateKeyPair",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.generateKeyPairSync",
"kind": "node-builtin",
"name": "crypto.generateKeyPairSync",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.generateKeySync",
@@ -614,7 +602,7 @@
"name": "crypto.getDiffieHellman",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.getRandomValues",
@@ -658,7 +646,7 @@
"name": "crypto.privateDecrypt",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.privateEncrypt",
@@ -666,7 +654,7 @@
"name": "crypto.privateEncrypt",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.publicDecrypt",
@@ -674,7 +662,7 @@
"name": "crypto.publicDecrypt",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.publicEncrypt",
@@ -682,7 +670,7 @@
"name": "crypto.publicEncrypt",
"status": "unsupported",
"code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "note": "Diffie-Hellman and RSA encryption have no lowering — the asymmetric crypto surface is RSA and EC KeyObjects (generateKeyPair, createPrivateKey, createPublicKey), sign, verify, createSign, createVerify, and createECDH"
},
{
"id": "node-builtin.crypto.randomBytes",
@@ -744,9 +732,7 @@
"id": "node-builtin.crypto.sign",
"kind": "node-builtin",
"name": "crypto.sign",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.crypto.timingSafeEqual",
@@ -758,9 +744,7 @@
"id": "node-builtin.crypto.verify",
"kind": "node-builtin",
"name": "crypto.verify",
- "status": "unsupported",
- "code": "SC2020",
- "note": "asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics"
+ "status": "static"
},
{
"id": "node-builtin.dgram",
@@ -5718,7 +5702,7 @@
"kind": "stdlib",
"name": "globalThis.crypto.subtle",
"status": "static",
- "note": "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused"
+ "note": "importKey, generateKey and exportKey (raw and jwk) for HMAC and AES keys, importKey for PBKDF2 and HKDF keys, AES-GCM, AES-CBC and AES-CTR encrypt and decrypt, wrapKey and unwrapKey of secret keys with AES-KW or those ciphers, PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign and verify, generateKey, importKey, exportKey, sign and verify for ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and RSA-PSS, and SHA-1/SHA-2 digest; other methods, algorithms and key formats are refused"
},
{
"id": "syntax.async-generators",
diff --git a/packages/compiler/test/await-call-emission.test.ts b/packages/compiler/test/await-call-emission.test.ts
index 96c039155..5a967cc5f 100644
--- a/packages/compiler/test/await-call-emission.test.ts
+++ b/packages/compiler/test/await-call-emission.test.ts
@@ -222,7 +222,8 @@ test("bodies that suspend only at their own awaits compile to stackless coroutin
});
const framedSources = {
- // Entering a store makes the frame isolate the AsyncLocalStorage context.
+ // Entering a store needs no frame: the body runs in its caller's context,
+ // as Node runs an async body's synchronous prefix.
context: `
import { AsyncLocalStorage } from "node:async_hooks";
const store = new AsyncLocalStorage();
@@ -238,7 +239,7 @@ async function leaf(x: number): Promise {
}`,
};
-test("fiberless bodies keep their frame where it isolates a context or a stack", async () => {
+test("fiberless bodies keep their frame only where it isolates a stack", async () => {
for (const [name, prefix] of Object.entries(framedSources)) {
const llvm = await emit(`${prefix}
async function slow(x: number): Promise {
@@ -262,9 +263,12 @@ async function main(): Promise {
void main();
`);
for (const symbol of ["sc_ai_leaf", "sc_aw_leaf", "sc_as_leaf"]) {
- expect(definition(llvm, symbol), `${name} ${symbol}`).toContain(
- "call void @scr_async_inline_enter(ptr %frame)",
- );
+ const body = definition(llvm, symbol);
+ if (name === "stack")
+ expect(body, `${name} ${symbol}`).toContain(
+ "call void @scr_async_inline_enter(ptr %frame)",
+ );
+ else expect(body, `${name} ${symbol}`).not.toContain("@scr_async_inline_enter");
}
}
});
diff --git a/packages/compiler/test/native-codegen-integration.test.ts b/packages/compiler/test/native-codegen-integration.test.ts
index c05e7f994..af8d9b724 100644
--- a/packages/compiler/test/native-codegen-integration.test.ts
+++ b/packages/compiler/test/native-codegen-integration.test.ts
@@ -378,7 +378,7 @@ done:
expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"]));
expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"]));
expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]);
- expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v25");
+ expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v26");
const clangExe = join(dir, "clang-program");
const linkDriver = join(dir, "clang-driver.c");
@@ -712,7 +712,7 @@ done:
.split("\n");
expect(undefinedSymbols).toContain("_sf_scale");
expect(undefinedSymbols).toContain("_sf_callback_mix");
- expect(undefinedSymbols).toContain("_scr_runtime_abi_v25");
+ expect(undefinedSymbols).toContain("_scr_runtime_abi_v26");
});
test("helper and clang object paths retain the same outbound FFI C ABI", async () => {
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6822-jsx-classic-runtime/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6822-jsx-classic-runtime/main.ts.json
new file mode 100644
index 000000000..9d97dc765
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6822-jsx-classic-runtime/main.ts.json
@@ -0,0 +1,11 @@
+{
+ "order": [
+ "/tests/corpus/6822-jsx-classic-runtime/html.ts",
+ "/tests/corpus/6822-jsx-classic-runtime/list.jsx",
+ "/tests/corpus/6822-jsx-classic-runtime/page.tsx",
+ "/tests/corpus/6822-jsx-classic-runtime/factory.ts",
+ "/tests/corpus/6822-jsx-classic-runtime/pragma.tsx",
+ "/tests/corpus/6822-jsx-classic-runtime/main.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6824-jsx-automatic-runtime/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6824-jsx-automatic-runtime/main.ts.json
new file mode 100644
index 000000000..95ba5cd15
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6824-jsx-automatic-runtime/main.ts.json
@@ -0,0 +1,12 @@
+{
+ "order": [
+ "/tests/corpus/6824-jsx-automatic-runtime/runtime/jsx-runtime.ts",
+ "/tests/corpus/6824-jsx-automatic-runtime/alt/jsx-runtime.ts",
+ "/tests/corpus/6824-jsx-automatic-runtime/alt.tsx",
+ "/tests/corpus/6824-jsx-automatic-runtime/runtime/index.ts",
+ "/tests/corpus/6824-jsx-automatic-runtime/page.tsx",
+ "/tests/corpus/6824-jsx-automatic-runtime/render.ts",
+ "/tests/corpus/6824-jsx-automatic-runtime/main.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6825-jsx-automatic-development/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6825-jsx-automatic-development/main.ts.json
new file mode 100644
index 000000000..649f0bdff
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6825-jsx-automatic-development/main.ts.json
@@ -0,0 +1,8 @@
+{
+ "order": [
+ "/tests/corpus/6825-jsx-automatic-development/runtime/jsx-dev-runtime.ts",
+ "/tests/corpus/6825-jsx-automatic-development/view.tsx",
+ "/tests/corpus/6825-jsx-automatic-development/main.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6870-error-constructor-values.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6870-error-constructor-values.js.json
new file mode 100644
index 000000000..a2f0e02ea
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6870-error-constructor-values.js.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/6870-error-constructor-values.js"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6871-worker-data-destructuring.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6871-worker-data-destructuring.cjs.json
new file mode 100644
index 000000000..b610a5878
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6871-worker-data-destructuring.cjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/6871-worker-data-destructuring.cjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7080-crypto-ed25519.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7080-crypto-ed25519.mjs.json
new file mode 100644
index 000000000..035d9d765
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7080-crypto-ed25519.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7080-crypto-ed25519.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7081-webcrypto-ed25519.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7081-webcrypto-ed25519.mjs.json
new file mode 100644
index 000000000..bbd2d1ebc
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7081-webcrypto-ed25519.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7081-webcrypto-ed25519.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7082-crypto-key-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7082-crypto-key-objects.ts.json
new file mode 100644
index 000000000..c2e50cc97
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7082-crypto-key-objects.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7082-crypto-key-objects.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7083-crypto-sign-verify.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7083-crypto-sign-verify.ts.json
new file mode 100644
index 000000000..385c9347c
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7083-crypto-sign-verify.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7083-crypto-sign-verify.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7084-crypto-key-pairs.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7084-crypto-key-pairs.cjs.json
new file mode 100644
index 000000000..e1ac90536
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7084-crypto-key-pairs.cjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7084-crypto-key-pairs.cjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7085-webcrypto-ecdsa-rsa.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7085-webcrypto-ecdsa-rsa.ts.json
new file mode 100644
index 000000000..3fc5406e8
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7085-webcrypto-ecdsa-rsa.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7085-webcrypto-ecdsa-rsa.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs.json
new file mode 100644
index 000000000..ef44f9180
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7087-tls-socket-wrap.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7087-tls-socket-wrap.ts.json
new file mode 100644
index 000000000..37009eaea
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7087-tls-socket-wrap.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7087-tls-socket-wrap.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7088-tls-socket-connect-tunnel.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7088-tls-socket-connect-tunnel.ts.json
new file mode 100644
index 000000000..71947ecdd
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7088-tls-socket-connect-tunnel.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7088-tls-socket-connect-tunnel.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7089-http-host-header-tunnels.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7089-http-host-header-tunnels.ts.json
new file mode 100644
index 000000000..9b3002173
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7089-http-host-header-tunnels.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7089-http-host-header-tunnels.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7094-tls-socket-failed-handshakes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7094-tls-socket-failed-handshakes.ts.json
new file mode 100644
index 000000000..8fa524155
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7094-tls-socket-failed-handshakes.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7094-tls-socket-failed-handshakes.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7106-async-prefix-enter-with.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7106-async-prefix-enter-with.ts.json
new file mode 100644
index 000000000..2945faca5
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7106-async-prefix-enter-with.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7106-async-prefix-enter-with.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7107-for-await-context.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7107-for-await-context.ts.json
new file mode 100644
index 000000000..331f64937
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7107-for-await-context.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7107-for-await-context.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7108-generator-step-context.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7108-generator-step-context.ts.json
new file mode 100644
index 000000000..e7c5876ac
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7108-generator-step-context.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7108-generator-step-context.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7180-generic-local-binding-early-uses.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7180-generic-local-binding-early-uses.ts.json
new file mode 100644
index 000000000..5fb971826
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7180-generic-local-binding-early-uses.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7180-generic-local-binding-early-uses.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7181-generic-module-binding-tdz.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7181-generic-module-binding-tdz.ts.json
new file mode 100644
index 000000000..a67424e94
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7181-generic-module-binding-tdz.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7181-generic-module-binding-tdz.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7182-generic-binding-direct-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7182-generic-binding-direct-calls.ts.json
new file mode 100644
index 000000000..b641615f8
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7182-generic-binding-direct-calls.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7182-generic-binding-direct-calls.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7240-objectmode-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7240-objectmode-streams.ts.json
new file mode 100644
index 000000000..0b212fb07
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7240-objectmode-streams.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7240-objectmode-streams.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7241-objectmode-streams.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7241-objectmode-streams.mjs.json
new file mode 100644
index 000000000..22dc5abc4
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7241-objectmode-streams.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7241-objectmode-streams.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7242-objectmode-dynamic.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7242-objectmode-dynamic.cjs.json
new file mode 100644
index 000000000..1b932b82b
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7242-objectmode-dynamic.cjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7242-objectmode-dynamic.cjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7243-for-await-close-order.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7243-for-await-close-order.mjs.json
new file mode 100644
index 000000000..ec479cf59
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7243-for-await-close-order.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7243-for-await-close-order.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7244-objectmode-subclasses.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7244-objectmode-subclasses.ts.json
new file mode 100644
index 000000000..827bba4c0
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7244-objectmode-subclasses.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7244-objectmode-subclasses.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7245-writable-callback-order.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7245-writable-callback-order.mjs.json
new file mode 100644
index 000000000..9485562b3
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7245-writable-callback-order.mjs.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7245-writable-callback-order.mjs"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7280-named-reads-hole-receivers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7280-named-reads-hole-receivers.ts.json
new file mode 100644
index 000000000..febb008f7
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7280-named-reads-hole-receivers.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/corpus/7280-named-reads-hole-receivers.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-factory-mismatch/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-factory-mismatch/main.ts.json
new file mode 100644
index 000000000..9552906dd
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-factory-mismatch/main.ts.json
@@ -0,0 +1,17 @@
+{
+ "order": [
+ "/tests/diagnostics/jsx-factory-mismatch/view.tsx",
+ "/tests/diagnostics/jsx-factory-mismatch/main.ts"
+ ],
+ "diags": [
+ {
+ "code": "SC0001",
+ "message": "Argument of type 'number' is not assignable to parameter of type 'string'. (in the call this JSX lowers to)",
+ "loc": {
+ "file": "/tests/diagnostics/jsx-factory-mismatch/view.tsx",
+ "start": 337,
+ "end": 342
+ }
+ }
+ ]
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-preserve/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-preserve/main.ts.json
new file mode 100644
index 000000000..eb5e492f4
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-preserve/main.ts.json
@@ -0,0 +1,18 @@
+{
+ "order": [
+ "/tests/diagnostics/jsx-preserve/view.tsx",
+ "/tests/diagnostics/jsx-preserve/main.ts"
+ ],
+ "diags": [
+ {
+ "code": "SC1090",
+ "message": "JSX with \"jsx\": \"preserve\" is not supported: that setting keeps JSX in the output instead of lowering it to calls",
+ "loc": {
+ "file": "/tests/diagnostics/jsx-preserve/view.tsx",
+ "start": 158,
+ "end": 176
+ },
+ "hint": "set \"jsx\" to \"react\", \"react-jsx\" or \"react-jsxdev\" in the project tsconfig"
+ }
+ ]
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-type-errors/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-type-errors/main.ts.json
new file mode 100644
index 000000000..62a162ee2
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/jsx-type-errors/main.ts.json
@@ -0,0 +1,26 @@
+{
+ "order": [
+ "/tests/diagnostics/jsx-type-errors/view.tsx",
+ "/tests/diagnostics/jsx-type-errors/main.ts"
+ ],
+ "diags": [
+ {
+ "code": "SC0001",
+ "message": "Type 'number' is not assignable to type 'string'.",
+ "loc": {
+ "file": "/tests/diagnostics/jsx-type-errors/view.tsx",
+ "start": 488,
+ "end": 495
+ }
+ },
+ {
+ "code": "SC0001",
+ "message": "Type 'string' is not assignable to type 'number'.",
+ "loc": {
+ "file": "/tests/diagnostics/jsx-type-errors/view.tsx",
+ "start": 520,
+ "end": 527
+ }
+ }
+ ]
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/tls-socket-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/tls-socket-fences.ts.json
new file mode 100644
index 000000000..409766067
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/tls-socket-fences.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/diagnostics/tls-socket-fences.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/keys.ts.json
new file mode 100644
index 000000000..b0b79568d
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/keys.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/fixtures/node-types/keys.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-socket-wrap.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-socket-wrap.ts.json
new file mode 100644
index 000000000..68346a115
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-socket-wrap.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/fixtures/node-types/tls-socket-wrap.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/webcrypto-ecdsa.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/webcrypto-ecdsa.ts.json
new file mode 100644
index 000000000..2c4a3b15b
--- /dev/null
+++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/webcrypto-ecdsa.ts.json
@@ -0,0 +1,6 @@
+{
+ "order": [
+ "/tests/fixtures/node-types/webcrypto-ecdsa.ts"
+ ],
+ "diags": []
+}
diff --git a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs
index db72284f6..5549b0db9 100644
--- a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs
+++ b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs
@@ -43,7 +43,7 @@ export const RUNTIME_PACK_MATRIX = withLibraryRuntimeFlavors(
{ id: "quickjs", predicate: false },
{ id: "libregexp", predicate: "regex" },
{ id: "zlib", predicate: "zlibEffective" },
- { id: "mbedtls", predicate: any("tlsEffective", "cipher", "ecdh") },
+ { id: "mbedtls", predicate: any("tlsEffective", "cipher", "ecdh", "pki") },
],
system_libraries: [{ name: "System", predicate: true }],
}),
diff --git a/packages/runtime-darwin-arm64/scripts/build.mjs b/packages/runtime-darwin-arm64/scripts/build.mjs
index 6cc32d7fa..f5c0aa395 100644
--- a/packages/runtime-darwin-arm64/scripts/build.mjs
+++ b/packages/runtime-darwin-arm64/scripts/build.mjs
@@ -173,6 +173,7 @@ async function build() {
...(unit.source === "scr_tls.c" ||
unit.source === "scr_cipher.c" ||
unit.source === "scr_ecdh.c" ||
+ unit.source === "scr_pki.c" ||
unit.source === "scr_webcrypto_aes.c"
? ["-I", join(mbedtls, "include")]
: []),
@@ -338,7 +339,7 @@ async function build() {
package: packageManifest.name,
version: packageManifest.version,
target: RUNTIME_PACK_MATRIX.target,
- runtime_abi: { version: 25, marker: "scr_runtime_abi_v25" },
+ runtime_abi: { version: 26, marker: "scr_runtime_abi_v26" },
compiler: {
command: compiler,
identity: compilerVersion,
diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs
index 3529df137..6af6a1167 100644
--- a/packages/runtime-pack-common/runtime-pack-matrix.mjs
+++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs
@@ -151,8 +151,11 @@ export const OPTIONAL_RUNTIME_SOURCES = [
["scr_tls.c", "tlsEffective"],
["scr_cipher.c", "cipher"],
["scr_ecdh.c", "ecdh"],
+ ["scr_pki.c", "pki"],
["scr_wasm.c", "wasm"],
["scr_webcrypto.c", "webcrypto"],
+ ["scr_webcrypto_pki.c", all("webcrypto", "pki")],
+ ["scr_ed25519.c", all("pki", "ed25519")],
// WebCrypto's AES ciphers run over the cipher feature's mbedTLS archive.
["scr_webcrypto_aes.c", all("cipher", "webcrypto")],
["scr_fetch.c", "nativeFetch"],
@@ -216,7 +219,7 @@ export function createRuntimePackMatrix({
{ id: "quickjs", predicate: false },
{ id: "libregexp", predicate: "regex" },
{ id: "zlib", predicate: "zlibEffective" },
- { id: "mbedtls", predicate: any("tlsEffective", "cipher", "ecdh") },
+ { id: "mbedtls", predicate: any("tlsEffective", "cipher", "ecdh", "pki") },
];
const matrix = withLibraryRuntimeFlavors(
withWorkerRuntimeVariants({
diff --git a/packages/runtime-pack-common/scripts/build.mjs b/packages/runtime-pack-common/scripts/build.mjs
index a2c987b97..7dd996f28 100644
--- a/packages/runtime-pack-common/scripts/build.mjs
+++ b/packages/runtime-pack-common/scripts/build.mjs
@@ -224,6 +224,7 @@ async function build() {
...(unit.source === "scr_tls.c" ||
unit.source === "scr_cipher.c" ||
unit.source === "scr_ecdh.c" ||
+ unit.source === "scr_pki.c" ||
unit.source === "scr_webcrypto_aes.c"
? ["-I", join(mbedtls, "include")]
: []),
@@ -435,7 +436,7 @@ async function build() {
package: packageManifest.name,
version: packageManifest.version,
target: matrix.target,
- runtime_abi: { version: 25, marker: "scr_runtime_abi_v25" },
+ runtime_abi: { version: 26, marker: "scr_runtime_abi_v26" },
compiler: {
command: compiler,
identity: compilerVersion,
diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs
index 0a8699b23..513007c7f 100644
--- a/packages/runtime-wasm32-wasi/scripts/build.mjs
+++ b/packages/runtime-wasm32-wasi/scripts/build.mjs
@@ -22,6 +22,9 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({
"scr_tls.c",
"scr_cipher.c",
"scr_ecdh.c",
+ "scr_pki.c",
+ "scr_webcrypto_pki.c",
+ "scr_ed25519.c",
"scr_webcrypto_aes.c",
"scr_wasm.c",
"scr_fetch.c",
diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c
index db1e54295..9e3365fbe 100644
--- a/packages/runtime/src/scr_async.c
+++ b/packages/runtime/src/scr_async.c
@@ -623,6 +623,10 @@ struct ScrFiber {
* awaiter destroys the record when it takes the result. */
bool direct;
struct ScrFiber *direct_awaiter;
+ /* While an async body runs its synchronous prefix (until its first await
+ * or its end), the AsyncLocalStorage slot of the execution that spawned
+ * it; NULL afterwards (scr_als_prefix_end). */
+ ScrAlsCtx **prefix_slot;
#ifdef SCR_ASAN_FIBERS
void *fake_stack;
/* Live stackless async fibers (scr_async_spawn_stackless) form a list
@@ -670,6 +674,61 @@ void scr_als_ctx_release(ScrAlsCtx *c) {
free(c);
}
+/* enterWith on the main stack lasts until that code returns to the loop,
+ * so the store does not reach callbacks scheduled without it: the loop
+ * clears the main slot when the main module's body returns, and an
+ * enterWith inside a callback the loop runs puts a tick that clears it at
+ * the front of the tick queue, so it runs as soon as that callback
+ * returns. A store that an async callee's prefix or a generator step
+ * enters becomes the caller's at scr_als_prefix_end and is cleared the
+ * same way. */
+static SCR_TL bool scr_loop_started = false;
+
+static void scr_als_main_clear(void) {
+ scr_als_ctx_release(scr_als_main_slot);
+ scr_als_main_slot = NULL;
+}
+
+static void scr_next_tick_raw_first(void (*fn)(void));
+
+/* A new store on the main slot: one a loop callback enters ends with it. */
+static void scr_als_main_entered(void) {
+ if (scr_loop_started) scr_next_tick_raw_first(scr_als_main_clear);
+}
+
+void scr_als_entered_with(void) {
+ if (SCR_ALS_SLOT() == &scr_als_main_slot) scr_als_main_entered();
+}
+
+/* The end of an async body's synchronous prefix: its first await, or its
+ * end. Node runs that prefix inside the caller's execution, so a store it
+ * entered (enterWith) is still the caller's store after the call; the body
+ * keeps its own copy of the context for what runs after its await. */
+static void scr_als_prefix_end(ScrFiber *f) {
+ ScrAlsCtx **slot = f->prefix_slot;
+ if (slot == NULL) return;
+ f->prefix_slot = NULL;
+ if (*slot != f->als) {
+ scr_als_ctx_release(*slot);
+ *slot = scr_als_ctx_retain(f->als);
+ if (f->als != NULL && slot == &scr_als_main_slot) scr_als_main_entered();
+ }
+}
+
+/* A synchronous generator has no context of its own: each step runs in its
+ * resumer's execution, as Node runs it. A fiber-backed one switches to its
+ * own slot, so its step starts from the resumer's store and a store it
+ * enters is the resumer's after the step (scr_als_prefix_end on the way
+ * back); a stackless one runs in the resumer's slot (scr_gen_coro_hop). */
+static void scr_als_step_begin(ScrFiber *f) {
+ ScrAlsCtx **slot = SCR_ALS_SLOT();
+ if (f->als != *slot) {
+ scr_als_ctx_release(f->als);
+ f->als = scr_als_ctx_retain(*slot);
+ }
+ f->prefix_slot = slot;
+}
+
/* The lifetime region a new async fiber carries (ScrFiber.region). */
static inline void *scr_fiber_region_capture(void) {
const ScrRgnFiberHooks *regions = atomic_load_explicit(&scr_region_fiber_hooks, memory_order_relaxed);
@@ -726,26 +785,6 @@ ScrDyn *scr_als_peek(double id) {
return NULL;
}
-/* enterWith on the main stack lasts until that code returns to the loop,
- * so the store does not reach callbacks scheduled without it: the loop
- * clears the main slot when the main module's body returns, and an
- * enterWith inside a callback the loop runs puts a tick that clears it at
- * the front of the tick queue, so it runs as soon as that callback
- * returns. */
-static SCR_TL bool scr_loop_started = false;
-
-static void scr_als_main_clear(void) {
- scr_als_ctx_release(scr_als_main_slot);
- scr_als_main_slot = NULL;
-}
-
-static void scr_next_tick_raw_first(void (*fn)(void));
-
-void scr_als_entered_with(void) {
- if (scr_loop_started && SCR_ALS_SLOT() == &scr_als_main_slot)
- scr_next_tick_raw_first(scr_als_main_clear);
-}
-
static SCR_TL ScrCtx scr_loop_ctx; /* the main stack (scheduler home) */
static SCR_TL ScrFiber *scr_current = NULL;
/* A finished fiber whose last switch went to the loop (scr_resume_fiber). */
@@ -1889,6 +1928,7 @@ static void scr_trampoline(void) {
ScrFiber *self = scr_current;
self->entry(self, self->argpack);
scr_fiber_finish(self);
+ scr_als_prefix_end(self);
/* The loop reaps the fiber that comes back to it finished, which need
* not be the one it resumed: a fiber that continued in place for the
* awaiters it parked (scr_await_yield) returns to the loop too. */
@@ -2010,6 +2050,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
/* AsyncLocalStorage: the child runs in the SPAWNER's context (Node's
* init-time capture); snapshots are immutable, so a retain suffices. */
f->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
+ f->prefix_slot = SCR_ALS_SLOT();
f->region = scr_fiber_region_capture();
scr_fibers_live++;
@@ -2023,13 +2064,15 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
#elif defined(__wasi__)
(void)awaiter;
ScrFiber *spawner = scr_current;
+ ScrAlsCtx **spawner_slot = scr_als_active;
scr_current = f;
scr_exc_swap_cell(&f->exc);
scr_als_active = &f->als;
entry(f, argpack); /* eager prefix; returns at suspend/final suspend */
+ scr_als_prefix_end(f);
scr_current = spawner;
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
- scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
+ scr_als_active = spawner_slot;
ScrPromise *result = scr_promise_retain(f->promise);
if (f->done) {
scr_promise_release(f->promise);
@@ -2057,6 +2100,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
#endif
f->return_to = home;
ScrFiber *spawner = scr_current;
+ ScrAlsCtx **spawner_slot = scr_als_active;
scr_switch(home, &f->ctx, f);
/* back: the fiber suspended or finished. The switch back targeted NULL
* (the child doesn't know who spawned it), which pointed both the current
@@ -2066,7 +2110,7 @@ static ScrPromise *scr_async_spawn_impl(void (*entry)(ScrFiber *, void *), void
* `undefined` rejection while the real payload leaks). */
scr_current = spawner;
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
- scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
+ scr_als_active = spawner_slot;
if (awaited != NULL) {
/* Parked by the child: the loop owns (and may have reaped) it. */
if (awaiter->await_parked) return awaited;
@@ -2172,6 +2216,7 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
scr_fiber_track(f);
f->stackless = true;
f->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
+ f->prefix_slot = SCR_ALS_SLOT();
/* The lifetime region the eager prefix runs in, which every later step
* resumes in (scr_resume_fiber), as a fiber's body does. */
f->region = scr_fiber_region_capture();
@@ -2183,6 +2228,7 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
scr_stackless_fibers = f;
#endif
ScrFiber *spawner = scr_current;
+ ScrAlsCtx **spawner_slot = scr_als_active;
#ifndef _WIN32
uintptr_t guard = scr_stack_guard;
if (guard != 0 && (uintptr_t)__builtin_frame_address(0) < guard + SCR_STACKLESS_PREFIX_ROOM) {
@@ -2195,9 +2241,10 @@ static ScrFiber *scr_spawn_stackless_fiber(void (*entry)(ScrFiber *, void *), vo
scr_als_active = &f->als;
entry(f, argpack); /* the eager prefix: returns at a suspension or the end */
}
+ scr_als_prefix_end(f);
scr_current = spawner;
scr_exc_swap_cell(spawner ? &spawner->exc : NULL);
- scr_als_active = spawner ? &spawner->als : &scr_als_main_slot;
+ scr_als_active = spawner_slot;
return f;
}
@@ -2303,8 +2350,10 @@ void *scr_async_spawn_await_ref(void (*entry)(ScrFiber *, void *), void *argpack
* settled promise is created and settled in the same order, rejections
* enter the unhandled ledger through the same settle path, and the frame
* isolates what a fresh fiber would — the Error.stack frame chain
- * (cell->stack) and the AsyncLocalStorage context (enterWith inside the
- * body stays inside it). The caller's exception cell is clean at any call
+ * (cell->stack). The AsyncLocalStorage context is the caller's throughout:
+ * the whole body is the synchronous prefix Node runs in the caller's
+ * execution, so a store it enters stays entered after the call
+ * (scr_als_prefix_end). The caller's exception cell is clean at any call
* (try/finally stashes in-flight exceptions), so a pending exception
* after the body is the body's own throw. */
static void scr_inline_park_trap(void) {
@@ -2321,21 +2370,12 @@ void scr_async_inline_enter(ScrAsyncInline *frame) {
ScrExcCell *cell = scr_exc_current_cell();
frame->stack = cell->stack;
cell->stack = NULL;
- frame->als = scr_als_ctx_retain(*SCR_ALS_SLOT());
if (scr_current != NULL) scr_current->inline_depth++;
}
void scr_async_inline_exit(ScrAsyncInline *frame) {
if (scr_current != NULL) scr_current->inline_depth--;
scr_exc_current_cell()->stack = (ScrStackFrame *)frame->stack;
- ScrAlsCtx *saved = (ScrAlsCtx *)frame->als;
- ScrAlsCtx **slot = SCR_ALS_SLOT();
- if (*slot != saved) {
- scr_als_ctx_release(*slot);
- *slot = saved; /* the frame's reference moves back in */
- } else {
- scr_als_ctx_release(saved);
- }
}
ScrPromise *scr_async_inline_leave(ScrAsyncInline *frame) {
@@ -2379,11 +2419,14 @@ ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
* switch to its own resumer: park it here, for each such awaiter up the
* chain, and switch there directly. Nothing runs in between either way. */
static ScrCtx *scr_fiber_park_awaiters(ScrFiber *self) {
+ scr_als_prefix_end(self);
ScrCtx *to = self->return_to;
#ifdef SCR_SPAWN_AWAIT
for (ScrFiber *cur = self; cur->awaiter != NULL;) {
ScrFiber *aw = cur->awaiter;
cur->awaiter = NULL;
+ /* The awaiter's await is its own suspension: its prefix ends here. */
+ scr_als_prefix_end(aw);
scr_promise_add_waiter(cur->promise, aw);
aw->await_parked = true;
to = aw->return_to;
@@ -5675,9 +5718,13 @@ __attribute__((always_inline)) static inline void scr_gen_coro_hop(ScrGen *g, bo
if (retain) scr_gen_retain(g);
g->state = SCR_GEN_RUNNING;
f->resumer = scr_current;
+ ScrAlsCtx **resumer_slot = scr_als_active;
scr_current = f;
scr_exc_swap_cell(&f->exc);
- scr_als_active = &f->als;
+ /* A synchronous generator has no AsyncLocalStorage context of its own:
+ * its step runs in the resumer's slot, as Node runs it in the execution
+ * that called next(). An async generator's step runs in its own. */
+ if (g->is_async) scr_als_active = &f->als;
if (f->coro == NULL) f->entry(f, f->argpack);
else scr_coro_resume(f->coro);
/* A running generator cannot be resumed again, so neither its fiber nor
@@ -5686,7 +5733,7 @@ __attribute__((always_inline)) static inline void scr_gen_coro_hop(ScrGen *g, bo
ScrFiber *me = f->resumer;
scr_current = me;
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
- scr_als_active = me != NULL ? &me->als : &scr_als_main_slot;
+ scr_als_active = resumer_slot;
scr_gen_switched_back(g, f, retain, true);
}
@@ -5708,6 +5755,7 @@ static void scr_gen_switch_in(ScrGen *g) {
#endif
scr_gen_retain(g);
g->state = SCR_GEN_RUNNING;
+ if (!g->is_async) scr_als_step_begin(f);
#ifdef _WIN32
ScrCtx here = scr_win_self();
#elif defined(__wasi__)
@@ -5717,6 +5765,7 @@ static void scr_gen_switch_in(ScrGen *g) {
#endif
f->return_to = &here;
ScrFiber *me = scr_current;
+ ScrAlsCtx **resumer_slot = scr_als_active;
#ifdef __wasi__
scr_current = f;
scr_exc_swap_cell(&f->exc);
@@ -5725,14 +5774,17 @@ static void scr_gen_switch_in(ScrGen *g) {
else scr_wasi_coro_resume(f->coro);
scr_current = me;
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
- scr_als_active = me != NULL ? &me->als : &scr_als_main_slot;
+ scr_als_active = resumer_slot;
#else
scr_switch(&here, &f->ctx, f);
- /* Back on the consumer: restore identity + the consumer's cell (the
- * yield/finish switch targeted NULL — main's cell). */
+ /* Back on the consumer: restore identity, the consumer's cell and its
+ * AsyncLocalStorage slot (the yield/finish switch targeted NULL — main's
+ * cell and slot). */
scr_current = me;
scr_exc_swap_cell(me != NULL ? &me->exc : NULL);
+ scr_als_active = resumer_slot;
#endif
+ scr_als_prefix_end(f);
scr_gen_switched_back(g, f, true, false);
}
diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h
index 827c303f9..fc0128294 100644
--- a/packages/runtime/src/scr_async.h
+++ b/packages/runtime/src/scr_async.h
@@ -62,12 +62,14 @@ ScrPromise *scr_async_spawn_after(ScrPromise *dependency,
/* Fiberless async call (native targets): the compiler proves an async body
* can never park its execution context and runs it directly on the
* caller's stack between enter and leave. The frame saves what a fresh
- * fiber would isolate — the Error.stack frame chain and the
- * AsyncLocalStorage context — and leave restores both, returning a fresh
- * pending promise (+1) the caller settles from the body's outcome. */
+ * fiber would isolate, the Error.stack frame chain, and leave restores it,
+ * returning a fresh pending promise (+1) the caller settles from the body's
+ * outcome. The body runs in the caller's AsyncLocalStorage context, as
+ * Node runs an async body's synchronous prefix. The second word is unused;
+ * emitted frames keep its size. */
typedef struct ScrAsyncInline {
void *stack;
- void *als;
+ void *reserved;
} ScrAsyncInline;
void scr_async_inline_enter(ScrAsyncInline *frame);
ScrPromise *scr_async_inline_leave(ScrAsyncInline *frame);
diff --git a/packages/runtime/src/scr_console.c b/packages/runtime/src/scr_console.c
index c8fa40b38..24cd8093b 100644
--- a/packages/runtime/src/scr_console.c
+++ b/packages/runtime/src/scr_console.c
@@ -148,7 +148,7 @@ static void scr_enable_utf8_console_output(void) {
}
#endif
-void scr_runtime_abi_v25(void) {}
+void scr_runtime_abi_v26(void) {}
void scr_init(void) {
#ifdef SCR_WORKERS
diff --git a/packages/runtime/src/scr_core.h b/packages/runtime/src/scr_core.h
index 5e5278588..791c600c7 100644
--- a/packages/runtime/src/scr_core.h
+++ b/packages/runtime/src/scr_core.h
@@ -81,7 +81,7 @@ void scr_init(void);
/* Program objects emitted by the bundled LLVM helper reference this symbol.
* Its versioned spelling makes a mismatched manual runtime link fail before
* the program can start. */
-void scr_runtime_abi_v25(void);
+void scr_runtime_abi_v26(void);
void scr_runtime_workers_v8(void);
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
diff --git a/packages/runtime/src/scr_crypto.h b/packages/runtime/src/scr_crypto.h
index 99d8b85bc..0316bcca5 100644
--- a/packages/runtime/src/scr_crypto.h
+++ b/packages/runtime/src/scr_crypto.h
@@ -85,6 +85,10 @@ ScrDyn *scr_crypto_create_secret_key(ScrDyn *key, ScrDyn *encoding);
ScrDyn *scr_crypto_generate_key_sync(ScrDyn *type, ScrDyn *options);
ScrBytes *scr_crypto_key_object_input(ScrDyn *key);
bool scr_crypto_is_key_object(const ScrDyn *value);
+/* Throws Node's ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE for a private or public
+ * KeyObject used as a secret key. scr_pki.c, which makes those keys,
+ * installs it with them, so its messages stay out of other programs. */
+extern void (*scr_crypto_reject_asymmetric_secret)(const ScrDyn *key);
const unsigned char *scr_key_object_bytes(const ScrDyn *value, size_t *len);
/* crypto.createCipheriv (decrypt false) and crypto.createDecipheriv
* (decrypt true) over checked-dynamic arguments (scr_cipher.c, linked
@@ -268,5 +272,17 @@ ScrStr *scr_crypto_x509_valid_from(ScrBytes *data);
ScrStr *scr_crypto_x509_valid_from_str(ScrStr *pem);
ScrStr *scr_crypto_x509_valid_to(ScrBytes *data);
ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem);
+/* Asymmetric keys and signatures (scr_pki.c, the pki unit). Arguments
+ * borrowed; results +1, or NULL with the error pending. */
+ScrDyn *scr_crypto_create_private_key(ScrDyn *key);
+ScrDyn *scr_crypto_create_public_key(ScrDyn *key);
+ScrDyn *scr_crypto_generate_key_pair_sync(ScrDyn *type, ScrDyn *options);
+void scr_crypto_generate_key_pair_async(ScrDyn *type, ScrDyn *options, ScrDyn *callback);
+ScrDyn *scr_crypto_sign_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key);
+ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, ScrDyn *signature);
+ScrDyn *scr_crypto_create_sign(ScrDyn *algorithm);
+ScrDyn *scr_crypto_create_verify(ScrDyn *algorithm);
+bool scr_crypto_is_asymmetric_key(const ScrDyn *value);
+const char *scr_crypto_asymmetric_key_type(const ScrDyn *value);
#endif /* SCR_CRYPTO_H */
diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h
index a427fc9a1..facf9f2ff 100644
--- a/packages/runtime/src/scr_dyn.h
+++ b/packages/runtime/src/scr_dyn.h
@@ -158,6 +158,11 @@ typedef enum {
X(DIR, 44) /* fs.Dir (scr_lib.c) */ \
X(DISPOSABLE_STACK, 49) /* DisposableStack (scr_symbol.c) */ \
X(ECDH, 64) /* node:crypto ECDH (scr_ecdh.c) */ \
+ X(PRIVATE_KEY, 65) /* node:crypto PrivateKeyObject (scr_pki.c) */ \
+ X(PUBLIC_KEY, 66) /* node:crypto PublicKeyObject (scr_pki.c) */ \
+ X(SIGN, 67) /* node:crypto Sign (scr_pki.c) */ \
+ X(VERIFY, 68) /* node:crypto Verify (scr_pki.c) */ \
+ X(ASYMMETRIC_CRYPTO_KEY, 69) /* WebCrypto ECDSA and RSA CryptoKey (scr_webcrypto_pki.c) */ \
X(EVENT, 14) /* ScrEvent — new Event() and AbortSignal's abort events */ \
X(EVENT_TARGET, 39) /* ScrEventTarget — new EventTarget() */ \
X(FETCH_HEADERS, 13) /* native static-fetch response Headers */ \
@@ -759,6 +764,9 @@ ScrDyn *scr_dyn_array_constructor(void);
/* The interned builtin constructor `name` (Object, Date, RegExp, Map, Set,
* Promise, WeakMap, WeakSet) as a dynamic function value: +1. */
ScrDyn *scr_dyn_global_constructor(const ScrStr *name);
+/* The interned function value of an error constructor ("TypeError"), or
+ * NULL for other names. */
+ScrDyn *scr_dyn_error_constructor(const char *name);
/* A builtin global function (setTimeout, setImmediate, process.nextTick, ...)
* as an interned dynamic function value: +1. */
ScrDyn *scr_dyn_builtin_function(const ScrStr *name);
diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c
index 209c8c39a..cdd815642 100644
--- a/packages/runtime/src/scr_dyn_builtin_stream.c
+++ b/packages/runtime/src/scr_dyn_builtin_stream.c
@@ -34,8 +34,7 @@
* receiver without a reference; the hook closures outlive the record.
*
* Not supported here (each throws the "not supported yet" Error where it
- * runs): objectMode and the other options scr_stream.c's dyn option walk
- * refuses, `_writev` and `_construct`, symbol event names, listeners() /
+ * runs): the options scr_stream.c's dyn option walk refuses, `_writev` and `_construct`, symbol event names, listeners() /
* rawListeners(), `wrap()`, and pipes into values that are neither runtime
* streams nor constructor-function streams. */
#include "scr_runtime.h"
@@ -275,11 +274,10 @@ static ScrDyn *bcs_done_call(ScrClosure *closure, ScrDyn *const *args, size_t ar
break;
}
default: {
+ /* the data is pushed as push(data) pushes it */
ScrDyn *data = bcs_arg(args, argc, 1);
- ScrBytes *bytes = data->kind == SCR_DYN_BYTES ? scr_bytes_raw_view(data->v.bytes) : NULL;
- ScrStr *text = data->kind == SCR_DYN_STR ? scr_str_retain(data->v.str) : NULL;
- if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done(s, err, bytes, text);
- else scr_stream_flush_done(s, err, bytes, text);
+ if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done_value(s, err, data);
+ else scr_stream_flush_done_value(s, err, data);
break;
}
}
@@ -362,13 +360,14 @@ static void bcs_read_inv(ScrClosure *cb, ScrStream *s, double size) {
scr_dyn_release(object);
}
-/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb): a
- * throw propagates to the write() caller, as in Node. */
+/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb), or the
+ * value and the write's encoding argument on an object-mode writable
+ * side: a throw propagates to the write() caller, as in Node. */
static void bcs_chunk_call(ScrClosure *cb, ScrStream *s, ScrBytes *chunk, const char *method, int done) {
ScrDyn *object = scr_box_get_ref(cb->caps[0]);
ScrDyn *args[3];
- args[0] = scr_dyn_new_buffer(chunk);
- args[1] = bcs_str("buffer");
+ args[0] = scr_stream_chunk_value(s, chunk);
+ args[1] = scr_stream_chunk_encoding_value(s);
args[2] = bcs_done(s, done);
ScrDyn *result = bcs_invoke(object, method, args, 3);
for (int i = 0; i < 3; i++) scr_dyn_release(args[i]);
@@ -488,8 +487,8 @@ static void bcs_listener0(ScrClosure *adapter) {
bcs_listener_call(adapter, NULL, 0);
}
-static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text) {
- ScrDyn *arg = text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes);
+static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text, ScrDyn *value) {
+ ScrDyn *arg = value ? scr_dyn_retain(value) : text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes);
bcs_listener_call(adapter, &arg, 1);
scr_dyn_release(arg);
}
@@ -583,7 +582,7 @@ static bool bcs_add_listener(ScrStream *s, ScrDyn *self, ScrDyn *type, ScrDyn *l
break;
case 1:
chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_data),
- scr_ee_inv_fixed2, once, prepend);
+ scr_ee_inv_fixed3, once, prepend);
break;
case 2:
chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_error),
@@ -625,14 +624,15 @@ static ScrDyn *bcs_emit(ScrStream *s, ScrDyn *const *args, size_t argc) {
had = scr_emitter_emit(em, name);
break;
case 1:
+ /* 'data' carries the bytes, string and value slots (scr_stream_emit_data) */
if (first->kind == SCR_DYN_BYTES) {
ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes);
- had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL);
+ had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL, first);
scr_bytes_release(bytes);
} else if (first->kind == SCR_DYN_STR) {
- had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str);
+ had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str, first);
} else {
- bcs_unsupported("emit('data') with a chunk other than a Buffer or string on a constructor-function stream is not supported yet");
+ had = scr_emitter_emit(em, name, (ScrBytes *)NULL, (ScrStr *)NULL, first);
}
break;
case 2:
@@ -1037,10 +1037,16 @@ static ScrDyn *bcs_write(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t
ScrDyn *cb = bcs_arg(args, argc, 2);
if (scr_dyn_is_callable(encoding)) {
cb = encoding;
- encoding = scr_dyn_undefined();
+ encoding = scr_stream_writes_values(s) ? scr_dyn_new_null() : scr_dyn_undefined();
+ }
+ bool below;
+ if (scr_stream_writes_values(s)) {
+ /* an object-mode side hands the encoding argument to _write */
+ below = scr_stream_write_enc(s, chunk, encoding, bcs_callback(cb, self, s));
+ } else {
+ if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
+ below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s));
}
- if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
- bool below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s));
if (scr_exc_pending()) return NULL;
return scr_dyn_new_bool(below);
}
@@ -1055,11 +1061,17 @@ static ScrDyn *bcs_end(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t a
encoding = scr_dyn_undefined();
} else if (scr_dyn_is_callable(encoding)) {
cb = encoding;
- encoding = scr_dyn_undefined();
+ encoding = scr_dyn_new_null();
}
- if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL;
ScrStream *chained = NULL;
- if (bcs_nullish(chunk)) {
+ if (!bcs_nullish(chunk) && scr_stream_writes_values(s)) {
+ /* end(chunk) writes the value with the encoding argument */
+ scr_stream_write_enc(s, chunk, encoding, NULL);
+ if (scr_exc_pending()) return NULL;
+ chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s));
+ } else if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) {
+ return NULL;
+ } else if (bcs_nullish(chunk)) {
chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s));
} else if (chunk->kind == SCR_DYN_STR) {
chained = scr_stream_end(s, NULL, chunk->v.str, bcs_callback(cb, self, s));
@@ -1376,8 +1388,6 @@ static ScrDyn *bcs_getter_call(ScrClosure *closure, ScrDyn *const *args, size_t
return bcs_flowing(s);
case BCS_P_ERRORED:
return bcs_errored(s);
- case BCS_P_WRITABLE_OBJECT_MODE:
- return scr_dyn_new_bool(false);
case BCS_P_READABLE_LENGTH:
case BCS_P_READABLE_HWM:
case BCS_P_WRITABLE_LENGTH:
diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c
index ec49dd383..80e01b08c 100644
--- a/packages/runtime/src/scr_dyn_invoke.c
+++ b/packages/runtime/src/scr_dyn_invoke.c
@@ -1923,6 +1923,8 @@ static ScrDyn *dyn_function_constructor(void) {
ScrDyn *scr_dyn_global_constructor(const ScrStr *name) {
if (strcmp(name->data, dyn_s20) == 0) return dyn_function_constructor();
+ ScrDyn *error = scr_dyn_error_constructor(name->data);
+ if (error) return error;
ScrDyn *ctor = dyn_global_ctor(name->data);
if (!ctor) scr_trap(dyn_s3);
return ctor;
diff --git a/packages/runtime/src/scr_ed25519.c b/packages/runtime/src/scr_ed25519.c
new file mode 100644
index 000000000..c6d6ce370
--- /dev/null
+++ b/packages/runtime/src/scr_ed25519.c
@@ -0,0 +1,76 @@
+/* Ed25519 for scr_pki.c: libsodium's ref10 implementation (vendored, see
+ * ../vendor/README.md), textually included so it builds as this one unit.
+ * It links only into programs that name Ed25519 (the ed25519 runtime
+ * feature) and installs itself into scr_pki.c at startup; without it,
+ * Ed25519 signing, verification and public-key derivation throw a coded
+ * SC2020 error.
+ *
+ * Signing runs in constant time. Verification rejects a non-canonical S
+ * and the small-order R and public-key encodings, then checks the
+ * cofactorless equation, which is what Node 24 answers. */
+#include "scr_runtime.h"
+
+#include
+#include
+#include
+#include
+
+#include "scr_pki.h"
+
+#define CONFIGURED 1
+#include "../vendor/libsodium/ed25519_ref10.c"
+#include "../vendor/libsodium/hash_sha512_cp.c"
+#include "../vendor/libsodium/verify.c"
+#include "../vendor/libsodium/sign.c"
+#include "../vendor/libsodium/open.c"
+#include "../vendor/libsodium/keypair.c"
+#undef CONFIGURED
+
+/* The libsodium utilities those units call. */
+void sodium_memzero(void *const pnt, const size_t len) {
+ volatile unsigned char *p = (volatile unsigned char *)pnt;
+ for (size_t i = 0; i < len; i++) p[i] = 0U;
+}
+
+int sodium_memcmp(const void *const b1_, const void *const b2_, size_t len) {
+ const volatile unsigned char *b1 = (const volatile unsigned char *)b1_;
+ const volatile unsigned char *b2 = (const volatile unsigned char *)b2_;
+ volatile unsigned char d = 0U;
+ for (size_t i = 0; i < len; i++) d |= b1[i] ^ b2[i];
+ return (1 & ((d - 1) >> 8)) - 1;
+}
+
+int sodium_is_zero(const unsigned char *n, const size_t nlen) {
+ volatile unsigned char d = 0U;
+ for (size_t i = 0; i < nlen; i++) d |= n[i];
+ return 1 & ((d - 1) >> 8);
+}
+
+void randombytes_buf(void *const buf, const size_t size) { arc4random_buf(buf, size); }
+
+static void se_public_key(uint8_t pub[32], const uint8_t seed[32]) {
+ uint8_t sk[64];
+ crypto_sign_ed25519_seed_keypair(pub, sk, seed);
+ sodium_memzero(sk, sizeof sk);
+}
+
+static void se_sign(uint8_t sig[64], const uint8_t seed[32], const uint8_t pub[32], const uint8_t *m,
+ size_t n) {
+ uint8_t sk[64];
+ memcpy(sk, seed, 32);
+ memcpy(sk + 32, pub, 32);
+ crypto_sign_ed25519_detached(sig, NULL, m, n, sk);
+ sodium_memzero(sk, sizeof sk);
+}
+
+static bool se_verify(const uint8_t pub[32], const uint8_t *m, size_t n, const uint8_t sig[64]) {
+ return crypto_sign_ed25519_verify_detached(sig, m, n, pub) == 0;
+}
+
+static const ScrPkiEd25519 se_table = {
+ .public_key = se_public_key,
+ .sign = se_sign,
+ .verify = se_verify,
+};
+
+__attribute__((constructor)) static void se_install(void) { scr_pki_ed25519 = &se_table; }
diff --git a/packages/runtime/src/scr_http.c b/packages/runtime/src/scr_http.c
index d9876c30b..a5847e59b 100644
--- a/packages/runtime/src/scr_http.c
+++ b/packages/runtime/src/scr_http.c
@@ -2918,22 +2918,6 @@ static bool scr_http_conn_parse_head(ScrHttpConn *conn, size_t head_len, bool *o
conn->trailer_bytes = 0;
conn->trailer_fields = 0;
- if (!http10 && conn->srv->require_host_header) {
- bool has_host = false;
- for (size_t i = 0; i < req->nheaders; i++) {
- const ScrStr *name = req->hnames[i];
- if (name->len == 4 && memcmp(name->data, "host", 4) == 0) {
- has_host = true;
- break;
- }
- }
- if (!has_host) {
- scr_http_req_release(req);
- scr_http_conn_missing_host(conn);
- return true;
- }
- }
-
/* framing: chunked wins over Content-Length (RFC 9112) */
bool chunked = false;
long long content_length = 0;
@@ -2958,12 +2942,33 @@ static bool scr_http_conn_parse_head(ScrHttpConn *conn, size_t head_len, bool *o
}
}
+ /* HTTP/1.1 requires Host (requireHostHeader). CONNECT requests, and
+ * upgrade requests an 'upgrade' listener takes, leave the parser before
+ * Node's check. */
+ bool is_connect = req->method->len == 7 && memcmp(req->method->data, "CONNECT", 7) == 0;
+ bool takes_upgrade = has_upgrade_hdr && conn_upgrade_token && conn->srv->upgrade_ls.n > 0;
+ if (!http10 && conn->srv->require_host_header && !is_connect && !takes_upgrade) {
+ bool has_host = false;
+ for (size_t i = 0; i < req->nheaders; i++) {
+ const ScrStr *name = req->hnames[i];
+ if (name->len == 4 && memcmp(name->data, "host", 4) == 0) {
+ has_host = true;
+ break;
+ }
+ }
+ if (!has_host) {
+ scr_http_req_release(req);
+ scr_http_conn_missing_host(conn);
+ return true;
+ }
+ }
+
/* A CONNECT request: 'connect' fires INSTEAD of 'request' — the
* upgrade machinery's exact shape ((req, socket, head); the parser
* steps aside; no listener destroys the socket, Node's default). The
* h2 compat server's HTTP/1.1 arm lands here too; its HTTP/2 arm is
* dispatched separately with the compatibility response handle. */
- if (req->method->len == 7 && memcmp(req->method->data, "CONNECT", 7) == 0) {
+ if (is_connect) {
memmove(conn->buf, conn->buf + head_len, conn->len - head_len);
conn->len -= head_len;
if (!scr_net_ls_has_live(&conn->srv->connect_ls)) {
diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c
index 4e21fa397..f610ffdbb 100644
--- a/packages/runtime/src/scr_json.c
+++ b/packages/runtime/src/scr_json.c
@@ -1814,6 +1814,55 @@ static void scr_builtin_object_prototype_cleanup(void) {
scr_dyn_release(prototype);
}
+/* The error constructors as values (an error's `constructor`, and the
+ * globals in JavaScript): one interned function per constructor and thread,
+ * indexed like scr_error_vts, with the constructor's name and length.
+ * Calling one through the value is not supported yet, as for the other
+ * interned constructors (scr_dyn_global_constructor). */
+static const char *const scr_error_ctor_names[] = {
+ "Error", "TypeError", "RangeError", "SyntaxError", NULL,
+ "ReferenceError", "EvalError", "URIError", "AggregateError",
+};
+#define SCR_ERROR_CTORS (sizeof scr_error_ctor_names / sizeof scr_error_ctor_names[0])
+static SCR_TL ScrDyn *scr_error_ctor_values[SCR_ERROR_CTORS];
+static SCR_TL bool scr_error_ctors_registered;
+
+static void scr_error_ctors_cleanup(void) {
+ for (size_t i = 0; i < SCR_ERROR_CTORS; i++) {
+ scr_dyn_release(scr_error_ctor_values[i]);
+ scr_error_ctor_values[i] = NULL;
+ }
+}
+
+static ScrDyn *scr_error_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) {
+ (void)args;
+ (void)argc;
+ char message[128];
+ int length = snprintf(message, sizeof message,
+ "calling the %s constructor through a stored value is not supported yet",
+ (const char *)closure->fn);
+ scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, "SC2020");
+ return NULL;
+}
+
+ScrDyn *scr_dyn_error_constructor(const char *name) {
+ for (size_t i = 0; i < SCR_ERROR_CTORS; i++) {
+ if (!scr_error_ctor_names[i] || strcmp(scr_error_ctor_names[i], name) != 0) continue;
+ if (!scr_error_ctors_registered) {
+ scr_error_ctors_registered = true;
+ scr_atexit(scr_error_ctors_cleanup);
+ }
+ if (!scr_error_ctor_values[i]) {
+ uint32_t length = strcmp(name, "AggregateError") == 0 ? 2 : 1;
+ scr_error_ctor_values[i] =
+ scr_dyn_new_func(scr_closure_new((void *)scr_error_ctor_names[i], 0), scr_error_ctor_call, length,
+ "native:constructor", scr_error_ctor_names[i]);
+ }
+ return scr_dyn_retain(scr_error_ctor_values[i]);
+ }
+ return NULL;
+}
+
ScrDyn *scr_dyn_object_prototype(void) {
if (!scr_builtin_object_prototype) {
scr_builtin_object_prototype = scr_dyn_alloc(SCR_DYN_OBJ);
@@ -7951,6 +8000,17 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) {
return out;
}
case SCR_DYN_FUNC: {
+ /* The interned builtin constructors print with their names, as in Node
+ * (function Map() { [native code] }); other functions without a source
+ * text print the anonymous native form. */
+ if (d->v.fn.sig && strcmp(d->v.fn.sig, "native:constructor") == 0 && d->v.fn.name) {
+ ScrJsonBuf b;
+ scr_jb_init(&b);
+ scr_jb_puts(&b, "function ");
+ scr_jb_puts(&b, d->v.fn.name);
+ scr_jb_puts(&b, "() { [native code] }");
+ return scr_jb_finish(&b);
+ }
static const char f[] = "function () { [native code] }";
return scr_str_new(f, sizeof f - 1);
}
@@ -11943,21 +12003,18 @@ static ScrDyn *scr_dyn_obj_read_receiver(const ScrDyn *d, const char *key, size_
ScrDyn *own = scr_dyn_obj_get(d, key, key_len);
if (own) return scr_dyn_retain(own);
if (key_len == 11 && memcmp(key, "constructor", 11) == 0) {
- static const char *const tokens[] = {
- "[builtin Error]", "[builtin TypeError]",
- "[builtin RangeError]", "[builtin SyntaxError]",
- NULL, "[builtin ReferenceError]", "[builtin EvalError]", "[builtin URIError]",
- "[builtin AggregateError]",
+ /* An error's constructor: the interned function value its global
+ * reads as (scr_dyn_error_constructor). */
+ static const char *const names[] = {
+ "Error", "TypeError", "RangeError", "SyntaxError", NULL,
+ "ReferenceError", "EvalError", "URIError", "AggregateError",
};
size_t hit = scr_errdyn_find(d, false);
const ScrVt *vt = hit == SIZE_MAX ? NULL : scr_errdyn_cache[hit].err->vt;
- for (size_t kind = 0; vt && kind < sizeof tokens / sizeof tokens[0]; kind++) {
- if (!tokens[kind]) continue;
+ for (size_t kind = 0; vt && kind < sizeof names / sizeof names[0]; kind++) {
+ if (!names[kind]) continue;
if (vt != &scr_error_vts[kind]) continue;
- ScrStr *token = scr_str_new(tokens[kind], strlen(tokens[kind]));
- ScrDyn *result = scr_dyn_new_str(token);
- scr_str_release(token);
- return result;
+ return scr_dyn_error_constructor(names[kind]);
}
}
return scr_dyn_retain(scr_dyn_undefined());
diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c
index 9bebdec5e..4be596ab5 100644
--- a/packages/runtime/src/scr_lib.c
+++ b/packages/runtime/src/scr_lib.c
@@ -9225,6 +9225,10 @@ ScrCryptoHash *scr_crypto_hmac_new_dyn(ScrStr *alg, ScrDyn *key, ScrDyn *options
if (key_object) {
bytes = scr_bytes_new(SCR_BYTES_U8, (double)key_object_len);
if (key_object_len) memcpy(bytes->data, key_object, key_object_len);
+ } else if (scr_crypto_is_key_object(key)) {
+ /* A private or public KeyObject: scr_pki.c reports Node's error. */
+ scr_crypto_reject_asymmetric_secret(key);
+ return NULL;
} else if (key->kind == SCR_DYN_STR) {
ScrStr *utf8 = encoding ? NULL : scr_str_new("utf8", 4);
bytes = scr_bytes_from_str_checked(key->v.str, encoding ? encoding : utf8);
@@ -9856,9 +9860,15 @@ const unsigned char *scr_key_object_bytes(const ScrDyn *value, size_t *len) {
return key->data;
}
-/* `value instanceof KeyObject`. */
+void (*scr_crypto_reject_asymmetric_secret)(const ScrDyn *key) = NULL;
+
+/* `value instanceof KeyObject`: a secret key, or a private or public key
+ * (scr_pki.c, matched by its handle tags so this unit does not link it). */
bool scr_crypto_is_key_object(const ScrDyn *value) {
size_t len;
+ if (value->kind == SCR_DYN_HANDLE &&
+ (value->v.handle.tag == SCR_DYNH_PRIVATE_KEY || value->v.handle.tag == SCR_DYNH_PUBLIC_KEY))
+ return true;
return scr_key_object_bytes(value, &len) != NULL;
}
diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c
index f091daede..29a4af393 100644
--- a/packages/runtime/src/scr_net.c
+++ b/packages/runtime/src/scr_net.c
@@ -517,6 +517,9 @@ struct ScrNetSocket {
int remote_port_cache;
bool remote_v6_cache;
ScrStr *pending_err; /* deferred connect failure */
+ /* pending_err's code when its message does not name one (static
+ * storage; NULL = the code the message maps to) */
+ const char *pending_code;
/* the caller-lookup dial (net.connect with a lookup option): true while
* the lookup decides; dial_ips holds its answered addresses (+1 each),
* dialed in order — a connect failure closes the fd and tries
@@ -531,6 +534,9 @@ struct ScrNetSocket {
size_t dial_n, dial_i;
/* a host name the dial resolves first (+1; NULL for IP literals) */
ScrStr *dial_host;
+ /* the host a client dial was given, Node's socket._host (+1; NULL on
+ * server sockets): tls.connect({ socket }) verifies against it */
+ ScrStr *host_name;
/* the in-flight dial chain's LAST failure — surfaced (moved into
* pending_err) only when the list exhausts; a later success drops it */
ScrStr *dial_err;
@@ -607,6 +613,12 @@ struct ScrNetSocket {
* until the proxy answers 200: writes buffer and the transport
* handshake waits until the tunnel is up */
struct ScrNetTunnel *tunnel;
+ /* new tls.TLSSocket(socket) and tls.connect({ socket }): on the wrapped
+ * socket, the socket that took over its connection (+1; end() and
+ * destroy() act on it); on that socket, the one it took over (+1; its
+ * 'close' fires just before this socket's, as in Node) */
+ struct ScrNetSocket *adopted_by;
+ struct ScrNetSocket *adopted_from;
};
/* The CONNECT head still being sent from out_off, and the proxy's
@@ -616,6 +628,9 @@ typedef struct ScrNetTunnel {
size_t out_len, out_off;
char *in;
size_t in_len;
+ /* bytes a wrapped socket left unsent, which go out before the
+ * transport's own; no response head is read */
+ bool send_only;
} ScrNetTunnel;
/* The 'lookup' event's half of the runtime, installed by the first
@@ -712,6 +727,7 @@ void scr_net_sock_release(ScrNetSocket *s) {
free(s->dial_ips);
scr_str_release(s->dial_err);
scr_str_release(s->dial_host);
+ scr_str_release(s->host_name);
scr_str_release(s->unix_path);
scr_net_ls_drop(&s->lookup_ls);
if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s);
@@ -721,6 +737,8 @@ void scr_net_sock_release(ScrNetSocket *s) {
if (s->pipe_dst) scr_net_sock_release(s->pipe_dst);
if (s->conn_pending) scr_net_server_release(s->conn_pending);
if (s->server) scr_net_server_release(s->server);
+ if (s->adopted_by) scr_net_sock_release(s->adopted_by);
+ if (s->adopted_from) scr_net_sock_release(s->adopted_from);
if (s->fd >= 0) scr_net_close_fd_raw(s->fd);
#ifdef SCR_RC_AUDIT
scr_net_live--;
@@ -962,7 +980,7 @@ static ScrNetSocket *scr_net_sock_new(void) {
static void scr_net_sock_update_read(ScrNetSocket *s) {
bool want = s->fd >= 0 && !s->connecting && !s->rd_eof && !s->user_paused &&
(s->data_ls.n > 0 || s->pipe_dst != NULL || s->native_data != NULL ||
- s->tunnel != NULL /* the proxy's CONNECT answer */ ||
+ (s->tunnel != NULL && !s->tunnel->send_only) /* the proxy's CONNECT answer */ ||
s->flowing /* resume(): flow (and discard sans listeners) */ ||
s->readable_ls.n > 0 /* paused-mode consumer: bytes buffer */ ||
s->tops != NULL /* a transport is ALWAYS a consumer: the TLS
@@ -977,7 +995,8 @@ static void scr_net_sock_update_read(ScrNetSocket *s) {
* finished socket the same way */ ||
(!s->eof_pending && s->rlen - s->rhead < SCR_NET_READ_AHEAD)
/* no consumer yet: read ahead into the buffer, as Node's
- * stream does up to its high-water mark */);
+ * stream does up to its high-water mark */) &&
+ !(s->tunnel != NULL && s->tunnel->send_only);
if (want && !s->read_armed) {
scr_net_watch_read(s->fd, s, true);
s->read_armed = true;
@@ -1056,6 +1075,23 @@ static void scr_net_sock_buffer(ScrNetSocket *s, const char *data, size_t len) {
* dead or ended write half (Node fires 'error' there; this slice drops —
* SEMANTICS.md documents the bound). */
static void scr_net_sock_write_raw(ScrNetSocket *s, const char *data, size_t len) {
+ if (s->adopted_by != NULL) {
+ /* Node writes these bytes to the wire as they are, between the TLS
+ * records of the socket that took over (the peer then fails the
+ * session). Bytes the kernel cannot take now end that socket. */
+ ScrNetSocket *t = s->adopted_by;
+ while (len > 0 && t->fd >= 0) {
+ ssize_t sent = scr_net_send(t->fd, data, len);
+ if (sent < 0 && errno == EINTR) continue;
+ if (sent <= 0) {
+ scr_net_sock_destroy(t);
+ break;
+ }
+ data += sent;
+ len -= (size_t)sent;
+ }
+ return;
+ }
/* A socket whose dial hasn't STARTED yet (the agent's deferred dial,
* the caller-lookup wait) has no fd but is logically connecting —
* bytes buffer and flush at establishment like any mid-connect write. */
@@ -1490,6 +1526,12 @@ static void scr_net_sock_tunnel_pump(ScrNetSocket *s) {
}
scr_net_sock_update_write(s);
if (s->fd < 0 || t->out_off < t->out_len) return;
+ if (t->send_only) {
+ scr_net_tunnel_free(t);
+ s->tunnel = NULL;
+ scr_net_sock_connected(s);
+ return;
+ }
for (;;) {
char buf[4096];
ssize_t n = read(s->fd, buf, sizeof buf);
@@ -1592,6 +1634,10 @@ static void scr_net_sock_transport_pump(ScrNetSocket *s) {
if (scr_exc_pending()) return;
scr_net_fire0_this(&s->ready_ls, s, SCR_DYNH_NET_SOCKET);
if (scr_exc_pending()) return;
+ if (s->tops->after_established) {
+ s->tops->after_established(s->tctx);
+ if (scr_exc_pending()) return;
+ }
scr_net_sock_flush(s);
scr_net_sock_update_read(s);
scr_net_sock_update_write(s);
@@ -2250,6 +2296,8 @@ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) {
* autoselect), and any other name is resolved when the dial starts. */
static void scr_net_sock_set_peer(ScrNetSocket *s, ScrStr *host /*borrowed, nullable*/) {
const char *h = host && host->len > 0 ? host->data : "localhost";
+ scr_str_release(s->host_name);
+ s->host_name = scr_str_new(h, strlen(h));
if (strcmp(h, "localhost") == 0) {
h = "127.0.0.1";
if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, "localhost", 9, h, 4, NULL);
@@ -3024,6 +3072,10 @@ void scr_net_sock_write_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/) {
}
void scr_net_sock_end(ScrNetSocket *s) {
+ if (s->adopted_by != NULL) {
+ scr_net_sock_end(s->adopted_by);
+ return;
+ }
if (s->fd < 0 || s->wr_ending) return;
s->wr_ending = true;
scr_net_sock_maybe_finish_write(s);
@@ -3136,7 +3188,10 @@ void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/) {
double scr_net_sock_bytes_written(ScrNetSocket *s) { return (double)s->bytes_written; }
/* socket.readable: true until the read half is done (peer FIN / destroy). */
-bool scr_net_sock_readable(ScrNetSocket *s) { return s->fd >= 0 && !s->rd_eof; }
+bool scr_net_sock_readable(ScrNetSocket *s) {
+ if (s->adopted_by != NULL) return scr_net_sock_readable(s->adopted_by);
+ return s->fd >= 0 && !s->rd_eof;
+}
/* socket.setTimeout(ms): ms > 0 arms the idle timer NOW (Node starts the
* clock at the call — connecting time counts), ms <= 0 disables. */
@@ -3206,6 +3261,7 @@ ScrStr *scr_net_sock_remote_family(ScrNetSocket *s) {
/* socket.localAddress / localPort / localFamily: read from the open
* socket (undefined while connecting and after close, as in Node). */
static bool scr_net_sock_local(ScrNetSocket *s, char *addr, size_t cap, int *port, bool *v6) {
+ if (s->adopted_by != NULL) return scr_net_sock_local(s->adopted_by, addr, cap, port, v6);
if (s->fd < 0 || s->connecting || s->dial_deferred || s->lookup_wait) return false;
struct sockaddr_storage ss;
socklen_t len = sizeof ss;
@@ -4652,6 +4708,10 @@ bool scr_net_is_ipv4(const ScrDyn *input) { return scr_net_is_ip(input) == 4; }
bool scr_net_is_ipv6(const ScrDyn *input) { return scr_net_is_ip(input) == 6; }
void scr_net_sock_destroy(ScrNetSocket *s) {
+ if (s->adopted_by != NULL) {
+ scr_net_sock_destroy(s->adopted_by);
+ return;
+ }
if (s->fd < 0 && !s->in_registry) return;
s->rd_eof = true;
s->wr_done = true;
@@ -4833,7 +4893,8 @@ void scr_net_sock_on_error(ScrNetSocket *s, ScrClosure *cb /*moves*/, ScrChildEr
}
void scr_net_sock_on_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
- if (s->close_emitted || (!s->connecting && s->fd < 0)) {
+ /* a socket that took over another's connection never emits 'connect' */
+ if (s->close_emitted || (!s->connecting && s->fd < 0) || s->adopted_from != NULL) {
scr_closure_release(cb);
return;
}
@@ -4939,12 +5000,15 @@ void scr_net_sock_clear_native_reader(ScrNetSocket *s) {
/* socket.destroyed — true once the fd is gone (destroy() or full close),
* Node's flag for "this stream is done". */
-bool scr_net_sock_destroyed(ScrNetSocket *s) { return s->fd < 0; }
+bool scr_net_sock_destroyed(ScrNetSocket *s) {
+ return s->adopted_by != NULL ? scr_net_sock_destroyed(s->adopted_by) : s->fd < 0;
+}
/* socket.writable — Node's stream flag: the write half is open (no end()
* yet, no FIN sent, fd alive). Connecting sockets answer true — writes
* queue, exactly Node. */
bool scr_net_sock_writable(ScrNetSocket *s) {
+ if (s->adopted_by != NULL) return scr_net_sock_writable(s->adopted_by);
return s->fd >= 0 && !s->wr_ending && !s->wr_done;
}
@@ -4996,8 +5060,78 @@ void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops,
scr_net_sock_update_write(s);
}
+/* A fresh socket takes over raw's open connection: the fd, the bytes it
+ * buffered either way and the peer identity. raw keeps its listeners and
+ * its server's accounting but does no I/O of its own; end() and destroy()
+ * on it act on the new socket, and its 'close' fires just before the new
+ * socket's. Bytes raw had not sent yet go out first. +1; the caller sets
+ * the transport, then starts it with scr_net_sock_adopt_start. NULL when
+ * raw has no open, idle connection to give (connecting, closed, already
+ * wrapped, or carrying a transport). */
+ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw) {
+ if (raw->fd < 0 || scr_net_sock_connecting(raw) || raw->adopted_by != NULL || raw->tops != NULL)
+ return NULL;
+ ScrNetSocket *t = scr_net_sock_new();
+ scr_str_release(scr_net_sock_remote_address(raw)); /* cache the peer while the fd is raw's */
+ if (raw->remote_cache) {
+ t->remote_cache = scr_str_retain(raw->remote_cache);
+ t->remote_port_cache = raw->remote_port_cache;
+ t->remote_v6_cache = raw->remote_v6_cache;
+ }
+ memcpy(t->peer_ip, raw->peer_ip, sizeof t->peer_ip);
+ t->peer_port = raw->peer_port;
+ t->unrefd = raw->unrefd;
+ t->rd_eof = raw->rd_eof;
+ scr_net_sock_timer_cancel(raw);
+ if (raw->read_armed) scr_net_watch_read(raw->fd, raw, false);
+ if (raw->write_armed) scr_net_watch_write(raw->fd, raw, false);
+ raw->read_armed = raw->write_armed = false;
+ t->fd = raw->fd;
+ raw->fd = -1;
+ if (raw->rlen > raw->rhead) scr_net_sock_append_rbuf(t, raw->rbuf + raw->rhead, raw->rlen - raw->rhead);
+ raw->rhead = raw->rlen = 0;
+ while (raw->wlen > raw->whead) {
+ ssize_t sent = scr_net_send(t->fd, raw->wbuf + raw->whead, raw->wlen - raw->whead);
+ if (sent < 0 && errno == EINTR) continue;
+ if (sent <= 0) break; /* EAGAIN, or an error the transport will meet */
+ raw->whead += (size_t)sent;
+ }
+ if (raw->wlen > raw->whead) {
+ ScrNetTunnel *tun = calloc(1, sizeof *tun);
+ if (!tun) scr_net_oom();
+ tun->out_len = raw->wlen - raw->whead;
+ tun->out = malloc(tun->out_len);
+ if (!tun->out) scr_net_oom();
+ memcpy(tun->out, raw->wbuf + raw->whead, tun->out_len);
+ tun->send_only = true;
+ t->tunnel = tun;
+ }
+ raw->whead = raw->wlen = 0;
+ raw->adopted_by = scr_net_sock_retain(t);
+ t->adopted_from = scr_net_sock_retain(raw);
+ scr_net_sock_register(t);
+ return t;
+}
+
+/* Starts an adopted socket once its transport is set: the unsent bytes
+ * drain first, then the handshake runs (over any bytes raw had already
+ * read). */
+void scr_net_sock_adopt_start(ScrNetSocket *t) {
+ if (t->tunnel != NULL) {
+ scr_net_sock_update_write(t);
+ return;
+ }
+ /* the handshake starts from the poller (the socket is writable), never
+ * the constructing stack — Node's start is asynchronous too */
+ scr_net_sock_transport_want_write(t);
+}
+
int scr_net_sock_fd(ScrNetSocket *s) { return s->fd; }
+/* The host a client socket was told to dial (Node's socket._host). +1;
+ * NULL on server sockets. */
+ScrStr *scr_net_sock_dial_name(ScrNetSocket *s) { return scr_str_retain(s->host_name); }
+
/* Drain up to n bytes of the receive buffer (peeked/unshifted bytes) —
* consumers call this BEFORE the fd so re-queued bytes keep their
* position in the stream. */
@@ -5050,6 +5184,32 @@ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len) {
s->rcap = total;
}
+/* The peer closed before the handshake finished, on a socket whose
+ * stream the program already holds (new tls.TLSSocket): 'end', then
+ * 'close' without an error, as Node reports it. */
+void scr_net_sock_transport_eof(ScrNetSocket *s) {
+ scr_net_sock_eof(s);
+ if (s->fd >= 0) scr_net_sock_close_fd(s);
+}
+
+/* A listener for the handshake's completion once TCP is up (a socket
+ * tls.connect({ socket }) took over is up from the start): it registers
+ * until the transport establishes. */
+void scr_net_sock_on_established(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
+ if (s->close_emitted || s->fd < 0 || s->tops == NULL || s->t_est) {
+ scr_closure_release(cb);
+ return;
+ }
+ scr_net_ls_add(&s->conn_ls, cb, NULL, once);
+}
+
+/* scr_net_sock_transport_error with the error's code given (static
+ * storage), for messages whose text does not name it. */
+void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const char *code) {
+ if (!s->pending_err) s->pending_code = code;
+ scr_net_sock_transport_error(s, msg);
+}
+
void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg) {
if (msg != NULL) {
if (!s->pending_err) s->pending_err = scr_str_new(msg, strlen(msg));
@@ -5141,6 +5301,72 @@ static void scr_net_server_settle(ScrNetServer *srv) {
scr_net_server_unregister(srv);
}
+/* Fires a closed socket's 'close' and settles it: its server's count
+ * drains, the protocol layer hears it, listeners drop and it leaves the
+ * registry. A socket that took over another's connection settles that one
+ * first, so the wrapped socket's 'close' comes just before its own. False
+ * with an exception pending. */
+static bool scr_net_sock_settle(ScrNetSocket *sock) {
+ ScrNetSocket *raw = sock->adopted_from;
+ if (raw != NULL) {
+ sock->adopted_from = NULL;
+ if (raw->adopted_by == sock) {
+ raw->adopted_by = NULL;
+ scr_net_sock_release(sock);
+ }
+ bool ok = raw->close_emitted || scr_net_sock_settle(raw);
+ scr_net_sock_release(raw);
+ if (!ok) return false;
+ }
+ sock->close_emitted = true;
+ sock->emit_close = false;
+ /* The owning server's count drains FIRST, and a now-drained closing
+ * server settles before this socket's own 'close' fires — Node's
+ * observed order (differential: the net-echo fixture). */
+ if (sock->server) {
+ ScrNetServer *srv = sock->server;
+ sock->server = NULL;
+ srv->nconns--;
+ if (srv->closing && srv->nconns == 0 && !srv->close_emitted) {
+ scr_net_server_settle(srv);
+ }
+ scr_net_server_release(srv);
+ if (scr_exc_pending()) return false;
+ }
+ if (sock->native_closed) {
+ sock->native_closed(sock->native_ctx);
+ if (scr_exc_pending()) return false;
+ }
+ {
+ ScrDyn *had_error = scr_dyn_new_bool(sock->had_error);
+ scr_net_fire_arg_this(&sock->close_ls, had_error, sock, SCR_DYNH_NET_SOCKET);
+ scr_dyn_release(had_error);
+ }
+ /* settle: listeners drop (cycle story) */
+ scr_net_ls_drop(&sock->data_ls);
+ scr_net_ls_drop(&sock->end_ls);
+ scr_net_ls_drop(&sock->close_ls);
+ scr_net_ls_drop(&sock->err_ls);
+ scr_net_ls_drop(&sock->conn_ls);
+ scr_net_ls_drop(&sock->timeout_ls);
+ scr_net_ls_drop(&sock->readable_ls);
+ scr_net_ls_drop(&sock->finish_ls);
+ scr_net_ls_drop(&sock->wcb_ls);
+ scr_net_ls_drop(&sock->ready_ls);
+ scr_net_ls_drop(&sock->lookup_ls);
+ if (sock->tops && sock->tops->settle) sock->tops->settle(sock->tctx);
+ if (sock->pipe_dst) {
+ scr_net_sock_release(sock->pipe_dst);
+ sock->pipe_dst = NULL;
+ }
+ if (sock->conn_pending) {
+ scr_net_server_release(sock->conn_pending);
+ sock->conn_pending = NULL;
+ }
+ scr_net_sock_unregister(sock);
+ return !scr_exc_pending();
+}
+
/* Sockets first (a dying connection may drain its server), servers after.
* Every fire checks for a pending exception and bails — the loop surfaces
* it as an uncaught throw. */
@@ -5200,7 +5426,19 @@ static void scr_net_sweep(void) {
* listeners — and the unhandled-'error' exit */
bool consumed = false;
if (sock->native_err) consumed = sock->native_err(sock->native_ctx, msg);
- if (!consumed && !scr_exc_pending()) scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
+ if (!consumed && !scr_exc_pending()) {
+ ScrError *coded = NULL;
+ ScrError *saved = scr_event_error_payload;
+ if (sock->pending_code != NULL) {
+ coded = scr_error_new(SCR_ERR_ERROR, msg);
+ coded->code = scr_str_new(sock->pending_code, strlen(sock->pending_code));
+ scr_event_error_payload = coded;
+ }
+ scr_net_fire_err_this(&sock->err_ls, msg, sock, SCR_DYNH_NET_SOCKET);
+ scr_event_error_payload = saved;
+ if (coded != NULL) scr_error_release(coded);
+ }
+ sock->pending_code = NULL;
scr_str_release(msg);
if (scr_exc_pending()) {
scr_net_sock_release(sock);
@@ -5208,58 +5446,7 @@ static void scr_net_sweep(void) {
}
}
if (sock->emit_close && !sock->close_emitted && sock->fd < 0) {
- sock->close_emitted = true;
- sock->emit_close = false;
- /* The owning server's count drains FIRST, and a now-drained closing
- * server settles before this socket's own 'close' fires — Node's
- * observed order (differential: the net-echo fixture). */
- if (sock->server) {
- ScrNetServer *srv = sock->server;
- sock->server = NULL;
- srv->nconns--;
- if (srv->closing && srv->nconns == 0 && !srv->close_emitted) {
- scr_net_server_settle(srv);
- }
- scr_net_server_release(srv);
- if (scr_exc_pending()) {
- scr_net_sock_release(sock);
- return;
- }
- }
- if (sock->native_closed) {
- sock->native_closed(sock->native_ctx);
- if (scr_exc_pending()) {
- scr_net_sock_release(sock);
- return;
- }
- }
- {
- ScrDyn *had_error = scr_dyn_new_bool(sock->had_error);
- scr_net_fire_arg_this(&sock->close_ls, had_error, sock, SCR_DYNH_NET_SOCKET);
- scr_dyn_release(had_error);
- }
- /* settle: listeners drop (cycle story) */
- scr_net_ls_drop(&sock->data_ls);
- scr_net_ls_drop(&sock->end_ls);
- scr_net_ls_drop(&sock->close_ls);
- scr_net_ls_drop(&sock->err_ls);
- scr_net_ls_drop(&sock->conn_ls);
- scr_net_ls_drop(&sock->timeout_ls);
- scr_net_ls_drop(&sock->readable_ls);
- scr_net_ls_drop(&sock->finish_ls);
- scr_net_ls_drop(&sock->wcb_ls);
- scr_net_ls_drop(&sock->ready_ls);
- scr_net_ls_drop(&sock->lookup_ls);
- if (sock->pipe_dst) {
- scr_net_sock_release(sock->pipe_dst);
- sock->pipe_dst = NULL;
- }
- if (sock->conn_pending) {
- scr_net_server_release(sock->conn_pending);
- sock->conn_pending = NULL;
- }
- scr_net_sock_unregister(sock);
- if (scr_exc_pending()) {
+ if (!scr_net_sock_settle(sock)) {
scr_net_sock_release(sock);
return;
}
@@ -5504,6 +5691,17 @@ static void scr_net_cleanup_atexit(void) {
scr_net_server_release(s->server);
s->server = NULL;
}
+ /* a wrapped socket and the socket that took it over hold each other */
+ if (s->adopted_by) {
+ ScrNetSocket *t = s->adopted_by;
+ s->adopted_by = NULL;
+ scr_net_sock_release(t);
+ }
+ if (s->adopted_from) {
+ ScrNetSocket *raw = s->adopted_from;
+ s->adopted_from = NULL;
+ scr_net_sock_release(raw);
+ }
scr_net_sock_unregister(s);
}
while (scr_net_servers) {
diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h
index 33638f699..ffa9b2f72 100644
--- a/packages/runtime/src/scr_net.h
+++ b/packages/runtime/src/scr_net.h
@@ -351,8 +351,22 @@ typedef struct ScrNetTransportOps {
bool (*pending)(void *tctx);
void (*shutdown_write)(void *tctx);
void (*free)(void *tctx);
+ /* nullable: runs after the established socket's 'connect' listeners
+ * (the TLS 'secure' event, which Node emits after secureConnect) */
+ void (*after_established)(void *tctx);
+ /* nullable: runs when the closed socket settles, after its own
+ * listeners drop; the transport drops the listeners it holds */
+ void (*settle)(void *tctx);
} ScrNetTransportOps;
void scr_net_sock_set_transport(ScrNetSocket *s, const ScrNetTransportOps *ops, void *tctx);
+/* new tls.TLSSocket(socket) and tls.connect({ socket }): a fresh socket
+ * takes over an open connection (fd, buffered bytes, peer identity); the
+ * old socket stays open without I/O, forwards end() and destroy(), and
+ * fires 'close' just before the new one. +1, or NULL when the socket has
+ * no open, idle connection to give. Set the transport on the result, then
+ * call scr_net_sock_adopt_start. */
+ScrNetSocket *scr_net_sock_adopt(ScrNetSocket *raw);
+void scr_net_sock_adopt_start(ScrNetSocket *t);
/* Routes a client socket through an HTTP CONNECT proxy. Call it right
* after the dial (scr_net_connect to the proxy) and before any transport
* is set: once TCP is up, `request` (the CONNECT head, copied) is sent
@@ -366,6 +380,9 @@ void scr_net_sock_set_tunnel(ScrNetSocket *s, const char *request, size_t len);
int scr_net_tunnel_status(const char *head, size_t len);
void scr_net_tunnel_failure(int status, char *msg, size_t cap);
int scr_net_sock_fd(ScrNetSocket *s);
+/* The host a client socket was told to dial (Node's socket._host). +1;
+ * NULL on server sockets. */
+ScrStr *scr_net_sock_dial_name(ScrNetSocket *s);
/* Drain up to n bytes of the socket's receive buffer (peeked/unshifted
* bytes — the demux path); consumers call it before the fd. */
size_t scr_net_sock_take_buffered(ScrNetSocket *s, char *buf, size_t n);
@@ -385,6 +402,16 @@ void scr_net_sock_replay(ScrNetSocket *s, const char *data, size_t len);
* 'error' + teardown to the sweep; NULL tears down without an 'error'
* event, Node's default for an unhandled server-side handshake failure. */
void scr_net_sock_transport_error(ScrNetSocket *s, const char *msg);
+/* The same with the error's code given (static storage), for messages
+ * whose text does not name it. */
+void scr_net_sock_transport_error_code(ScrNetSocket *s, const char *msg, const char *code);
+/* The peer closed before the handshake finished on a wrapped socket:
+ * 'end', then 'close' without an error. */
+void scr_net_sock_transport_eof(ScrNetSocket *s);
+/* Registers a listener that fires when the transport establishes, once
+ * TCP is up (a socket that took over an open connection is up from the
+ * start). */
+void scr_net_sock_on_established(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
/* Defer the server's 'connection' listeners to handshake completion —
* tls.createServer's handler is the 'secureConnection' event, fired with
* the socket only once the TLS layer stands (Node's timing). */
diff --git a/packages/runtime/src/scr_pki.c b/packages/runtime/src/scr_pki.c
new file mode 100644
index 000000000..54aadc06b
--- /dev/null
+++ b/packages/runtime/src/scr_pki.c
@@ -0,0 +1,2144 @@
+/* node:crypto asymmetric keys and signatures over the vendored mbedTLS
+ * public-key layer: createPrivateKey, createPublicKey, generateKeyPairSync
+ * and generateKeyPair for "rsa", "ec" and "ed25519" keys, the
+ * PrivateKeyObject and PublicKeyObject handles (type, asymmetricKeyType,
+ * asymmetricKeyDetails, export(), equals()), crypto.sign and crypto.verify,
+ * and the Sign and Verify objects of createSign and createVerify. Keys read
+ * and write PEM and DER as PKCS#1, SEC1, PKCS#8 and SubjectPublicKeyInfo,
+ * and JWK; the EC curves are prime256v1, secp384r1, secp521r1 and
+ * secp256k1, and the Ed25519 arithmetic is scr_ed25519.c. Argument checks
+ * and errors follow Node 24's lib/internal/crypto/keys.js, keygen.js and
+ * sig.js. Encrypted keys and other key types throw a coded SC2020 error.
+ * All arguments are borrowed. */
+#include "scr_runtime.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "mbedtls/asn1.h"
+#include "mbedtls/asn1write.h"
+#include "mbedtls/base64.h"
+#include "mbedtls/bignum.h"
+#include "mbedtls/ecdsa.h"
+#include "mbedtls/ecp.h"
+#include "mbedtls/md.h"
+#include "mbedtls/oid.h"
+#include "mbedtls/pk.h"
+#include "mbedtls/platform_util.h"
+#include "mbedtls/rsa.h"
+#include "mbedtls/x509_crt.h"
+
+#include "scr_pki.h"
+
+/* ── errors ───────────────────────────────────────────────────────────── */
+
+static void sp_throw(int kind, const char *code, const char *message) {
+ scr_throw_error_msg_code(kind, message, strlen(message), code);
+}
+
+void scr_pki_throw_fmt(int kind, const char *code, const char *fmt, ...) {
+ va_list ap, sized;
+ va_start(ap, fmt);
+ va_copy(sized, ap);
+ int n = vsnprintf(NULL, 0, fmt, sized);
+ va_end(sized);
+ if (n < 0) n = 0;
+ char *message = malloc((size_t)n + 1);
+ if (!message) scr_trap("scriptc: out of memory\n");
+ vsnprintf(message, (size_t)n + 1, fmt, ap);
+ va_end(ap);
+ if (code) scr_throw_error_msg_code(kind, message, (size_t)n, code);
+ else scr_throw_error_msg(kind, message, (size_t)n);
+ free(message);
+}
+
+/* The "Received" text of Node's ERR_INVALID_ARG_VALUE and ERR_OUT_OF_RANGE
+ * messages: a string as util.inspect quotes it (cut at 128 UTF-16 units
+ * plus "..."), a number as ERR_OUT_OF_RANGE prints it when `range`, any
+ * other value as its sketch. +1. */
+static ScrStr *sp_received(const ScrDyn *got, bool range) {
+ if (got->kind == SCR_DYN_STR) return scr_str_inspect_quoted(got->v.str, true);
+ char text[96];
+ if (range && got->kind == SCR_DYN_NUM) return scr_str_new(text, scr_num_received(got->v.num, text));
+ const char *lite = scr_dyn_inspect_lite(got, text, sizeof text);
+ return scr_str_new(lite, strlen(lite));
+}
+
+/* ERR_INVALID_ARG_VALUE for an option ("The property 'options.type' is
+ * invalid. Received 'spki'"). */
+static void sp_property_invalid(const char *name, const ScrDyn *got) {
+ ScrStr *received = sp_received(got, false);
+ scr_pki_throw_fmt(SCR_ERR_TYPE, "ERR_INVALID_ARG_VALUE", "The property '%s' is invalid. Received %.*s", name,
+ (int)received->len, received->data);
+ scr_str_release(received);
+}
+
+static void sp_unsupported_input(void) {
+ sp_throw(SCR_ERR_ERROR, "ERR_OSSL_UNSUPPORTED", "error:1E08010C:DECODER routines::unsupported");
+}
+
+/* The coded refusal for a form this runtime does not implement. */
+static void sp_not_supported(const char *what) {
+ scr_pki_throw_fmt(SCR_ERR_ERROR, "SC2020", "'%s' is part of the standard library types but has no scriptc lowering yet",
+ what);
+}
+
+static int sp_random(void *state, unsigned char *out, size_t len) {
+ (void)state;
+ arc4random_buf(out, len);
+ return 0;
+}
+
+/* ── Ed25519 ──────────────────────────────────────────────────────────
+ * The arithmetic is scr_ed25519.c, which links only into programs that
+ * name Ed25519 and installs itself here at startup. Keys are the 32-byte
+ * seed and the 32-byte encoded public point. */
+const ScrPkiEd25519 *scr_pki_ed25519 = NULL;
+
+/* The Ed25519 arithmetic, or NULL with a coded SC2020 error pending. */
+static const ScrPkiEd25519 *sp_ed(void) {
+ if (!scr_pki_ed25519)
+ sp_throw(SCR_ERR_ERROR, "SC2020",
+ "Ed25519 keys are supported when a string literal in the program names 'ed25519'");
+ return scr_pki_ed25519;
+}
+
+bool scr_pki_ed25519_ready(void) { return sp_ed() != NULL; }
+
+static bool sp_ed_verify(const ScrPkiEd25519 *ed, const uint8_t pub[32], const uint8_t *m, size_t n,
+ const uint8_t *sig, size_t sig_len) {
+ return sig_len == 64 && ed->verify(pub, m, n, sig);
+}
+
+/* ── option bags ──────────────────────────────────────────────────────── */
+
+static bool sp_is_object(const ScrDyn *v) { return v != NULL && v->kind == SCR_DYN_OBJ; }
+
+/* A statically typed object argument (a record or class instance) as a
+ * plain object: +1, or NULL with the error pending. Other values are
+ * retained as they are. */
+static ScrDyn *sp_plain(const ScrDyn *v) {
+ if (v != NULL && v->kind == SCR_DYN_TYPED_REF) return scr_dyn_typed_ref_materialize(v);
+ return scr_dyn_retain((ScrDyn *)(v ? v : scr_dyn_undefined()));
+}
+
+/* A borrowed option, or the undefined singleton. */
+static const ScrDyn *sp_opt(const ScrDyn *obj, const char *key) {
+ const ScrDyn *v = sp_is_object(obj) ? scr_dyn_obj_get(obj, key, strlen(key)) : NULL;
+ return v ? v : scr_dyn_undefined();
+}
+
+static bool sp_is_str(const ScrDyn *v, const char *text) {
+ size_t n = strlen(text);
+ return v->kind == SCR_DYN_STR && v->v.str->len == n && memcmp(v->v.str->data, text, n) == 0;
+}
+
+static bool sp_absent(const ScrDyn *v) { return v->kind == SCR_DYN_UNDEF; }
+
+/* ── keys ─────────────────────────────────────────────────────────────── */
+
+typedef struct SpKey {
+ size_t rc;
+ mbedtls_pk_context pk; /* RSA and EC keys */
+ bool private_key;
+ bool ed25519; /* an Ed25519 key: ed_seed (private keys) and ed_pub */
+ uint8_t ed_seed[32];
+ uint8_t ed_pub[32];
+} SpKey;
+
+static void *sp_key_retain(void *h) {
+ SpKey *k = h;
+ if (k->rc != SIZE_MAX) k->rc++;
+ return h;
+}
+
+static void sp_key_release(void *h) {
+ SpKey *k = h;
+ if (!k || k->rc == SIZE_MAX || --k->rc != 0) return;
+ mbedtls_pk_free(&k->pk);
+ mbedtls_platform_zeroize(k, sizeof *k);
+ free(k);
+}
+
+static SpKey *sp_key_alloc(bool private_key) {
+ SpKey *k = calloc(1, sizeof *k);
+ if (!k) scr_trap("scriptc: out of memory\n");
+ k->rc = 1;
+ k->private_key = private_key;
+ mbedtls_pk_init(&k->pk);
+ return k;
+}
+
+static void sp_key_install(void);
+
+/* Boxes the key (consuming the caller's reference). */
+static ScrDyn *sp_key_box(SpKey *k) {
+ sp_key_install();
+ ScrDyn *box = scr_dyn_new_handle(k, k->private_key ? SCR_DYNH_PRIVATE_KEY : SCR_DYNH_PUBLIC_KEY);
+ sp_key_release(k);
+ return box;
+}
+
+static SpKey *sp_key_of(const ScrDyn *v) {
+ if (v == NULL || v->kind != SCR_DYN_HANDLE) return NULL;
+ if (v->v.handle.tag != SCR_DYNH_PRIVATE_KEY && v->v.handle.tag != SCR_DYNH_PUBLIC_KEY) return NULL;
+ return v->v.handle.ptr;
+}
+
+bool scr_crypto_is_asymmetric_key(const ScrDyn *value) { return sp_key_of(value) != NULL; }
+
+/* The type getter of a KeyObject value: "private", "public", or NULL for
+ * anything else. */
+const char *scr_crypto_asymmetric_key_type(const ScrDyn *value) {
+ SpKey *k = sp_key_of(value);
+ return k ? (k->private_key ? "private" : "public") : NULL;
+}
+
+static bool sp_is_rsa(const SpKey *k) { return mbedtls_pk_get_type(&k->pk) == MBEDTLS_PK_RSA; }
+
+static bool sp_is_ec(const SpKey *k) { return mbedtls_pk_get_type(&k->pk) == MBEDTLS_PK_ECKEY; }
+
+static bool sp_is_ed(const SpKey *k) { return k->ed25519; }
+
+typedef struct {
+ const char *name; /* OpenSSL short name (namedCurve) */
+ const char *nist; /* EC_curve_nist2nid alias, or NULL */
+ const char *jwk; /* JWK crv */
+ mbedtls_ecp_group_id id;
+} SpCurve;
+
+static const SpCurve sp_curves[] = {
+ {"prime256v1", "P-256", "P-256", MBEDTLS_ECP_DP_SECP256R1},
+ {"secp384r1", "P-384", "P-384", MBEDTLS_ECP_DP_SECP384R1},
+ {"secp521r1", "P-521", "P-521", MBEDTLS_ECP_DP_SECP521R1},
+ {"secp256k1", NULL, "secp256k1", MBEDTLS_ECP_DP_SECP256K1},
+};
+
+static const SpCurve *sp_curve_by_id(mbedtls_ecp_group_id id) {
+ for (size_t i = 0; i < sizeof sp_curves / sizeof sp_curves[0]; i++)
+ if (sp_curves[i].id == id) return &sp_curves[i];
+ return NULL;
+}
+
+static const SpCurve *sp_curve_of(const SpKey *k) {
+ return sp_is_ec(k) ? sp_curve_by_id(mbedtls_pk_ec(k->pk)->MBEDTLS_PRIVATE(grp).id) : NULL;
+}
+
+/* A parsed key is usable when it is RSA or EC on a curve above. */
+static bool sp_key_supported(const SpKey *k) {
+ return sp_is_ed(k) || sp_is_rsa(k) || (sp_is_ec(k) && sp_curve_of(k) != NULL);
+}
+
+/* ── DER writing ──────────────────────────────────────────────────────── */
+
+/* mbedtls writes DER backwards into the end of a buffer; these helpers
+ * answer the written length or a negative error. */
+#define SP_DER_MAX 8192
+
+static int sp_write_private_der(SpKey *k, unsigned char *buf, size_t size) {
+ return mbedtls_pk_write_key_der(&k->pk, buf, size);
+}
+
+/* Ed25519's PKCS#8 and SubjectPublicKeyInfo prefixes (RFC 8410): the
+ * 32-byte seed or public key follows. */
+static const uint8_t sp_ed_pkcs8_prefix[16] = {0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06,
+ 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20};
+static const uint8_t sp_ed_spki_prefix[12] = {0x30, 0x2a, 0x30, 0x05, 0x06, 0x03,
+ 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00};
+
+/* Writes prefix || 32 bytes at the end of buf, as the mbedtls writers do. */
+static int sp_write_ed_der(const uint8_t *prefix, size_t prefix_len, const uint8_t key[32],
+ unsigned char *buf, size_t size) {
+ size_t n = prefix_len + 32;
+ if (size < n) return MBEDTLS_ERR_ASN1_BUF_TOO_SMALL;
+ memcpy(buf + size - n, prefix, prefix_len);
+ memcpy(buf + size - 32, key, 32);
+ return (int)n;
+}
+
+static int sp_write_spki_der(SpKey *k, unsigned char *buf, size_t size) {
+ if (sp_is_ed(k)) return sp_write_ed_der(sp_ed_spki_prefix, sizeof sp_ed_spki_prefix, k->ed_pub, buf, size);
+ return mbedtls_pk_write_pubkey_der(&k->pk, buf, size);
+}
+
+/* RSAPublicKey (PKCS#1) DER. */
+static int sp_write_pkcs1_public_der(SpKey *k, unsigned char *buf, size_t size) {
+ unsigned char *p = buf + size;
+ return mbedtls_pk_write_pubkey(&p, buf, &k->pk);
+}
+
+/* PKCS#8 PrivateKeyInfo around the PKCS#1 or SEC1 key, as OpenSSL writes
+ * it: version 0, the AlgorithmIdentifier (rsaEncryption with NULL
+ * parameters, or id-ecPublicKey with the curve OID), and the inner key,
+ * which for EC omits the parameters field. */
+static int sp_write_pkcs8_der(SpKey *k, unsigned char *buf, size_t size) {
+ if (sp_is_ed(k))
+ return sp_write_ed_der(sp_ed_pkcs8_prefix, sizeof sp_ed_pkcs8_prefix, k->ed_seed, buf, size);
+ unsigned char inner[SP_DER_MAX];
+ size_t inner_len;
+ if (sp_is_rsa(k)) {
+ int n = sp_write_private_der(k, inner, sizeof inner);
+ if (n < 0) return n;
+ inner_len = (size_t)n;
+ memmove(inner, inner + sizeof inner - inner_len, inner_len);
+ } else {
+ /* ECPrivateKey ::= SEQUENCE { version 1, privateKey OCTET STRING,
+ * [1] publicKey BIT STRING } */
+ const mbedtls_ecp_keypair *ec = mbedtls_pk_ec(k->pk);
+ unsigned char *c = inner + sizeof inner;
+ size_t len = 0;
+ int r;
+ unsigned char point[160];
+ size_t point_len;
+ if ((r = mbedtls_ecp_point_write_binary(&ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(Q),
+ MBEDTLS_ECP_PF_UNCOMPRESSED, &point_len, point,
+ sizeof point)) != 0)
+ return r;
+ size_t pub_len = 0;
+ if ((r = mbedtls_asn1_write_bitstring(&c, inner, point, point_len * 8)) < 0) return r;
+ pub_len += (size_t)r;
+ if ((r = mbedtls_asn1_write_len(&c, inner, pub_len)) < 0) return r;
+ pub_len += (size_t)r;
+ if ((r = mbedtls_asn1_write_tag(&c, inner, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 1)) < 0)
+ return r;
+ pub_len += (size_t)r;
+ len += pub_len;
+ size_t d_len = (ec->MBEDTLS_PRIVATE(grp).nbits + 7) / 8;
+ unsigned char d[72];
+ if ((r = mbedtls_mpi_write_binary(&ec->MBEDTLS_PRIVATE(d), d, d_len)) != 0) return r;
+ if ((r = mbedtls_asn1_write_octet_string(&c, inner, d, d_len)) < 0) return r;
+ len += (size_t)r;
+ mbedtls_platform_zeroize(d, sizeof d);
+ if ((r = mbedtls_asn1_write_int(&c, inner, 1)) < 0) return r;
+ len += (size_t)r;
+ if ((r = mbedtls_asn1_write_len(&c, inner, len)) < 0) return r;
+ len += (size_t)r;
+ if ((r = mbedtls_asn1_write_tag(&c, inner, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0)
+ return r;
+ len += (size_t)r;
+ inner_len = len;
+ memmove(inner, c, inner_len);
+ }
+ unsigned char *c = buf + size;
+ size_t len = 0;
+ int r;
+ if ((r = mbedtls_asn1_write_octet_string(&c, buf, inner, inner_len)) < 0) goto done;
+ len += (size_t)r;
+ {
+ const char *oid;
+ size_t oid_len;
+ size_t alg_len = 0;
+ if (sp_is_rsa(k)) {
+ if ((r = mbedtls_asn1_write_null(&c, buf)) < 0) goto done;
+ alg_len += (size_t)r;
+ oid = MBEDTLS_OID_PKCS1_RSA;
+ oid_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_PKCS1_RSA);
+ } else {
+ const char *curve_oid;
+ size_t curve_oid_len;
+ if ((r = mbedtls_oid_get_oid_by_ec_grp(mbedtls_pk_ec(k->pk)->MBEDTLS_PRIVATE(grp).id, &curve_oid,
+ &curve_oid_len)) != 0)
+ goto done;
+ if ((r = mbedtls_asn1_write_oid(&c, buf, curve_oid, curve_oid_len)) < 0) goto done;
+ alg_len += (size_t)r;
+ oid = MBEDTLS_OID_EC_ALG_UNRESTRICTED;
+ oid_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_EC_ALG_UNRESTRICTED);
+ }
+ if ((r = mbedtls_asn1_write_oid(&c, buf, oid, oid_len)) < 0) goto done;
+ alg_len += (size_t)r;
+ if ((r = mbedtls_asn1_write_len(&c, buf, alg_len)) < 0) goto done;
+ alg_len += (size_t)r;
+ if ((r = mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0)
+ goto done;
+ alg_len += (size_t)r;
+ len += alg_len;
+ }
+ if ((r = mbedtls_asn1_write_int(&c, buf, 0)) < 0) goto done;
+ len += (size_t)r;
+ if ((r = mbedtls_asn1_write_len(&c, buf, len)) < 0) goto done;
+ len += (size_t)r;
+ if ((r = mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) < 0)
+ goto done;
+ len += (size_t)r;
+ r = (int)len;
+done:
+ mbedtls_platform_zeroize(inner, sizeof inner);
+ return r;
+}
+
+/* PEM armor with 64-character base64 lines and a final newline. */
+static ScrStr *sp_pem(const char *label, const unsigned char *der, size_t len) {
+ size_t b64_len = 0;
+ mbedtls_base64_encode(NULL, 0, &b64_len, der, len);
+ unsigned char *b64 = malloc(b64_len + 1);
+ char *out = malloc(b64_len + b64_len / 64 + 2 * strlen(label) + 64);
+ if (!b64 || !out) scr_trap("scriptc: out of memory\n");
+ mbedtls_base64_encode(b64, b64_len + 1, &b64_len, der, len);
+ size_t o = (size_t)sprintf(out, "-----BEGIN %s-----\n", label);
+ for (size_t i = 0; i < b64_len; i += 64) {
+ size_t n = b64_len - i < 64 ? b64_len - i : 64;
+ memcpy(out + o, b64 + i, n);
+ o += n;
+ out[o++] = '\n';
+ }
+ o += (size_t)sprintf(out + o, "-----END %s-----\n", label);
+ ScrStr *s = scr_str_new(out, o);
+ mbedtls_platform_zeroize(b64, b64_len);
+ mbedtls_platform_zeroize(out, o);
+ free(b64);
+ free(out);
+ return s;
+}
+
+static ScrDyn *sp_dyn_buffer(const unsigned char *data, size_t len) {
+ ScrBytes *bytes = scr_bytes_new(SCR_BYTES_U8, (double)len);
+ bytes->is_buffer = true;
+ if (len) memcpy(bytes->data, data, len);
+ ScrDyn *d = scr_dyn_new_buffer(bytes);
+ scr_bytes_release(bytes);
+ return d;
+}
+
+static ScrDyn *sp_dyn_str(ScrStr *s) {
+ ScrDyn *d = scr_dyn_new_str(s);
+ scr_str_release(s);
+ return d;
+}
+
+/* ── JWK ──────────────────────────────────────────────────────────────── */
+
+/* base64url without padding of the big-endian integer `x` in `len` bytes
+ * (0 = its minimal length). */
+static ScrDyn *sp_jwk_int(const mbedtls_mpi *x, size_t len) {
+ if (len == 0) len = mbedtls_mpi_size(x);
+ if (len == 0) len = 1;
+ unsigned char *raw = malloc(len);
+ size_t b64_len = 0;
+ if (!raw) scr_trap("scriptc: out of memory\n");
+ mbedtls_mpi_write_binary(x, raw, len);
+ mbedtls_base64_encode(NULL, 0, &b64_len, raw, len);
+ unsigned char *b64 = malloc(b64_len + 1);
+ if (!b64) scr_trap("scriptc: out of memory\n");
+ mbedtls_base64_encode(b64, b64_len + 1, &b64_len, raw, len);
+ size_t o = 0;
+ for (size_t i = 0; i < b64_len; i++) {
+ unsigned char c = b64[i];
+ if (c == '=') break;
+ b64[o++] = c == '+' ? '-' : c == '/' ? '_' : c;
+ }
+ ScrDyn *d = sp_dyn_str(scr_str_new((const char *)b64, o));
+ mbedtls_platform_zeroize(raw, len);
+ free(raw);
+ free(b64);
+ return d;
+}
+
+static void sp_set(ScrDyn *obj, const char *key, ScrDyn *value) {
+ scr_dyn_obj_set(obj, key, strlen(key), value);
+}
+
+/* base64url without padding of raw bytes. */
+static ScrDyn *sp_b64url(const uint8_t *raw, size_t len) {
+ size_t b64_len = 0;
+ mbedtls_base64_encode(NULL, 0, &b64_len, raw, len);
+ unsigned char *b64 = malloc(b64_len + 1);
+ if (!b64) scr_trap("scriptc: out of memory\n");
+ mbedtls_base64_encode(b64, b64_len + 1, &b64_len, raw, len);
+ size_t o = 0;
+ for (size_t i = 0; i < b64_len; i++) {
+ unsigned char c = b64[i];
+ if (c == '=') break;
+ b64[o++] = c == '+' ? '-' : c == '/' ? '_' : c;
+ }
+ ScrDyn *d = sp_dyn_str(scr_str_new((const char *)b64, o));
+ mbedtls_platform_zeroize(b64, b64_len);
+ free(b64);
+ return d;
+}
+
+static ScrDyn *sp_export_jwk(SpKey *k) {
+ ScrDyn *jwk = scr_dyn_new_obj();
+ if (sp_is_ed(k)) {
+ sp_set(jwk, "kty", sp_dyn_str(scr_str_new("OKP", 3)));
+ sp_set(jwk, "crv", sp_dyn_str(scr_str_new("Ed25519", 7)));
+ sp_set(jwk, "x", sp_b64url(k->ed_pub, 32));
+ if (k->private_key) sp_set(jwk, "d", sp_b64url(k->ed_seed, 32));
+ return jwk;
+ }
+ if (sp_is_rsa(k)) {
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(k->pk);
+ mbedtls_mpi n, e, d, p, q, dp, dq, qi;
+ mbedtls_mpi *all[] = {&n, &e, &d, &p, &q, &dp, &dq, &qi};
+ for (size_t i = 0; i < 8; i++) mbedtls_mpi_init(all[i]);
+ mbedtls_rsa_export(rsa, &n, k->private_key ? &p : NULL, k->private_key ? &q : NULL,
+ k->private_key ? &d : NULL, &e);
+ sp_set(jwk, "kty", sp_dyn_str(scr_str_new("RSA", 3)));
+ sp_set(jwk, "n", sp_jwk_int(&n, 0));
+ sp_set(jwk, "e", sp_jwk_int(&e, 0));
+ if (k->private_key) {
+ mbedtls_rsa_export_crt(rsa, &dp, &dq, &qi);
+ sp_set(jwk, "d", sp_jwk_int(&d, 0));
+ sp_set(jwk, "p", sp_jwk_int(&p, 0));
+ sp_set(jwk, "q", sp_jwk_int(&q, 0));
+ sp_set(jwk, "dp", sp_jwk_int(&dp, 0));
+ sp_set(jwk, "dq", sp_jwk_int(&dq, 0));
+ sp_set(jwk, "qi", sp_jwk_int(&qi, 0));
+ }
+ for (size_t i = 0; i < 8; i++) mbedtls_mpi_free(all[i]);
+ } else {
+ const mbedtls_ecp_keypair *ec = mbedtls_pk_ec(k->pk);
+ const SpCurve *curve = sp_curve_of(k);
+ size_t size = (ec->MBEDTLS_PRIVATE(grp).nbits + 7) / 8;
+ sp_set(jwk, "kty", sp_dyn_str(scr_str_new("EC", 2)));
+ sp_set(jwk, "x", sp_jwk_int(&ec->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), size));
+ sp_set(jwk, "y", sp_jwk_int(&ec->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), size));
+ sp_set(jwk, "crv", sp_dyn_str(scr_str_new(curve->jwk, strlen(curve->jwk))));
+ if (k->private_key) sp_set(jwk, "d", sp_jwk_int(&ec->MBEDTLS_PRIVATE(d), size));
+ }
+ return jwk;
+}
+
+/* Decodes a JWK base64url member into `x`; false when it is missing or
+ * not a string. */
+static bool sp_jwk_read(const ScrDyn *jwk, const char *key, mbedtls_mpi *x) {
+ const ScrDyn *v = sp_opt(jwk, key);
+ if (v->kind != SCR_DYN_STR) return false;
+ size_t n = v->v.str->len;
+ unsigned char *b64 = malloc(n + 4);
+ unsigned char *raw = malloc(n + 4);
+ if (!b64 || !raw) scr_trap("scriptc: out of memory\n");
+ for (size_t i = 0; i < n; i++) {
+ char c = v->v.str->data[i];
+ b64[i] = (unsigned char)(c == '-' ? '+' : c == '_' ? '/' : c);
+ }
+ while (n % 4) b64[n++] = '=';
+ size_t raw_len = 0;
+ bool ok = mbedtls_base64_decode(raw, n, &raw_len, b64, n) == 0 &&
+ mbedtls_mpi_read_binary(x, raw, raw_len) == 0;
+ mbedtls_platform_zeroize(raw, n);
+ free(b64);
+ free(raw);
+ return ok;
+}
+
+/* Decodes a 32-byte JWK base64url member; false when it is missing, not a
+ * string, or another length. */
+static bool sp_jwk_bytes(const ScrDyn *jwk, const char *key, uint8_t out[32]) {
+ const ScrDyn *v = sp_opt(jwk, key);
+ if (v->kind != SCR_DYN_STR || v->v.str->len != 43) return false;
+ unsigned char b64[44];
+ for (size_t i = 0; i < 43; i++) {
+ char c = v->v.str->data[i];
+ b64[i] = (unsigned char)(c == '-' ? '+' : c == '_' ? '/' : c);
+ }
+ b64[43] = '=';
+ size_t n = 0;
+ return mbedtls_base64_decode(out, 32, &n, b64, 44) == 0 && n == 32;
+}
+
+static void sp_invalid_jwk(void) {
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_JWK", "Invalid JWK data");
+}
+
+/* A key from a JWK object: RSA (n, e, and d, p, q for a private key) or EC
+ * (crv, x, y, and d). NULL with the error pending. */
+static SpKey *sp_key_from_jwk_plain(const ScrDyn *jwk, bool want_private);
+
+static SpKey *sp_key_from_jwk(const ScrDyn *jwk, bool want_private) {
+ ScrDyn *plain = sp_plain(jwk);
+ if (!plain) return NULL;
+ SpKey *k = sp_key_from_jwk_plain(plain, want_private);
+ scr_dyn_release(plain);
+ return k;
+}
+
+static SpKey *sp_key_from_jwk_plain(const ScrDyn *jwk, bool want_private) {
+ if (!sp_is_object(jwk)) {
+ scr_dyn_prop_type_fail("key.key", "of type object", jwk);
+ return NULL;
+ }
+ const ScrDyn *kty = sp_opt(jwk, "kty");
+ if (kty->kind != SCR_DYN_STR) {
+ scr_dyn_prop_type_fail("key.kty", "of type string", kty);
+ return NULL;
+ }
+ bool has_d = !sp_absent(sp_opt(jwk, "d"));
+ if (want_private && !has_d) {
+ sp_invalid_jwk();
+ return NULL;
+ }
+ bool private_key = want_private;
+ SpKey *k = sp_key_alloc(private_key);
+ bool ok = false;
+ if (sp_is_str(kty, "OKP")) {
+ const ScrDyn *crv = sp_opt(jwk, "crv");
+ if (!sp_is_str(crv, "Ed25519")) {
+ sp_key_release(k);
+ if (crv->kind != SCR_DYN_STR) {
+ scr_dyn_prop_type_fail("key.crv", "of type string", crv);
+ return NULL;
+ }
+ sp_not_supported("JWK keys of this crv");
+ return NULL;
+ }
+ k->ed25519 = true;
+ if (private_key) {
+ ok = sp_jwk_bytes(jwk, "d", k->ed_seed);
+ if (ok) {
+ const ScrPkiEd25519 *ed = sp_ed();
+ if (!ed) {
+ sp_key_release(k);
+ return NULL;
+ }
+ ed->public_key(k->ed_pub, k->ed_seed);
+ }
+ } else {
+ ok = sp_jwk_bytes(jwk, "x", k->ed_pub);
+ }
+ } else if (sp_is_str(kty, "RSA")) {
+ mbedtls_mpi n, e, d, p, q;
+ mbedtls_mpi_init(&n);
+ mbedtls_mpi_init(&e);
+ mbedtls_mpi_init(&d);
+ mbedtls_mpi_init(&p);
+ mbedtls_mpi_init(&q);
+ if (sp_jwk_read(jwk, "n", &n) && sp_jwk_read(jwk, "e", &e) &&
+ mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) == 0) {
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(k->pk);
+ if (private_key) {
+ ok = sp_jwk_read(jwk, "d", &d) && sp_jwk_read(jwk, "p", &p) && sp_jwk_read(jwk, "q", &q) &&
+ mbedtls_rsa_import(rsa, &n, &p, &q, &d, &e) == 0 && mbedtls_rsa_complete(rsa) == 0 &&
+ mbedtls_rsa_check_privkey(rsa) == 0;
+ } else {
+ ok = mbedtls_rsa_import(rsa, &n, NULL, NULL, NULL, &e) == 0 &&
+ mbedtls_rsa_complete(rsa) == 0 && mbedtls_rsa_check_pubkey(rsa) == 0;
+ }
+ }
+ mbedtls_mpi_free(&n);
+ mbedtls_mpi_free(&e);
+ mbedtls_mpi_free(&d);
+ mbedtls_mpi_free(&p);
+ mbedtls_mpi_free(&q);
+ } else if (sp_is_str(kty, "EC")) {
+ const ScrDyn *crv = sp_opt(jwk, "crv");
+ if (crv->kind != SCR_DYN_STR) {
+ sp_key_release(k);
+ scr_dyn_prop_type_fail("key.crv", "of type string", crv);
+ return NULL;
+ }
+ const SpCurve *curve = NULL;
+ for (size_t i = 0; i < sizeof sp_curves / sizeof sp_curves[0]; i++)
+ if (sp_is_str(crv, sp_curves[i].jwk)) curve = &sp_curves[i];
+ if (!curve) {
+ sp_key_release(k);
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_CURVE", "Invalid EC curve name");
+ return NULL;
+ }
+ if (mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) == 0) {
+ mbedtls_ecp_keypair *ec = mbedtls_pk_ec(k->pk);
+ mbedtls_ecp_point *Q = &ec->MBEDTLS_PRIVATE(Q);
+ ok = mbedtls_ecp_group_load(&ec->MBEDTLS_PRIVATE(grp), curve->id) == 0 &&
+ sp_jwk_read(jwk, "x", &Q->MBEDTLS_PRIVATE(X)) &&
+ sp_jwk_read(jwk, "y", &Q->MBEDTLS_PRIVATE(Y)) &&
+ mbedtls_mpi_lset(&Q->MBEDTLS_PRIVATE(Z), 1) == 0 &&
+ mbedtls_ecp_check_pubkey(&ec->MBEDTLS_PRIVATE(grp), Q) == 0;
+ if (ok && private_key)
+ ok = sp_jwk_read(jwk, "d", &ec->MBEDTLS_PRIVATE(d)) &&
+ mbedtls_ecp_check_privkey(&ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(d)) == 0;
+ }
+ } else {
+ sp_key_release(k);
+ sp_not_supported("JWK keys of this kty");
+ return NULL;
+ }
+ if (!ok) {
+ sp_key_release(k);
+ sp_invalid_jwk();
+ return NULL;
+ }
+ return k;
+}
+
+/* ── parsing ──────────────────────────────────────────────────────────── */
+
+/* The private key of a public SpKey derived from `k`. */
+static SpKey *sp_public_of(SpKey *k) {
+ if (sp_is_ed(k)) {
+ SpKey *pub = sp_key_alloc(false);
+ pub->ed25519 = true;
+ memcpy(pub->ed_pub, k->ed_pub, 32);
+ return pub;
+ }
+ unsigned char der[SP_DER_MAX];
+ int n = sp_write_spki_der(k, der, sizeof der);
+ SpKey *pub = sp_key_alloc(false);
+ if (n < 0 || mbedtls_pk_parse_public_key(&pub->pk, der + sizeof der - n, (size_t)n) != 0) {
+ sp_key_release(pub);
+ return NULL;
+ }
+ return pub;
+}
+
+/* Parses key material (PEM or DER bytes). `private_key` selects
+ * createPrivateKey's parse (PKCS#1, SEC1, PKCS#8) or createPublicKey's
+ * (SubjectPublicKeyInfo, PKCS#1 RSAPublicKey, then a private key or an
+ * X.509 certificate, whose public key it takes). NULL with Node's
+ * decoder error pending. */
+/* The DER of the first PEM block in data (+1 malloc'd in *out), or the
+ * data itself when it has no PEM armor (*out NULL). */
+static bool sp_der_of(const unsigned char *data, size_t len, unsigned char **out, const unsigned char **der,
+ size_t *der_len) {
+ *out = NULL;
+ const unsigned char *begin = NULL;
+ for (size_t i = 0; i + 11 <= len; i++) {
+ if (memcmp(data + i, "-----BEGIN ", 11) == 0) {
+ begin = data + i;
+ break;
+ }
+ }
+ if (!begin) {
+ *der = data;
+ *der_len = len;
+ return true;
+ }
+ const unsigned char *end = data + len;
+ const unsigned char *body = memchr(begin, '\n', (size_t)(end - begin));
+ if (!body) return false;
+ body++;
+ unsigned char *b64 = malloc((size_t)(end - body) + 1);
+ unsigned char *raw = malloc((size_t)(end - body) + 1);
+ if (!b64 || !raw) scr_trap("scriptc: out of memory\n");
+ size_t n = 0;
+ for (const unsigned char *p = body; p < end && *p != '-'; p++)
+ if (*p != '\n' && *p != '\r' && *p != ' ' && *p != '\t') b64[n++] = *p;
+ size_t raw_len = 0;
+ bool ok = mbedtls_base64_decode(raw, (size_t)(end - body) + 1, &raw_len, b64, n) == 0;
+ free(b64);
+ if (!ok) {
+ free(raw);
+ return false;
+ }
+ *out = raw;
+ *der = raw;
+ *der_len = raw_len;
+ return true;
+}
+
+/* The SubjectPublicKeyInfo element of an X.509 certificate's DER. */
+static bool sp_cert_spki(const unsigned char *der, size_t len, const unsigned char **spki, size_t *spki_len) {
+ unsigned char *p = (unsigned char *)der;
+ const unsigned char *end = der + len;
+ size_t n;
+ if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false;
+ if (mbedtls_asn1_get_tag(&p, end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false;
+ const unsigned char *tbs_end = p + n;
+ /* [0] version, serialNumber, signature, issuer, validity, subject */
+ if (mbedtls_asn1_get_tag(&p, tbs_end, &n, MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | 0) == 0)
+ p += n;
+ int tags[] = {MBEDTLS_ASN1_INTEGER, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE,
+ MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE,
+ MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE};
+ for (size_t i = 0; i < 5; i++) {
+ if (mbedtls_asn1_get_tag(&p, tbs_end, &n, tags[i]) != 0) return false;
+ p += n;
+ }
+ const unsigned char *start = p;
+ if (mbedtls_asn1_get_tag(&p, tbs_end, &n, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) != 0) return false;
+ *spki = start;
+ *spki_len = (size_t)(p + n - start);
+ return true;
+}
+
+/* An Ed25519 key from PKCS#8 or SubjectPublicKeyInfo DER, or (for a public
+ * key) the key of an X.509 certificate; NULL when the DER is none of
+ * these, or with *failed set and an error pending. A private key wanted
+ * as public answers its public key. */
+static SpKey *sp_ed_from_der(const unsigned char *der, size_t len, bool private_key, bool *failed) {
+ *failed = false;
+ if (len == 48 && memcmp(der, sp_ed_pkcs8_prefix, 16) == 0) {
+ const ScrPkiEd25519 *ed = sp_ed();
+ if (!ed) {
+ *failed = true;
+ return NULL;
+ }
+ SpKey *k = sp_key_alloc(private_key);
+ k->ed25519 = true;
+ ed->public_key(k->ed_pub, der + 16);
+ if (private_key) memcpy(k->ed_seed, der + 16, 32);
+ return k;
+ }
+ if (private_key) return NULL;
+ const unsigned char *spki = der;
+ size_t spki_len = len;
+ if (!(len == 44 && memcmp(der, sp_ed_spki_prefix, 12) == 0) && !sp_cert_spki(der, len, &spki, &spki_len))
+ return NULL;
+ if (spki_len != 44 || memcmp(spki, sp_ed_spki_prefix, 12) != 0) return NULL;
+ SpKey *k = sp_key_alloc(false);
+ k->ed25519 = true;
+ memcpy(k->ed_pub, spki + 12, 32);
+ return k;
+}
+
+static SpKey *sp_parse(const unsigned char *data, size_t len, bool private_key) {
+ {
+ unsigned char *decoded;
+ const unsigned char *der;
+ size_t der_len;
+ if (sp_der_of(data, len, &decoded, &der, &der_len)) {
+ bool failed;
+ SpKey *ed = sp_ed_from_der(der, der_len, private_key, &failed);
+ if (decoded) {
+ mbedtls_platform_zeroize(decoded, der_len);
+ free(decoded);
+ }
+ if (ed || failed) return ed;
+ }
+ }
+ /* mbedtls reads PEM from a NUL-terminated buffer whose length counts the
+ * NUL. */
+ bool pem = false;
+ for (size_t i = 0; i + 11 <= len; i++) {
+ if (memcmp(data + i, "-----BEGIN ", 11) == 0) {
+ pem = true;
+ break;
+ }
+ }
+ unsigned char *buf = malloc(len + 1);
+ if (!buf) scr_trap("scriptc: out of memory\n");
+ memcpy(buf, data, len);
+ buf[len] = 0;
+ size_t n = pem ? len + 1 : len;
+ SpKey *k = sp_key_alloc(private_key);
+ bool ok;
+ if (private_key) {
+ ok = mbedtls_pk_parse_key(&k->pk, buf, n, NULL, 0, sp_random, NULL) == 0;
+ } else {
+ ok = mbedtls_pk_parse_public_key(&k->pk, buf, n) == 0;
+ if (!ok) {
+ SpKey *priv = sp_key_alloc(true);
+ mbedtls_pk_free(&k->pk);
+ mbedtls_pk_init(&k->pk);
+ if (mbedtls_pk_parse_key(&priv->pk, buf, n, NULL, 0, sp_random, NULL) == 0) {
+ sp_key_release(k);
+ k = sp_public_of(priv);
+ ok = k != NULL;
+ if (!k) k = sp_key_alloc(false);
+ } else {
+ mbedtls_x509_crt crt;
+ mbedtls_x509_crt_init(&crt);
+ if (mbedtls_x509_crt_parse(&crt, buf, n) == 0) {
+ unsigned char der[SP_DER_MAX];
+ int w = mbedtls_pk_write_pubkey_der(&crt.pk, der, sizeof der);
+ ok = w > 0 && mbedtls_pk_parse_public_key(&k->pk, der + sizeof der - w, (size_t)w) == 0;
+ }
+ mbedtls_x509_crt_free(&crt);
+ }
+ sp_key_release(priv);
+ }
+ }
+ mbedtls_platform_zeroize(buf, len);
+ free(buf);
+ if (!ok) {
+ sp_key_release(k);
+ sp_unsupported_input();
+ return NULL;
+ }
+ if (!sp_key_supported(k)) {
+ sp_key_release(k);
+ sp_not_supported("crypto keys of this type");
+ return NULL;
+ }
+ return k;
+}
+
+/* The bytes of a string or buffer source, +1; NULL when `v` is neither. */
+static ScrBytes *sp_source_bytes(const ScrDyn *v, const ScrDyn *encoding) {
+ if (v->kind == SCR_DYN_STR) {
+ bool named = encoding != NULL && encoding->kind == SCR_DYN_STR;
+ ScrStr *utf8 = named ? NULL : scr_str_new("utf8", 4);
+ ScrBytes *b = scr_bytes_from_str_checked(v->v.str, named ? encoding->v.str : utf8);
+ if (utf8) scr_str_release(utf8);
+ return b;
+ }
+ if (v->kind == SCR_DYN_BYTES || scr_buffer_storage_is(v)) return scr_bytes_buffer_source(v);
+ return NULL;
+}
+
+static const char sp_key_input_types[] =
+ "of type string or an instance of ArrayBuffer, Buffer, TypedArray, DataView, or URL";
+
+/* createPrivateKey / createPublicKey: a PEM string or buffer, or
+ * { key, format, type, encoding, passphrase }; createPublicKey also takes
+ * a private KeyObject. */
+static ScrDyn *sp_create_key(ScrDyn *input, bool private_key) {
+ if (input->kind == SCR_DYN_TYPED_REF) {
+ ScrDyn *value = scr_dyn_typed_ref_materialize(input);
+ if (!value) return NULL;
+ ScrDyn *result = sp_create_key(value, private_key);
+ scr_dyn_release(value);
+ return result;
+ }
+ SpKey *object = sp_key_of(input);
+ if (object) {
+ if (private_key) {
+ scr_dyn_arg_type_fail("key", sp_key_input_types, input);
+ return NULL;
+ }
+ if (!object->private_key) {
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE",
+ "Invalid key object type public, expected private.");
+ return NULL;
+ }
+ SpKey *pub = sp_public_of(object);
+ if (!pub) {
+ sp_unsupported_input();
+ return NULL;
+ }
+ return sp_key_box(pub);
+ }
+ const ScrDyn *data = input;
+ const ScrDyn *format = scr_dyn_undefined();
+ const ScrDyn *type = scr_dyn_undefined();
+ const ScrDyn *encoding = NULL;
+ if (sp_is_object(input) && !scr_buffer_storage_is(input)) {
+ data = sp_opt(input, "key");
+ format = sp_opt(input, "format");
+ type = sp_opt(input, "type");
+ encoding = sp_opt(input, "encoding");
+ if (!sp_absent(sp_opt(input, "passphrase"))) {
+ sp_not_supported("encrypted private keys");
+ return NULL;
+ }
+ SpKey *inner = sp_key_of(data);
+ if (inner) return sp_create_key((ScrDyn *)data, private_key);
+ if (sp_is_str(format, "jwk")) {
+ SpKey *k = sp_key_from_jwk(data, private_key);
+ return k ? sp_key_box(k) : NULL;
+ }
+ if (!sp_absent(format) && !sp_is_str(format, "pem") && !sp_is_str(format, "der")) {
+ sp_property_invalid("key.format", format);
+ return NULL;
+ }
+ if (sp_is_str(format, "der")) {
+ bool valid = private_key ? sp_is_str(type, "pkcs1") || sp_is_str(type, "pkcs8") ||
+ sp_is_str(type, "sec1")
+ : sp_is_str(type, "pkcs1") || sp_is_str(type, "spki");
+ if (!valid) {
+ sp_property_invalid("key.type", type);
+ return NULL;
+ }
+ }
+ }
+ ScrBytes *bytes = sp_source_bytes(data, encoding);
+ if (!bytes) {
+ if (data->kind == SCR_DYN_STR || data->kind == SCR_DYN_BYTES || scr_buffer_storage_is(data))
+ return NULL; /* the decoder threw */
+ scr_dyn_arg_type_fail(data == input ? "key" : "key.key", sp_key_input_types, data);
+ return NULL;
+ }
+ SpKey *k = sp_parse(bytes->data, (size_t)scr_bytes_byte_len(bytes), private_key);
+ scr_bytes_release(bytes);
+ return k ? sp_key_box(k) : NULL;
+}
+
+ScrDyn *scr_crypto_create_private_key(ScrDyn *key) { return sp_create_key(key, true); }
+
+ScrDyn *scr_crypto_create_public_key(ScrDyn *key) { return sp_create_key(key, false); }
+
+/* ── export ───────────────────────────────────────────────────────────── */
+
+/* export({ format, type }) per Node's parseKeyEncoding: format "pem",
+ * "der" or "jwk"; type pkcs1/pkcs8/sec1 for private keys and pkcs1/spki
+ * for public keys, where pkcs1 needs an RSA key and sec1 an EC key. */
+static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options);
+
+static ScrDyn *sp_export(SpKey *k, const ScrDyn *options) {
+ ScrDyn *plain = sp_plain(options);
+ if (!plain) return NULL;
+ ScrDyn *result = sp_export_plain(k, plain);
+ scr_dyn_release(plain);
+ return result;
+}
+
+static ScrDyn *sp_export_plain(SpKey *k, const ScrDyn *options) {
+ if (options == NULL || !sp_is_object(options)) {
+ scr_dyn_arg_type_fail("options", "of type object", options ? options : scr_dyn_undefined());
+ return NULL;
+ }
+ const ScrDyn *format = sp_opt(options, "format");
+ if (sp_is_str(format, "jwk")) return sp_export_jwk(k);
+ bool pem = sp_is_str(format, "pem");
+ if (!pem && !sp_is_str(format, "der")) {
+ sp_property_invalid("options.format", format);
+ return NULL;
+ }
+ const ScrDyn *type = sp_opt(options, "type");
+ bool pkcs1 = sp_is_str(type, "pkcs1");
+ bool pkcs8 = k->private_key && sp_is_str(type, "pkcs8");
+ bool sec1 = k->private_key && sp_is_str(type, "sec1");
+ bool spki = !k->private_key && sp_is_str(type, "spki");
+ if (!pkcs1 && !pkcs8 && !sec1 && !spki) {
+ sp_property_invalid("options.type", type);
+ return NULL;
+ }
+ if (!sp_absent(sp_opt(options, "cipher")) || !sp_absent(sp_opt(options, "passphrase"))) {
+ sp_not_supported("encrypted private key export");
+ return NULL;
+ }
+ if (pkcs1 && !sp_is_rsa(k)) {
+ sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS",
+ "The selected key encoding pkcs1 can only be used for RSA keys.");
+ return NULL;
+ }
+ if (sec1 && !sp_is_ec(k)) {
+ sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS",
+ "The selected key encoding sec1 can only be used for EC keys.");
+ return NULL;
+ }
+ unsigned char *der = malloc(SP_DER_MAX);
+ if (!der) scr_trap("scriptc: out of memory\n");
+ int n;
+ const char *label;
+ if (pkcs8) {
+ n = sp_write_pkcs8_der(k, der, SP_DER_MAX);
+ label = "PRIVATE KEY";
+ } else if (spki) {
+ n = sp_write_spki_der(k, der, SP_DER_MAX);
+ label = "PUBLIC KEY";
+ } else if (k->private_key) {
+ n = sp_write_private_der(k, der, SP_DER_MAX);
+ label = pkcs1 ? "RSA PRIVATE KEY" : "EC PRIVATE KEY";
+ } else {
+ n = sp_write_pkcs1_public_der(k, der, SP_DER_MAX);
+ label = "RSA PUBLIC KEY";
+ }
+ ScrDyn *result = NULL;
+ if (n < 0) {
+ sp_unsupported_input();
+ } else {
+ const unsigned char *start = der + SP_DER_MAX - n;
+ result = pem ? sp_dyn_str(sp_pem(label, start, (size_t)n)) : sp_dyn_buffer(start, (size_t)n);
+ }
+ mbedtls_platform_zeroize(der, SP_DER_MAX);
+ free(der);
+ return result;
+}
+
+/* ── the KeyObject handles ────────────────────────────────────────────── */
+
+static ScrDyn *sp_key_details(SpKey *k) {
+ ScrDyn *details = scr_dyn_new_obj();
+ if (sp_is_ed(k)) return details;
+ if (sp_is_rsa(k)) {
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(k->pk);
+ mbedtls_mpi e;
+ mbedtls_mpi_init(&e);
+ mbedtls_rsa_export(rsa, NULL, NULL, NULL, NULL, &e);
+ uint64_t exponent = 0;
+ size_t bytes = mbedtls_mpi_size(&e);
+ unsigned char raw[8] = {0};
+ if (bytes <= 8) mbedtls_mpi_write_binary(&e, raw, 8);
+ for (size_t i = 0; i < 8; i++) exponent = (exponent << 8) | raw[i];
+ mbedtls_mpi_free(&e);
+ sp_set(details, "modulusLength", scr_dyn_new_num((double)mbedtls_pk_get_bitlen(&k->pk)));
+ ScrBigInt *big = scr_bigint_from_u64(exponent);
+ sp_set(details, "publicExponent", scr_dyn_new_bigint(big));
+ scr_bigint_release(big);
+ } else {
+ const SpCurve *curve = sp_curve_of(k);
+ sp_set(details, "namedCurve", sp_dyn_str(scr_str_new(curve->name, strlen(curve->name))));
+ }
+ return details;
+}
+
+/* The DER that identifies a key for equals(): its private encoding for a
+ * private key, its SubjectPublicKeyInfo otherwise. */
+static int sp_identity_der(SpKey *k, unsigned char *buf, size_t size) {
+ if (sp_is_ed(k)) return k->private_key ? sp_write_pkcs8_der(k, buf, size) : sp_write_spki_der(k, buf, size);
+ return k->private_key ? sp_write_private_der(k, buf, size) : sp_write_spki_der(k, buf, size);
+}
+
+static ScrDyn *sp_key_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args,
+ size_t argc, const char *what) {
+ (void)self;
+ SpKey *k = h;
+ if (strcmp(method, "export") == 0) return sp_export(k, argc ? args[0] : NULL);
+ if (strcmp(method, "equals") == 0) {
+ const ScrDyn *other_value = argc ? args[0] : scr_dyn_undefined();
+ SpKey *other = sp_key_of(other_value);
+ if (!other) {
+ size_t len;
+ if (scr_key_object_bytes(other_value, &len)) return scr_dyn_new_bool(false);
+ scr_dyn_arg_type_fail("otherKeyObject", "an instance of KeyObject", other_value);
+ return NULL;
+ }
+ if (other->private_key != k->private_key ||
+ mbedtls_pk_get_type(&other->pk) != mbedtls_pk_get_type(&k->pk))
+ return scr_dyn_new_bool(false);
+ unsigned char *a = malloc(SP_DER_MAX), *b = malloc(SP_DER_MAX);
+ if (!a || !b) scr_trap("scriptc: out of memory\n");
+ int na = sp_identity_der(k, a, SP_DER_MAX);
+ int nb = sp_identity_der(other, b, SP_DER_MAX);
+ bool same = na > 0 && na == nb && memcmp(a + SP_DER_MAX - na, b + SP_DER_MAX - nb, (size_t)na) == 0;
+ mbedtls_platform_zeroize(a, SP_DER_MAX);
+ mbedtls_platform_zeroize(b, SP_DER_MAX);
+ free(a);
+ free(b);
+ return scr_dyn_new_bool(same);
+ }
+ if (strcmp(method, "toCryptoKey") == 0) {
+ sp_not_supported("KeyObject.toCryptoKey");
+ return NULL;
+ }
+ scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what);
+ return NULL;
+}
+
+static ScrDyn *sp_key_get(void *h, const char *name, size_t len) {
+ SpKey *k = h;
+ if (len == 4 && memcmp(name, "type", 4) == 0)
+ return k->private_key ? sp_dyn_str(scr_str_new("private", 7)) : sp_dyn_str(scr_str_new("public", 6));
+ if (len == 17 && memcmp(name, "asymmetricKeyType", 17) == 0)
+ return sp_is_ed(k) ? sp_dyn_str(scr_str_new("ed25519", 7))
+ : sp_is_rsa(k) ? sp_dyn_str(scr_str_new("rsa", 3))
+ : sp_dyn_str(scr_str_new("ec", 2));
+ if (len == 20 && memcmp(name, "asymmetricKeyDetails", 20) == 0) return sp_key_details(k);
+ return NULL;
+}
+
+static bool sp_key_set(void *h, const char *name, size_t len, const ScrDyn *value) {
+ (void)h;
+ (void)value;
+ const char *holder = len == 4 && memcmp(name, "type", 4) == 0 ? "KeyObject"
+ : (len == 17 && memcmp(name, "asymmetricKeyType", 17) == 0) ||
+ (len == 20 && memcmp(name, "asymmetricKeyDetails", 20) == 0)
+ ? "AsymmetricKeyObject"
+ : NULL;
+ if (!holder) return false;
+ scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "Cannot set property %.*s of #<%s> which has only a getter", (int)len, name,
+ holder);
+ return true;
+}
+
+static ScrDyn *sp_key_inspect_view(void *h) {
+ (void)h;
+ return scr_dyn_new_obj();
+}
+
+static const ScrDynHandleOps sp_private_ops = {
+ .cls = "PrivateKeyObject",
+ .retain = sp_key_retain,
+ .release = sp_key_release,
+ .invoke = sp_key_invoke,
+ .get = sp_key_get,
+ .set = sp_key_set,
+ .inspect_view = sp_key_inspect_view,
+ .json_empty_object = true,
+ .to_string_tag = "KeyObject",
+ .inspect_view_empty = true,
+};
+
+static const ScrDynHandleOps sp_public_ops = {
+ .cls = "PublicKeyObject",
+ .retain = sp_key_retain,
+ .release = sp_key_release,
+ .invoke = sp_key_invoke,
+ .get = sp_key_get,
+ .set = sp_key_set,
+ .inspect_view = sp_key_inspect_view,
+ .json_empty_object = true,
+ .to_string_tag = "KeyObject",
+ .inspect_view_empty = true,
+};
+
+/* createHmac with a private or public key (scr_crypto_reject_asymmetric_secret). */
+static void sp_reject_as_secret(const ScrDyn *key) {
+ bool private_key = key->v.handle.tag == SCR_DYNH_PRIVATE_KEY;
+ scr_throw_error_msg_code(SCR_ERR_TYPE,
+ private_key ? "Invalid key object type private, expected secret."
+ : "Invalid key object type public, expected secret.",
+ private_key ? 49 : 48, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE");
+}
+
+static void sp_key_install(void) {
+ static SCR_TL bool installed;
+ if (installed) return;
+ installed = true;
+ scr_dyn_handle_install(SCR_DYNH_PRIVATE_KEY, &sp_private_ops);
+ scr_dyn_handle_install(SCR_DYNH_PUBLIC_KEY, &sp_public_ops);
+ scr_crypto_reject_asymmetric_secret = sp_reject_as_secret;
+}
+
+/* ── generateKeyPairSync ──────────────────────────────────────────────── */
+
+/* A key in the encoding an options.publicKeyEncoding/privateKeyEncoding
+ * object asks for, or the KeyObject when it is undefined. Consumes `k`. */
+static ScrDyn *sp_encoded(SpKey *k, const ScrDyn *encoding) {
+ if (sp_absent(encoding)) return sp_key_box(k);
+ ScrDyn *result = sp_export(k, encoding);
+ sp_key_release(k);
+ return result;
+}
+
+static bool sp_check_encoding_plain(const ScrDyn *encoding, const char *name, bool private_key);
+
+static bool sp_check_encoding(const ScrDyn *options, const char *name, bool private_key) {
+ ScrDyn *encoding = sp_plain(sp_opt(options, name));
+ if (!encoding) return false;
+ bool ok = sp_check_encoding_plain(encoding, name, private_key);
+ scr_dyn_release(encoding);
+ return ok;
+}
+
+static bool sp_check_encoding_plain(const ScrDyn *encoding, const char *name, bool private_key) {
+ if (sp_absent(encoding)) return true;
+ char full[64];
+ snprintf(full, sizeof full, "options.%s", name);
+ if (!sp_is_object(encoding)) {
+ sp_property_invalid(full, encoding);
+ return false;
+ }
+ const ScrDyn *format = sp_opt(encoding, "format");
+ if (!sp_is_str(format, "pem") && !sp_is_str(format, "der") && !sp_is_str(format, "jwk")) {
+ snprintf(full, sizeof full, "options.%s.format", name);
+ sp_property_invalid(full, format);
+ return false;
+ }
+ if (sp_is_str(format, "jwk")) return true;
+ const ScrDyn *type = sp_opt(encoding, "type");
+ bool valid = private_key ? sp_is_str(type, "pkcs1") || sp_is_str(type, "pkcs8") || sp_is_str(type, "sec1")
+ : sp_is_str(type, "pkcs1") || sp_is_str(type, "spki");
+ if (!valid) {
+ snprintf(full, sizeof full, "options.%s.type", name);
+ sp_property_invalid(full, type);
+ return false;
+ }
+ return true;
+}
+
+/* Generates the private key for (type, options), validated in Node's
+ * order; NULL with the error pending. */
+static SpKey *sp_generate(const ScrDyn *type, const ScrDyn *options) {
+ if (type->kind != SCR_DYN_STR) {
+ scr_dyn_arg_type_fail("type", "of type string", type);
+ return NULL;
+ }
+ bool rsa = sp_is_str(type, "rsa");
+ bool ec = sp_is_str(type, "ec");
+ bool ed = sp_is_str(type, "ed25519");
+ static const char *const known[] = {"rsa-pss", "dsa", "ed448", "x25519", "x448", "dh"};
+ bool other = false;
+ for (size_t i = 0; i < sizeof known / sizeof known[0]; i++) other = other || sp_is_str(type, known[i]);
+ if (!rsa && !ec && !ed && !other) {
+ ScrStr *received = sp_received(type, false);
+ scr_pki_throw_fmt(SCR_ERR_TYPE, "ERR_INVALID_ARG_VALUE",
+ "The argument 'type' must be a supported key type. Received %.*s", (int)received->len,
+ received->data);
+ scr_str_release(received);
+ return NULL;
+ }
+ /* The ed25519 options are optional. */
+ if (!sp_is_object(options) && !(ed && sp_absent(options))) {
+ scr_dyn_arg_type_fail("options", "of type object", options);
+ return NULL;
+ }
+ if (other) {
+ sp_not_supported("generateKeyPairSync for this key type");
+ return NULL;
+ }
+ SpKey *k = sp_key_alloc(true);
+ if (ed) {
+ if (!sp_check_encoding(options, "publicKeyEncoding", false) ||
+ !sp_check_encoding(options, "privateKeyEncoding", true)) {
+ sp_key_release(k);
+ return NULL;
+ }
+ const ScrPkiEd25519 *ed = sp_ed();
+ if (!ed) {
+ sp_key_release(k);
+ return NULL;
+ }
+ k->ed25519 = true;
+ sp_random(NULL, k->ed_seed, 32);
+ ed->public_key(k->ed_pub, k->ed_seed);
+ return k;
+ }
+ if (rsa) {
+ const ScrDyn *bits = sp_opt(options, "modulusLength");
+ if (bits->kind != SCR_DYN_NUM) {
+ sp_key_release(k);
+ scr_dyn_prop_type_fail("options.modulusLength", "of type number", bits);
+ return NULL;
+ }
+ double b = bits->v.num;
+ /* validateUint32: an integer first, then the range. */
+ bool integer = isfinite(b) && trunc(b) == b;
+ if (!integer || b < 0 || b > 4294967295.0) {
+ sp_key_release(k);
+ ScrStr *received = sp_received(bits, true);
+ scr_pki_throw_fmt(SCR_ERR_RANGE, "ERR_OUT_OF_RANGE",
+ "The value of \"options.modulusLength\" is out of range. It must be %s. Received %.*s",
+ integer ? ">= 0 && <= 4294967295" : "an integer", (int)received->len, received->data);
+ scr_str_release(received);
+ return NULL;
+ }
+ const ScrDyn *exp = sp_opt(options, "publicExponent");
+ double e = 65537;
+ if (!sp_absent(exp)) {
+ if (exp->kind != SCR_DYN_NUM) {
+ sp_key_release(k);
+ scr_dyn_prop_type_fail("options.publicExponent", "of type number", exp);
+ return NULL;
+ }
+ e = exp->v.num;
+ }
+ if (!sp_check_encoding(options, "publicKeyEncoding", false) ||
+ !sp_check_encoding(options, "privateKeyEncoding", true)) {
+ sp_key_release(k);
+ return NULL;
+ }
+ if (b < 512 || b > 16384 || e < 3 || e > 2147483647.0 || e != (double)(int64_t)e || ((int64_t)e & 1) == 0 ||
+ mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 ||
+ mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, (unsigned)b, (int)e) != 0) {
+ sp_key_release(k);
+ sp_not_supported("generateKeyPairSync with this RSA modulus length or exponent");
+ return NULL;
+ }
+ return k;
+ }
+ const ScrDyn *curve_name = sp_opt(options, "namedCurve");
+ if (curve_name->kind != SCR_DYN_STR) {
+ sp_key_release(k);
+ scr_dyn_prop_type_fail("options.namedCurve", "of type string", curve_name);
+ return NULL;
+ }
+ if (!sp_check_encoding(options, "publicKeyEncoding", false) ||
+ !sp_check_encoding(options, "privateKeyEncoding", true)) {
+ sp_key_release(k);
+ return NULL;
+ }
+ const SpCurve *curve = NULL;
+ for (size_t i = 0; i < sizeof sp_curves / sizeof sp_curves[0]; i++)
+ if (sp_is_str(curve_name, sp_curves[i].name) ||
+ (sp_curves[i].nist && sp_is_str(curve_name, sp_curves[i].nist)))
+ curve = &sp_curves[i];
+ if (!curve) {
+ sp_key_release(k);
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_CURVE", "Invalid EC curve name");
+ return NULL;
+ }
+ if (mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) != 0 ||
+ mbedtls_ecp_gen_key(curve->id, mbedtls_pk_ec(k->pk), sp_random, NULL) != 0) {
+ sp_key_release(k);
+ sp_unsupported_input();
+ return NULL;
+ }
+ return k;
+}
+
+/* generateKeyPairSync(type, options): { publicKey, privateKey }, each a
+ * KeyObject or the encoding the options ask for. */
+ScrDyn *scr_crypto_generate_key_pair_sync(ScrDyn *type, ScrDyn *options) {
+ if (options->kind == SCR_DYN_TYPED_REF) {
+ ScrDyn *value = scr_dyn_typed_ref_materialize(options);
+ if (!value) return NULL;
+ ScrDyn *result = scr_crypto_generate_key_pair_sync(type, value);
+ scr_dyn_release(value);
+ return result;
+ }
+ SpKey *k = sp_generate(type, options);
+ if (!k) return NULL;
+ SpKey *pub = sp_public_of(k);
+ if (!pub) {
+ sp_key_release(k);
+ sp_unsupported_input();
+ return NULL;
+ }
+ ScrDyn *public_value = sp_encoded(pub, sp_opt(options, "publicKeyEncoding"));
+ if (!public_value) {
+ sp_key_release(k);
+ return NULL;
+ }
+ ScrDyn *private_value = sp_encoded(k, sp_opt(options, "privateKeyEncoding"));
+ if (!private_value) {
+ scr_dyn_release(public_value);
+ return NULL;
+ }
+ ScrDyn *pair = scr_dyn_new_obj();
+ sp_set(pair, "publicKey", public_value);
+ sp_set(pair, "privateKey", private_value);
+ return pair;
+}
+
+/* ── sign and verify ──────────────────────────────────────────────────── */
+
+typedef struct {
+ const char *name;
+ mbedtls_md_type_t md;
+} SpDigest;
+
+/* Digest names as OpenSSL's EVP_get_digestbyname takes them for
+ * signatures, matched without regard to case. */
+static const SpDigest sp_digests[] = {
+ {"sha1", MBEDTLS_MD_SHA1}, {"rsa-sha1", MBEDTLS_MD_SHA1},
+ {"sha1withrsaencryption", MBEDTLS_MD_SHA1}, {"ecdsa-with-sha1", MBEDTLS_MD_SHA1},
+ {"sha224", MBEDTLS_MD_SHA224}, {"rsa-sha224", MBEDTLS_MD_SHA224},
+ {"sha224withrsaencryption", MBEDTLS_MD_SHA224},
+ {"sha256", MBEDTLS_MD_SHA256}, {"rsa-sha256", MBEDTLS_MD_SHA256},
+ {"sha256withrsaencryption", MBEDTLS_MD_SHA256},
+ {"sha384", MBEDTLS_MD_SHA384}, {"rsa-sha384", MBEDTLS_MD_SHA384},
+ {"sha384withrsaencryption", MBEDTLS_MD_SHA384},
+ {"sha512", MBEDTLS_MD_SHA512}, {"rsa-sha512", MBEDTLS_MD_SHA512},
+ {"sha512withrsaencryption", MBEDTLS_MD_SHA512},
+ {"sha2-256", MBEDTLS_MD_SHA256}, {"sha2-384", MBEDTLS_MD_SHA384},
+ {"sha2-512", MBEDTLS_MD_SHA512}, {"sha2-224", MBEDTLS_MD_SHA224},
+};
+
+static bool sp_digest_of(const ScrStr *name, mbedtls_md_type_t *md) {
+ for (size_t i = 0; i < sizeof sp_digests / sizeof sp_digests[0]; i++) {
+ size_t n = strlen(sp_digests[i].name);
+ if (name->len != n) continue;
+ bool same = true;
+ for (size_t j = 0; j < n && same; j++) {
+ char c = name->data[j];
+ if (c >= 'A' && c <= 'Z') c = (char)(c - 'A' + 'a');
+ same = c == sp_digests[i].name[j];
+ }
+ if (same) {
+ *md = sp_digests[i].md;
+ return true;
+ }
+ }
+ return false;
+}
+
+/* The algorithm argument of crypto.sign/verify: a digest name, or
+ * null/undefined for the key type's default (SHA-256). */
+static bool sp_algorithm(const ScrDyn *alg, mbedtls_md_type_t *md) {
+ if (alg->kind == SCR_DYN_UNDEF || alg->kind == SCR_DYN_NULL) {
+ *md = MBEDTLS_MD_SHA256;
+ return true;
+ }
+ if (alg->kind != SCR_DYN_STR) {
+ scr_dyn_arg_type_fail("algorithm", "of type string", alg);
+ return false;
+ }
+ if (!sp_digest_of(alg->v.str, md)) {
+ /* The whole name, at any length and with any bytes, as in Node. */
+ static const char prefix[] = "Invalid digest: ";
+ size_t len = sizeof prefix - 1 + alg->v.str->len;
+ char *message = malloc(len);
+ if (!message) scr_trap("scriptc: out of memory\n");
+ memcpy(message, prefix, sizeof prefix - 1);
+ memcpy(message + sizeof prefix - 1, alg->v.str->data, alg->v.str->len);
+ scr_throw_error_msg_code(SCR_ERR_TYPE, message, len, "ERR_CRYPTO_INVALID_DIGEST");
+ free(message);
+ return false;
+ }
+ return true;
+}
+
+typedef struct {
+ SpKey *key; /* +1 */
+ int padding; /* RSA_PKCS1_PADDING (1) or RSA_PKCS1_PSS_PADDING (6) */
+ int salt; /* -1 digest length, -2 maximum (sign) or auto (verify) */
+ bool p1363; /* dsaEncoding: "ieee-p1363" */
+} SpSigKey;
+
+/* The key argument of a signing operation: a KeyObject, key material, or
+ * { key, dsaEncoding, padding, saltLength, ...createKey options }. */
+static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out);
+
+static bool sp_sig_key(const ScrDyn *value, bool sign, SpSigKey *out) {
+ out->key = NULL;
+ ScrDyn *plain = sp_plain(value);
+ if (!plain) return false;
+ bool ok = sp_sig_key_plain(plain, sign, out);
+ scr_dyn_release(plain);
+ return ok;
+}
+
+static bool sp_sig_key_plain(const ScrDyn *value, bool sign, SpSigKey *out) {
+ out->key = NULL;
+ out->padding = 1;
+ out->salt = sign ? -2 : -2;
+ out->p1363 = false;
+ const ScrDyn *options = NULL;
+ if (sp_is_object(value) && !sp_key_of(value) && !scr_buffer_storage_is(value)) {
+ options = value;
+ const ScrDyn *dsa = sp_opt(value, "dsaEncoding");
+ if (sp_is_str(dsa, "ieee-p1363")) out->p1363 = true;
+ else if (!sp_absent(dsa) && !sp_is_str(dsa, "der")) {
+ sp_property_invalid("options.dsaEncoding", dsa);
+ return false;
+ }
+ const ScrDyn *padding = sp_opt(value, "padding");
+ if (!sp_absent(padding)) {
+ if (padding->kind != SCR_DYN_NUM || (padding->v.num != 1 && padding->v.num != 6)) {
+ sp_not_supported("signatures with this padding");
+ return false;
+ }
+ out->padding = (int)padding->v.num;
+ }
+ const ScrDyn *salt = sp_opt(value, "saltLength");
+ if (!sp_absent(salt)) {
+ if (salt->kind != SCR_DYN_NUM || salt->v.num != (double)(int)salt->v.num || salt->v.num < -2) {
+ sp_not_supported("signatures with this saltLength");
+ return false;
+ }
+ out->salt = (int)salt->v.num;
+ }
+ }
+ SpKey *direct = sp_key_of(options ? sp_opt(options, "key") : value);
+ if (direct) {
+ if (sign && !direct->private_key) {
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE",
+ "Invalid key object type public, expected private.");
+ return false;
+ }
+ sp_key_retain(direct);
+ out->key = direct;
+ return true;
+ }
+ ScrDyn *created = sp_create_key((ScrDyn *)value, sign);
+ if (!created) return false;
+ out->key = sp_key_retain(sp_key_of(created));
+ scr_dyn_release(created);
+ return true;
+}
+
+/* DER ECDSA-Sig-Value ⇄ IEEE P1363 r || s. */
+static bool sp_der_to_p1363(const unsigned char *der, size_t len, size_t size, unsigned char *out) {
+ unsigned char *p = (unsigned char *)der;
+ const unsigned char *end = der + len;
+ size_t seq;
+ mbedtls_mpi r, s;
+ mbedtls_mpi_init(&r);
+ mbedtls_mpi_init(&s);
+ bool ok = mbedtls_asn1_get_tag(&p, end, &seq, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE) == 0 &&
+ mbedtls_asn1_get_mpi(&p, end, &r) == 0 && mbedtls_asn1_get_mpi(&p, end, &s) == 0 &&
+ mbedtls_mpi_write_binary(&r, out, size) == 0 &&
+ mbedtls_mpi_write_binary(&s, out + size, size) == 0;
+ mbedtls_mpi_free(&r);
+ mbedtls_mpi_free(&s);
+ return ok;
+}
+
+static int sp_p1363_to_der(const unsigned char *sig, size_t len, unsigned char *buf, size_t size) {
+ mbedtls_mpi r, s;
+ mbedtls_mpi_init(&r);
+ mbedtls_mpi_init(&s);
+ int out = -1;
+ if (len % 2 == 0 && mbedtls_mpi_read_binary(&r, sig, len / 2) == 0 &&
+ mbedtls_mpi_read_binary(&s, sig + len / 2, len / 2) == 0) {
+ unsigned char *c = buf + size;
+ size_t n = 0;
+ int w;
+ if ((w = mbedtls_asn1_write_mpi(&c, buf, &s)) >= 0) {
+ n += (size_t)w;
+ if ((w = mbedtls_asn1_write_mpi(&c, buf, &r)) >= 0) {
+ n += (size_t)w;
+ if ((w = mbedtls_asn1_write_len(&c, buf, n)) >= 0) {
+ n += (size_t)w;
+ if ((w = mbedtls_asn1_write_tag(&c, buf, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE)) >= 0)
+ out = (int)(n + (size_t)w);
+ }
+ }
+ }
+ }
+ mbedtls_mpi_free(&r);
+ mbedtls_mpi_free(&s);
+ return out;
+}
+
+static size_t sp_ec_size(SpKey *k) { return (mbedtls_pk_ec(k->pk)->MBEDTLS_PRIVATE(grp).nbits + 7) / 8; }
+
+/* Applies the RSA padding of a signature operation. */
+static bool sp_rsa_padding(SpSigKey *key, mbedtls_md_type_t md) {
+ if (!sp_is_rsa(key->key)) return true;
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key->key->pk);
+ if (key->padding == 6) return mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V21, md) == 0;
+ return mbedtls_rsa_set_padding(rsa, MBEDTLS_RSA_PKCS_V15, MBEDTLS_MD_NONE) == 0;
+}
+
+/* Signs a digest: +1 Buffer or NULL with the error pending. */
+static ScrDyn *sp_sign_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len) {
+ unsigned char sig[MBEDTLS_PK_SIGNATURE_MAX_SIZE];
+ size_t sig_len = 0;
+ int r;
+ if (!sp_rsa_padding(key, md)) {
+ sp_unsupported_input();
+ return NULL;
+ }
+ if (sp_is_rsa(key->key) && key->padding == 6) {
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key->key->pk);
+ int salt = key->salt;
+ size_t bytes = mbedtls_rsa_get_len(rsa);
+ size_t bits = mbedtls_pk_get_bitlen(&key->key->pk);
+ size_t max = bytes - hash_len - 2 - ((bits - 1) % 8 == 0 ? 1 : 0);
+ if (salt == -1) salt = (int)hash_len;
+ else if (salt == -2) salt = (int)max;
+ if (salt < 0 || (size_t)salt > max) {
+ sp_throw(SCR_ERR_ERROR, "ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE",
+ "error:1C8000A5:Provider routines::data too large for key size");
+ return NULL;
+ }
+ r = mbedtls_rsa_rsassa_pss_sign_ext(rsa, sp_random, NULL, md, (unsigned)hash_len, hash, salt, sig);
+ sig_len = bytes;
+ } else {
+ r = mbedtls_pk_sign(&key->key->pk, md, hash, hash_len, sig, sizeof sig, &sig_len, sp_random, NULL);
+ }
+ if (r != 0) {
+ sp_unsupported_input();
+ return NULL;
+ }
+ if (sp_is_ec(key->key) && key->p1363) {
+ size_t size = sp_ec_size(key->key);
+ unsigned char raw[2 * 66];
+ if (!sp_der_to_p1363(sig, sig_len, size, raw)) {
+ sp_unsupported_input();
+ return NULL;
+ }
+ return sp_dyn_buffer(raw, 2 * size);
+ }
+ return sp_dyn_buffer(sig, sig_len);
+}
+
+/* Verifies a digest against a signature; false for a signature that does
+ * not verify or does not parse. */
+static bool sp_verify_hash(SpSigKey *key, mbedtls_md_type_t md, const unsigned char *hash, size_t hash_len,
+ const unsigned char *sig, size_t sig_len) {
+ if (!sp_rsa_padding(key, md)) return false;
+ if (sp_is_rsa(key->key) && key->padding == 6) {
+ mbedtls_rsa_context *rsa = mbedtls_pk_rsa(key->key->pk);
+ if (sig_len != mbedtls_rsa_get_len(rsa)) return false;
+ int salt = key->salt;
+ if (salt == -1) salt = (int)hash_len;
+ else if (salt == -2) salt = MBEDTLS_RSA_SALT_LEN_ANY;
+ return mbedtls_rsa_rsassa_pss_verify_ext(rsa, md, (unsigned)hash_len, hash, md, salt, sig) == 0;
+ }
+ if (sp_is_ec(key->key) && key->p1363) {
+ unsigned char der[2 * 66 + 16];
+ if (sig_len != 2 * sp_ec_size(key->key)) return false;
+ int n = sp_p1363_to_der(sig, sig_len, der, sizeof der);
+ if (n < 0) return false;
+ return mbedtls_pk_verify(&key->key->pk, md, hash, hash_len, der + sizeof der - n, (size_t)n) == 0;
+ }
+ return mbedtls_pk_verify(&key->key->pk, md, hash, hash_len, sig, sig_len) == 0;
+}
+
+static const char sp_data_types[] = "an instance of Buffer, TypedArray, or DataView";
+
+/* Ed25519 signs the message itself: an algorithm other than null or
+ * undefined is OpenSSL's invalid digest (false, error pending). */
+static bool sp_ed_digest_given(const ScrDyn *algorithm) {
+ if (algorithm->kind == SCR_DYN_UNDEF || algorithm->kind == SCR_DYN_NULL) return true;
+ sp_throw(SCR_ERR_ERROR, "ERR_OSSL_INVALID_DIGEST", "error:1C80007A:Provider routines::invalid digest");
+ return false;
+}
+
+/* crypto.sign(algorithm, data, key): the signature Buffer. */
+ScrDyn *scr_crypto_sign_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key) {
+ mbedtls_md_type_t md;
+ if (!sp_algorithm(algorithm, &md)) return NULL;
+ if (data->kind != SCR_DYN_BYTES) {
+ scr_dyn_arg_type_fail("data", sp_data_types, data);
+ return NULL;
+ }
+ SpSigKey k;
+ if (!sp_sig_key(key, true, &k)) return NULL;
+ ScrBytes *bytes = scr_bytes_buffer_source(data);
+ if (sp_is_ed(k.key)) {
+ ScrDyn *result = NULL;
+ const ScrPkiEd25519 *ed = sp_ed_digest_given(algorithm) ? sp_ed() : NULL;
+ if (ed) {
+ uint8_t sig[64];
+ ed->sign(sig, k.key->ed_seed, k.key->ed_pub, bytes->data, (size_t)scr_bytes_byte_len(bytes));
+ result = sp_dyn_buffer(sig, 64);
+ }
+ scr_bytes_release(bytes);
+ sp_key_release(k.key);
+ return result;
+ }
+ unsigned char hash[MBEDTLS_MD_MAX_SIZE];
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md);
+ mbedtls_md(info, bytes->data, (size_t)scr_bytes_byte_len(bytes), hash);
+ scr_bytes_release(bytes);
+ ScrDyn *result = sp_sign_hash(&k, md, hash, mbedtls_md_get_size(info));
+ sp_key_release(k.key);
+ return result;
+}
+
+/* crypto.verify(algorithm, data, key, signature): a boolean. */
+ScrDyn *scr_crypto_verify_oneshot(ScrDyn *algorithm, ScrDyn *data, ScrDyn *key, ScrDyn *signature) {
+ mbedtls_md_type_t md;
+ if (!sp_algorithm(algorithm, &md)) return NULL;
+ if (data->kind != SCR_DYN_BYTES) {
+ scr_dyn_arg_type_fail("data", sp_data_types, data);
+ return NULL;
+ }
+ SpSigKey k;
+ if (!sp_sig_key(key, false, &k)) return NULL;
+ if (signature->kind != SCR_DYN_BYTES) {
+ sp_key_release(k.key);
+ scr_dyn_arg_type_fail("signature", sp_data_types, signature);
+ return NULL;
+ }
+ ScrBytes *bytes = scr_bytes_buffer_source(data);
+ ScrBytes *sig = scr_bytes_buffer_source(signature);
+ if (sp_is_ed(k.key)) {
+ ScrDyn *result = NULL;
+ const ScrPkiEd25519 *ed = sp_ed_digest_given(algorithm) ? sp_ed() : NULL;
+ if (ed)
+ result = scr_dyn_new_bool(sp_ed_verify(ed, k.key->ed_pub, bytes->data,
+ (size_t)scr_bytes_byte_len(bytes), sig->data,
+ (size_t)scr_bytes_byte_len(sig)));
+ scr_bytes_release(bytes);
+ scr_bytes_release(sig);
+ sp_key_release(k.key);
+ return result;
+ }
+ unsigned char hash[MBEDTLS_MD_MAX_SIZE];
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md);
+ mbedtls_md(info, bytes->data, (size_t)scr_bytes_byte_len(bytes), hash);
+ bool ok = sp_verify_hash(&k, md, hash, mbedtls_md_get_size(info), sig->data, (size_t)scr_bytes_byte_len(sig));
+ scr_bytes_release(bytes);
+ scr_bytes_release(sig);
+ sp_key_release(k.key);
+ return scr_dyn_new_bool(ok);
+}
+
+/* ── Sign and Verify objects ──────────────────────────────────────────── */
+
+typedef struct SpSigner {
+ size_t rc;
+ mbedtls_md_context_t md;
+ mbedtls_md_type_t type;
+ bool done; /* sign()/verify() ran: Node's "Not initialised" afterwards */
+} SpSigner;
+
+static void *sp_signer_retain(void *h) {
+ ((SpSigner *)h)->rc++;
+ return h;
+}
+
+static void sp_signer_release(void *h) {
+ SpSigner *s = h;
+ if (--s->rc) return;
+ mbedtls_md_free(&s->md);
+ free(s);
+}
+
+static void sp_not_initialised(void) {
+ sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_INVALID_STATE", "Not initialised");
+}
+
+/* update(data[, inputEncoding]): strings encode with the encoding (UTF-8
+ * by default). */
+static bool sp_signer_update(SpSigner *s, ScrDyn *const *args, size_t argc) {
+ if (s->done) {
+ sp_not_initialised();
+ return false;
+ }
+ const ScrDyn *data = argc ? args[0] : scr_dyn_undefined();
+ if (data->kind != SCR_DYN_STR && data->kind != SCR_DYN_BYTES) {
+ scr_dyn_arg_type_fail("data", "of type string or an instance of Buffer, TypedArray, or DataView", data);
+ return false;
+ }
+ ScrBytes *bytes = sp_source_bytes(data, argc > 1 ? args[1] : NULL);
+ if (!bytes) return false;
+ mbedtls_md_update(&s->md, bytes->data, (size_t)scr_bytes_byte_len(bytes));
+ scr_bytes_release(bytes);
+ return true;
+}
+
+/* An output or signature encoding argument: a string encoding, or the
+ * Buffer form for undefined, null and "buffer". */
+static const ScrStr *sp_encoding_arg(const ScrDyn *v) {
+ if (v == NULL || v->kind != SCR_DYN_STR || sp_is_str(v, "buffer")) return NULL;
+ return v->v.str;
+}
+
+static ScrDyn *sp_signer_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
+ const char *what, bool verifier) {
+ SpSigner *s = h;
+ if (strcmp(method, "update") == 0) return sp_signer_update(s, args, argc) ? scr_dyn_retain(self) : NULL;
+ if (!verifier && strcmp(method, "sign") == 0) {
+ if (s->done) {
+ sp_not_initialised();
+ return NULL;
+ }
+ SpSigKey k;
+ if (!sp_sig_key(argc ? args[0] : scr_dyn_undefined(), true, &k)) return NULL;
+ if (sp_is_ed(k.key)) {
+ sp_key_release(k.key);
+ sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_UNSUPPORTED_OPERATION", "Unsupported crypto operation");
+ return NULL;
+ }
+ unsigned char hash[MBEDTLS_MD_MAX_SIZE];
+ mbedtls_md_finish(&s->md, hash);
+ s->done = true;
+ ScrDyn *sig = sp_sign_hash(&k, s->type, hash, mbedtls_md_get_size(mbedtls_md_info_from_type(s->type)));
+ sp_key_release(k.key);
+ const ScrStr *encoding = sp_encoding_arg(argc > 1 ? args[1] : NULL);
+ if (!sig || !encoding) return sig;
+ ScrBytes *bytes = scr_bytes_buffer_source(sig);
+ ScrStr *text = scr_bytes_to_str_checked(bytes, encoding);
+ scr_bytes_release(bytes);
+ scr_dyn_release(sig);
+ return text ? sp_dyn_str(text) : NULL;
+ }
+ if (verifier && strcmp(method, "verify") == 0) {
+ if (s->done) {
+ sp_not_initialised();
+ return NULL;
+ }
+ SpSigKey k;
+ if (!sp_sig_key(argc ? args[0] : scr_dyn_undefined(), false, &k)) return NULL;
+ if (sp_is_ed(k.key)) {
+ sp_key_release(k.key);
+ sp_throw(SCR_ERR_ERROR, "ERR_CRYPTO_UNSUPPORTED_OPERATION", "Unsupported crypto operation");
+ return NULL;
+ }
+ const ScrDyn *signature = argc > 1 ? args[1] : scr_dyn_undefined();
+ if (signature->kind != SCR_DYN_STR && signature->kind != SCR_DYN_BYTES) {
+ sp_key_release(k.key);
+ scr_dyn_arg_type_fail("signature", "of type string or an instance of Buffer, TypedArray, or DataView",
+ signature);
+ return NULL;
+ }
+ const ScrStr *encoding = sp_encoding_arg(argc > 2 ? args[2] : NULL);
+ ScrDyn *encoding_value = encoding ? scr_dyn_new_str((ScrStr *)encoding) : NULL;
+ ScrBytes *sig = sp_source_bytes(signature, encoding_value);
+ if (encoding_value) scr_dyn_release(encoding_value);
+ if (!sig) {
+ sp_key_release(k.key);
+ return NULL;
+ }
+ unsigned char hash[MBEDTLS_MD_MAX_SIZE];
+ mbedtls_md_finish(&s->md, hash);
+ s->done = true;
+ bool ok = sp_verify_hash(&k, s->type, hash, mbedtls_md_get_size(mbedtls_md_info_from_type(s->type)),
+ sig->data, (size_t)scr_bytes_byte_len(sig));
+ scr_bytes_release(sig);
+ sp_key_release(k.key);
+ return scr_dyn_new_bool(ok);
+ }
+ scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what);
+ return NULL;
+}
+
+static ScrDyn *sp_sign_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
+ const char *what) {
+ return sp_signer_invoke(h, self, method, args, argc, what, false);
+}
+
+static ScrDyn *sp_verify_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
+ const char *what) {
+ return sp_signer_invoke(h, self, method, args, argc, what, true);
+}
+
+/* Sign and Verify have no modeled properties. */
+static ScrDyn *sp_signer_get(void *h, const char *name, size_t len) {
+ (void)h;
+ (void)name;
+ (void)len;
+ return NULL;
+}
+
+static bool sp_signer_set(void *h, const char *name, size_t len, const ScrDyn *value) {
+ (void)h;
+ (void)name;
+ (void)len;
+ (void)value;
+ return false;
+}
+
+static ScrDyn *sp_signer_inspect_view(void *h) {
+ (void)h;
+ return scr_dyn_new_obj();
+}
+
+static const ScrDynHandleOps sp_sign_ops = {
+ .cls = "Sign",
+ .retain = sp_signer_retain,
+ .release = sp_signer_release,
+ .invoke = sp_sign_invoke,
+ .get = sp_signer_get,
+ .set = sp_signer_set,
+ .inspect_view = sp_signer_inspect_view,
+};
+
+static const ScrDynHandleOps sp_verify_ops = {
+ .cls = "Verify",
+ .retain = sp_signer_retain,
+ .release = sp_signer_release,
+ .invoke = sp_verify_invoke,
+ .get = sp_signer_get,
+ .set = sp_signer_set,
+ .inspect_view = sp_signer_inspect_view,
+};
+
+/* createSign(algorithm) / createVerify(algorithm). */
+static ScrDyn *sp_signer_new(ScrDyn *algorithm, bool verifier) {
+ if (algorithm->kind != SCR_DYN_STR) {
+ scr_dyn_arg_type_fail("algorithm", "of type string", algorithm);
+ return NULL;
+ }
+ mbedtls_md_type_t md;
+ if (!sp_digest_of(algorithm->v.str, &md)) {
+ sp_throw(SCR_ERR_TYPE, "ERR_CRYPTO_INVALID_DIGEST", "Invalid digest");
+ return NULL;
+ }
+ static SCR_TL bool installed;
+ if (!installed) {
+ installed = true;
+ scr_dyn_handle_install(SCR_DYNH_SIGN, &sp_sign_ops);
+ scr_dyn_handle_install(SCR_DYNH_VERIFY, &sp_verify_ops);
+ }
+ SpSigner *s = calloc(1, sizeof *s);
+ if (!s) scr_trap("scriptc: out of memory\n");
+ s->rc = 1;
+ s->type = md;
+ mbedtls_md_init(&s->md);
+ mbedtls_md_setup(&s->md, mbedtls_md_info_from_type(md), 0);
+ mbedtls_md_starts(&s->md);
+ ScrDyn *box = scr_dyn_new_handle(s, verifier ? SCR_DYNH_VERIFY : SCR_DYNH_SIGN);
+ sp_signer_release(s);
+ return box;
+}
+
+ScrDyn *scr_crypto_create_sign(ScrDyn *algorithm) { return sp_signer_new(algorithm, false); }
+
+ScrDyn *scr_crypto_create_verify(ScrDyn *algorithm) { return sp_signer_new(algorithm, true); }
+
+/* ── generateKeyPair ──────────────────────────────────────────────────── */
+
+static ScrDyn *sp_cap(ScrClosure *c, size_t i) { return scr_box_get_ref(c->caps[i]); }
+
+static void sp_key_pair_deliver(ScrClosure *self) {
+ ScrDyn *callback = sp_cap(self, 0);
+ ScrDyn *pair = sp_cap(self, 1);
+ ScrDyn *args[3] = {scr_dyn_new_null(), scr_dyn_retain(scr_dyn_obj_get(pair, "publicKey", 9)),
+ scr_dyn_retain(scr_dyn_obj_get(pair, "privateKey", 10))};
+ ScrDyn *result = scr_dyn_call(callback, args, 3, "callback");
+ if (result) scr_dyn_release(result);
+ for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]);
+ scr_dyn_release(pair);
+ scr_dyn_release(callback);
+}
+
+/* generateKeyPair(type, options, callback): argument errors throw, and the
+ * callback receives (null, publicKey, privateKey) in a later macrotask. */
+void scr_crypto_generate_key_pair_async(ScrDyn *type, ScrDyn *options, ScrDyn *callback) {
+ ScrDyn *pair = scr_crypto_generate_key_pair_sync(type, options);
+ if (!pair) return;
+ ScrClosure *c = scr_closure_new((void *)sp_key_pair_deliver, 2);
+ c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
+ scr_box_set_ref(c->caps[0], scr_dyn_retain(callback));
+ c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
+ scr_box_set_ref(c->caps[1], pair);
+ scr_loop_defer(c);
+}
+
+/* ── the core for the WebCrypto layer (scr_pki.h) ─────────────────────── */
+
+ScrPkiKey *scr_pki_key_retain(ScrPkiKey *key) { return sp_key_retain(key); }
+
+void scr_pki_key_release(ScrPkiKey *key) { sp_key_release(key); }
+
+bool scr_pki_key_is_private(const ScrPkiKey *key) { return key->private_key; }
+
+bool scr_pki_key_is_rsa(const ScrPkiKey *key) { return sp_is_rsa(key); }
+
+bool scr_pki_key_is_ed25519(const ScrPkiKey *key) { return sp_is_ed(key); }
+
+const char *scr_pki_key_curve(const ScrPkiKey *key) {
+ const SpCurve *curve = sp_curve_of(key);
+ return curve ? curve->jwk : NULL;
+}
+
+size_t scr_pki_key_bits(const ScrPkiKey *key) { return mbedtls_pk_get_bitlen(&key->pk); }
+
+ScrBytes *scr_pki_key_exponent(const ScrPkiKey *key) {
+ mbedtls_mpi e;
+ mbedtls_mpi_init(&e);
+ mbedtls_rsa_export(mbedtls_pk_rsa(key->pk), NULL, NULL, NULL, NULL, &e);
+ size_t n = mbedtls_mpi_size(&e);
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)n);
+ if (n) mbedtls_mpi_write_binary(&e, out->data, n);
+ mbedtls_mpi_free(&e);
+ return out;
+}
+
+ScrPkiKey *scr_pki_key_parse_der(const uint8_t *der, size_t len, bool private_key) {
+ if ((private_key && len == 48 && memcmp(der, sp_ed_pkcs8_prefix, 16) == 0) ||
+ (!private_key && len == 44 && memcmp(der, sp_ed_spki_prefix, 12) == 0))
+ {
+ bool failed;
+ return sp_ed_from_der(der, len, private_key, &failed);
+ }
+ SpKey *k = sp_key_alloc(private_key);
+ int r = private_key ? mbedtls_pk_parse_key(&k->pk, der, len, NULL, 0, sp_random, NULL)
+ : mbedtls_pk_parse_public_key(&k->pk, der, len);
+ if (r != 0 || !sp_key_supported(k)) {
+ sp_key_release(k);
+ return NULL;
+ }
+ return k;
+}
+
+ScrPkiKey *scr_pki_key_from_jwk(const ScrDyn *jwk, bool private_key) {
+ SpKey *k = sp_key_from_jwk(jwk, private_key);
+ if (!k) scr_exc_clear();
+ return k;
+}
+
+ScrPkiKey *scr_pki_key_from_point(const char *curve_name, const uint8_t *point, size_t len) {
+ const SpCurve *curve = NULL;
+ for (size_t i = 0; i < sizeof sp_curves / sizeof sp_curves[0]; i++)
+ if (strcmp(sp_curves[i].jwk, curve_name) == 0) curve = &sp_curves[i];
+ if (!curve) return NULL;
+ SpKey *k = sp_key_alloc(false);
+ bool ok = false;
+ if (mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) == 0) {
+ mbedtls_ecp_keypair *ec = mbedtls_pk_ec(k->pk);
+ ok = mbedtls_ecp_group_load(&ec->MBEDTLS_PRIVATE(grp), curve->id) == 0 &&
+ mbedtls_ecp_point_read_binary(&ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(Q), point, len) == 0 &&
+ mbedtls_ecp_check_pubkey(&ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(Q)) == 0;
+ }
+ if (!ok) {
+ sp_key_release(k);
+ return NULL;
+ }
+ return k;
+}
+
+ScrPkiKey *scr_pki_key_public_of(ScrPkiKey *key) { return sp_public_of(key); }
+
+ScrPkiKey *scr_pki_generate_rsa(unsigned bits, uint64_t exponent) {
+ SpKey *k = sp_key_alloc(true);
+ if (exponent > 2147483647 || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) != 0 ||
+ mbedtls_rsa_gen_key(mbedtls_pk_rsa(k->pk), sp_random, NULL, bits, (int)exponent) != 0) {
+ sp_key_release(k);
+ return NULL;
+ }
+ return k;
+}
+
+ScrPkiKey *scr_pki_generate_ec(const char *curve_name) {
+ const SpCurve *curve = NULL;
+ for (size_t i = 0; i < sizeof sp_curves / sizeof sp_curves[0]; i++)
+ if (strcmp(sp_curves[i].jwk, curve_name) == 0) curve = &sp_curves[i];
+ SpKey *k = sp_key_alloc(true);
+ if (!curve || mbedtls_pk_setup(&k->pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) != 0 ||
+ mbedtls_ecp_gen_key(curve->id, mbedtls_pk_ec(k->pk), sp_random, NULL) != 0) {
+ sp_key_release(k);
+ return NULL;
+ }
+ return k;
+}
+
+ScrPkiKey *scr_pki_generate_ed25519(void) {
+ const ScrPkiEd25519 *ed = sp_ed();
+ if (!ed) return NULL;
+ SpKey *k = sp_key_alloc(true);
+ k->ed25519 = true;
+ sp_random(NULL, k->ed_seed, 32);
+ ed->public_key(k->ed_pub, k->ed_seed);
+ return k;
+}
+
+ScrPkiKey *scr_pki_key_from_ed25519_raw(const uint8_t *raw, size_t len) {
+ if (len != 32) return NULL;
+ SpKey *k = sp_key_alloc(false);
+ k->ed25519 = true;
+ memcpy(k->ed_pub, raw, 32);
+ return k;
+}
+
+ScrBytes *scr_pki_export_ed25519_raw(ScrPkiKey *key) {
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, 32);
+ memcpy(out->data, key->ed_pub, 32);
+ return out;
+}
+
+ScrBytes *scr_pki_export_der(ScrPkiKey *key) {
+ unsigned char *der = malloc(SP_DER_MAX);
+ if (!der) scr_trap("scriptc: out of memory\n");
+ int n = key->private_key ? sp_write_pkcs8_der(key, der, SP_DER_MAX) : sp_write_spki_der(key, der, SP_DER_MAX);
+ ScrBytes *out = NULL;
+ if (n > 0) {
+ out = scr_bytes_new(SCR_BYTES_U8, (double)n);
+ memcpy(out->data, der + SP_DER_MAX - n, (size_t)n);
+ }
+ mbedtls_platform_zeroize(der, SP_DER_MAX);
+ free(der);
+ return out;
+}
+
+ScrBytes *scr_pki_export_point(ScrPkiKey *key) {
+ const mbedtls_ecp_keypair *ec = mbedtls_pk_ec(key->pk);
+ unsigned char point[160];
+ size_t n;
+ if (mbedtls_ecp_point_write_binary(&ec->MBEDTLS_PRIVATE(grp), &ec->MBEDTLS_PRIVATE(Q),
+ MBEDTLS_ECP_PF_UNCOMPRESSED, &n, point, sizeof point) != 0)
+ return NULL;
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, (double)n);
+ memcpy(out->data, point, n);
+ return out;
+}
+
+ScrDyn *scr_pki_export_jwk(ScrPkiKey *key) { return sp_export_jwk(key); }
+
+static bool sp_webcrypto_md(const char *hash, mbedtls_md_type_t *md) {
+ if (strcmp(hash, "SHA-1") == 0) *md = MBEDTLS_MD_SHA1;
+ else if (strcmp(hash, "SHA-256") == 0) *md = MBEDTLS_MD_SHA256;
+ else if (strcmp(hash, "SHA-384") == 0) *md = MBEDTLS_MD_SHA384;
+ else if (strcmp(hash, "SHA-512") == 0) *md = MBEDTLS_MD_SHA512;
+ else return false;
+ return true;
+}
+
+ScrBytes *scr_pki_sign(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
+ size_t len) {
+ if (sp_is_ed(key)) {
+ const ScrPkiEd25519 *ed = sp_ed();
+ if (!ed) return NULL;
+ ScrBytes *out = scr_bytes_new(SCR_BYTES_U8, 64);
+ ed->sign(out->data, key->ed_seed, key->ed_pub, data, len);
+ return out;
+ }
+ mbedtls_md_type_t md;
+ if (!sp_webcrypto_md(hash, &md)) return NULL;
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md);
+ unsigned char digest[MBEDTLS_MD_MAX_SIZE];
+ mbedtls_md(info, data, len, digest);
+ SpSigKey k = {.key = key, .padding = pss ? 6 : 1, .salt = salt, .p1363 = true};
+ ScrDyn *sig = sp_sign_hash(&k, md, digest, mbedtls_md_get_size(info));
+ if (!sig) {
+ scr_exc_clear();
+ return NULL;
+ }
+ ScrBytes *out = scr_bytes_buffer_source(sig);
+ scr_dyn_release(sig);
+ return out;
+}
+
+bool scr_pki_verify(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
+ size_t len, const uint8_t *signature, size_t signature_len) {
+ if (sp_is_ed(key)) {
+ const ScrPkiEd25519 *ed = sp_ed();
+ return ed && sp_ed_verify(ed, key->ed_pub, data, len, signature, signature_len);
+ }
+ mbedtls_md_type_t md;
+ if (!sp_webcrypto_md(hash, &md)) return false;
+ const mbedtls_md_info_t *info = mbedtls_md_info_from_type(md);
+ unsigned char digest[MBEDTLS_MD_MAX_SIZE];
+ mbedtls_md(info, data, len, digest);
+ SpSigKey k = {.key = key, .padding = pss ? 6 : 1, .salt = salt, .p1363 = true};
+ return sp_verify_hash(&k, md, digest, mbedtls_md_get_size(info), signature, signature_len);
+}
diff --git a/packages/runtime/src/scr_pki.h b/packages/runtime/src/scr_pki.h
new file mode 100644
index 000000000..71be3c1df
--- /dev/null
+++ b/packages/runtime/src/scr_pki.h
@@ -0,0 +1,79 @@
+/* The key and signature core of scr_pki.c, for the WebCrypto layer
+ * (scr_webcrypto_pki.c) and the Ed25519 unit (scr_ed25519.c). Keys are
+ * reference-counted RSA keys, EC keys on P-256, P-384, P-521 or secp256k1,
+ * or Ed25519 keys. Functions answering NULL or false set no error unless
+ * they say so; byte results are +1. */
+#ifndef SCR_PKI_H
+#define SCR_PKI_H
+
+#include
+#include
+#include
+
+typedef struct SpKey ScrPkiKey;
+
+/* Throws an error of `kind` (with `code`, or none for NULL) whose message
+ * is the printf format's whole output, at any length. */
+void scr_pki_throw_fmt(int kind, const char *code, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
+
+/* The Ed25519 arithmetic (scr_ed25519.c), installed at startup in
+ * programs that name Ed25519; NULL otherwise. Keys are the 32-byte seed
+ * and the 32-byte encoded public point. */
+typedef struct {
+ void (*public_key)(uint8_t pub[32], const uint8_t seed[32]);
+ void (*sign)(uint8_t sig[64], const uint8_t seed[32], const uint8_t pub[32], const uint8_t *m, size_t n);
+ bool (*verify)(const uint8_t pub[32], const uint8_t *m, size_t n, const uint8_t sig[64]);
+} ScrPkiEd25519;
+extern const ScrPkiEd25519 *scr_pki_ed25519;
+
+/* Whether the Ed25519 arithmetic is linked; false with a coded SC2020
+ * error pending otherwise. */
+bool scr_pki_ed25519_ready(void);
+
+ScrPkiKey *scr_pki_key_retain(ScrPkiKey *key);
+void scr_pki_key_release(ScrPkiKey *key);
+bool scr_pki_key_is_private(const ScrPkiKey *key);
+bool scr_pki_key_is_rsa(const ScrPkiKey *key);
+bool scr_pki_key_is_ed25519(const ScrPkiKey *key);
+/* The EC key's JWK curve name ("P-256", ...), or NULL for RSA keys. */
+const char *scr_pki_key_curve(const ScrPkiKey *key);
+/* The RSA modulus length in bits. */
+size_t scr_pki_key_bits(const ScrPkiKey *key);
+/* The RSA public exponent's big-endian bytes, without leading zeros. */
+ScrBytes *scr_pki_key_exponent(const ScrPkiKey *key);
+
+/* A key from SubjectPublicKeyInfo (public) or PKCS#8 (private) DER. */
+ScrPkiKey *scr_pki_key_parse_der(const uint8_t *der, size_t len, bool private_key);
+/* A key from a JWK object (borrowed; a plain object). */
+ScrPkiKey *scr_pki_key_from_jwk(const ScrDyn *jwk, bool private_key);
+/* A public EC key from an uncompressed or compressed point. */
+ScrPkiKey *scr_pki_key_from_point(const char *curve, const uint8_t *point, size_t len);
+ScrPkiKey *scr_pki_key_public_of(ScrPkiKey *key);
+ScrPkiKey *scr_pki_generate_rsa(unsigned bits, uint64_t exponent);
+ScrPkiKey *scr_pki_generate_ec(const char *curve);
+/* NULL with a coded SC2020 error pending when the program does not link
+ * the Ed25519 unit. */
+ScrPkiKey *scr_pki_generate_ed25519(void);
+/* A public Ed25519 key from its 32-byte encoding. */
+ScrPkiKey *scr_pki_key_from_ed25519_raw(const uint8_t *raw, size_t len);
+/* The Ed25519 public key's 32-byte encoding. */
+ScrBytes *scr_pki_export_ed25519_raw(ScrPkiKey *key);
+
+/* SubjectPublicKeyInfo (public keys) or PKCS#8 (private keys) DER. */
+ScrBytes *scr_pki_export_der(ScrPkiKey *key);
+/* The EC public key's uncompressed point. */
+ScrBytes *scr_pki_export_point(ScrPkiKey *key);
+/* The JWK members in Node's order (kty, n, e, ... or kty, x, y, crv, d),
+ * +1. */
+ScrDyn *scr_pki_export_jwk(ScrPkiKey *key);
+
+/* Signs `data` with the digest named by its WebCrypto name ("SHA-256"):
+ * RSA PKCS#1 v1.5 (pss false), RSA-PSS with `salt` bytes, or ECDSA as
+ * IEEE P1363 r || s. Ed25519 keys sign the message itself (hash
+ * ignored). */
+ScrBytes *scr_pki_sign(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
+ size_t len);
+bool scr_pki_verify(ScrPkiKey *key, const char *hash, bool pss, int salt, const uint8_t *data,
+ size_t len, const uint8_t *signature, size_t signature_len);
+
+#endif
diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c
index 271937f2e..be5ccb910 100644
--- a/packages/runtime/src/scr_stream.c
+++ b/packages/runtime/src/scr_stream.c
@@ -80,8 +80,11 @@ static ScrBytes *scr_stream_buffer(ScrBytes *b) {
/* ── state ────────────────────────────────────────────────────────────── */
typedef struct ScrWEntry {
- ScrBytes *chunk; /* owned */
+ ScrBytes *chunk; /* owned; a ScrDyn* value on an object-mode writable side */
ScrClosure *cb; /* owned or NULL (the user's write(chunk, cb) callback) */
+ ScrDyn *enc; /* owned or NULL = undefined: the encoding argument an
+ * object-mode write passed (Node hands it to _write
+ * unchanged; null when the callback took its place) */
} ScrWEntry;
struct ScrStreamState {
@@ -132,6 +135,14 @@ struct ScrStreamState {
bool object_entries;
/* An operator output's entry kind is decided by its first push. */
bool object_kind_set;
+ /* An object-mode buffer that holds a value other than a string or a
+ * Buffer, or both kinds: every entry is an owned ScrDyn*
+ * (scr_stream_add_value). Takes precedence over `encoded`. */
+ bool dyn_entries;
+ /* The objectMode or readableObjectMode option made the buffer hold
+ * object entries (a stream without _read is not a seeded
+ * Readable.from source). */
+ bool object_mode_opt;
/* The parked for-await next() promise (at most one — the loop awaits
* each chunk before asking again). next_dyn picks the resolution
* shape: a dyn boxed by tag (the JS lane), or bytes. */
@@ -146,6 +157,11 @@ struct ScrStreamState {
* nextTick and then the resumed `await new Promise(next)`
* (next_wake: that tick is queued). */
bool next_parked, next_wake;
+ /* Node's iterator ends through eos, which waits for 'close' when the
+ * stream will emit it (willEmitClose, fixed when the iterator starts:
+ * a readable-only stream with autoDestroy and emitClose, not yet
+ * closed). next_eof_close: the ended waiter is held for 'close'. */
+ bool next_started, next_close_wait, next_eof_close;
ScrClosure *read_cb;
ScrStreamReadInv read_inv;
} r;
@@ -157,9 +173,23 @@ struct ScrStreamState {
size_t n, cap, head;
int corked;
bool writing, wsync, need_drain, after_scheduled;
+ /* Deferred afterWrites Node also defers (a user callback, a 'drain'
+ * or a destroyed stream): its pendingcb, which holds the finish until
+ * they ran — afterWrite's finishMaybe then puts 'finish' on a tick
+ * of its own. */
+ size_t after_holds;
+ /* clearBuffer is running its loop: a write completing inside it
+ * leaves the next buffered write to the loop (Node's
+ * kBufferProcessing), so completions keep their order */
+ bool buffer_processing;
+ /* objectMode writable side: queued chunks, and the chunk the write
+ * and transform steps receive, are ScrDyn* values (in the ScrBytes*
+ * slots), each counting 1 toward length. */
+ bool object_mode;
bool ending, finished, prefinished, final_called, finish_scheduled;
bool final_pending; /* _final called, its callback not yet run */
ScrClosure *inflight_cb; /* owned or NULL */
+ ScrDyn *cur_enc; /* borrowed: the running write's encoding argument */
ScrClosure *write_cb;
ScrStreamChunkInv write_inv;
ScrClosure *final_cb;
@@ -267,31 +297,79 @@ static bool scr_stream_is(const ScrEmitter *em) {
scr_vt_within(vt, &scr_passthrough_vt);
}
-/* Readable-buffer entry accessors: bytes chunks, or strings once an
+/* Readable-buffer entry accessors: bytes chunks, strings once an
* encoding is set (lengths then count JS string units, Node's encoded
- * accounting). */
+ * accounting), or checked-dynamic values in an object-mode buffer that
+ * holds other values. */
+typedef enum { SCR_ENTRY_BYTES, SCR_ENTRY_STR, SCR_ENTRY_DYN } ScrEntryKind;
+
+static ScrEntryKind scr_stream_rkind(const ScrStreamState *st) {
+ return st->r.dyn_entries ? SCR_ENTRY_DYN : st->r.encoded ? SCR_ENTRY_STR : SCR_ENTRY_BYTES;
+}
+
static size_t scr_stream_entry_len(const ScrStreamState *st, void *e) {
if (st->r.object_entries) return 1; /* objectMode-style accounting */
return st->r.encoded ? (size_t)scr_str_utf16_len((ScrStr *)e) : ((ScrBytes *)e)->len;
}
-static void scr_stream_entry_release(const ScrStreamState *st, void *e) {
- if (st->r.encoded) scr_str_release((ScrStr *)e);
+/* Releases an entry by the kind it had when it left the buffer: a 'data'
+ * listener may call setEncoding() or push another kind of value while the
+ * entry is out. */
+static void scr_stream_entry_release_kind(void *e, ScrEntryKind kind) {
+ if (kind == SCR_ENTRY_DYN) scr_dyn_release((ScrDyn *)e);
+ else if (kind == SCR_ENTRY_STR) scr_str_release((ScrStr *)e);
else scr_bytes_release((ScrBytes *)e);
}
-/* Releases an entry by the kind it had when it left the buffer: a 'data'
- * listener may call setEncoding() while the entry is out. */
-static void scr_stream_entry_release_kind(void *e, bool is_str) {
- if (is_str) scr_str_release((ScrStr *)e);
- else scr_bytes_release((ScrBytes *)e);
+static void scr_stream_entry_release(const ScrStreamState *st, void *e) {
+ scr_stream_entry_release_kind(e, scr_stream_rkind(st));
+}
+
+/* An entry of the given kind as a dyn value (+1; moves entry). */
+static ScrDyn *scr_stream_entry_box(void *entry, ScrEntryKind kind) {
+ if (kind == SCR_ENTRY_DYN) return entry;
+ ScrDyn *d;
+ if (kind == SCR_ENTRY_STR) {
+ d = scr_dyn_new_str((ScrStr *)entry);
+ scr_str_release((ScrStr *)entry);
+ } else {
+ ScrBytes *b = entry;
+ d = b->is_buffer ? scr_dyn_new_buffer(b) : scr_dyn_new_bytes(b);
+ scr_bytes_release(b);
+ }
+ return d;
+}
+
+/* Writable-queue chunks: Buffers, or dyn values on an object-mode side. */
+static size_t scr_stream_wchunk_len(const ScrStreamState *st, ScrBytes *chunk) {
+ return st->w.object_mode ? 1 : chunk->len;
+}
+
+static ScrBytes *scr_stream_wchunk_retain(const ScrStreamState *st, ScrBytes *chunk) {
+ if (st->w.object_mode) return (ScrBytes *)scr_dyn_retain((ScrDyn *)chunk);
+ return scr_bytes_retain(chunk);
+}
+
+static void scr_stream_wchunk_release(const ScrStreamState *st, ScrBytes *chunk) {
+ if (st->w.object_mode) scr_dyn_release((ScrDyn *)chunk);
+ else scr_bytes_release(chunk);
+}
+
+/* A dyn entry the collector traces, and so frees itself in a collection
+ * (the kinds that carry a collector header). */
+static bool scr_stream_dyn_entry_traced(const ScrDyn *d) {
+ return d->kind > SCR_DYN_STR && d->kind != SCR_DYN_UNDEF;
}
static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
if (!st) return;
if (st->errored) scr_error_release(st->errored);
if (st->native) st->native_release(st->native);
- for (size_t i = 0; i < st->r.n; i++) scr_stream_entry_release(st, st->r.buf[i]);
+ for (size_t i = 0; i < st->r.n; i++) {
+ /* The collector frees the dyn entries it traced. */
+ if (gc && st->r.dyn_entries && scr_stream_dyn_entry_traced(st->r.buf[i])) continue;
+ scr_stream_entry_release(st, st->r.buf[i]);
+ }
free(st->r.buf);
if (st->r.enc) scr_str_release(st->r.enc);
if (st->r.push_enc) scr_str_release(st->r.push_enc);
@@ -312,8 +390,11 @@ static void scr_stream_state_drop(ScrStreamState *st, bool gc) {
for (size_t i = 0; i < st->drain_srcs.n; i++) scr_stream_release(st->drain_srcs.src[i]);
}
for (size_t i = st->w.head; i < st->w.n; i++) {
- scr_bytes_release(st->w.q[i].chunk);
+ bool traced = st->w.object_mode && scr_stream_dyn_entry_traced((ScrDyn *)st->w.q[i].chunk);
+ if (!gc || !traced) scr_stream_wchunk_release(st, st->w.q[i].chunk);
if (!gc && st->w.q[i].cb) scr_closure_release(st->w.q[i].cb);
+ ScrDyn *enc = st->w.q[i].enc;
+ if (enc && (!gc || !scr_stream_dyn_entry_traced(enc))) scr_dyn_release(enc);
}
free(st->w.q);
free(st->w.end_cbs);
@@ -335,6 +416,11 @@ void scr_stream_st_gcfree(ScrStreamState *st) { scr_stream_state_drop(st, true);
void scr_stream_st_trace(ScrStreamState *st, ScrTraceVisit visit, void *ctx) {
if (!st) return;
+ if (st->r.dyn_entries) {
+ for (size_t i = 0; i < st->r.n; i++) {
+ if (scr_stream_dyn_entry_traced(st->r.buf[i])) visit(st->r.buf[i], ctx);
+ }
+ }
if (st->r.next_waiter) visit(st->r.next_waiter, ctx);
if (st->r.read_cb) visit(st->r.read_cb, ctx);
if (st->construct_cb) visit(st->construct_cb, ctx);
@@ -348,6 +434,9 @@ void scr_stream_st_trace(ScrStreamState *st, ScrTraceVisit visit, void *ctx) {
for (size_t i = 0; i < st->fin.n; i++) visit(st->fin.cb[i], ctx);
for (size_t i = st->w.head; i < st->w.n; i++) {
if (st->w.q[i].cb) visit(st->w.q[i].cb, ctx);
+ if (st->w.object_mode && scr_stream_dyn_entry_traced((ScrDyn *)st->w.q[i].chunk))
+ visit(st->w.q[i].chunk, ctx);
+ if (st->w.q[i].enc && scr_stream_dyn_entry_traced(st->w.q[i].enc)) visit(st->w.q[i].enc, ctx);
}
for (size_t i = 0; i < st->pipes.n; i++) visit(st->pipes.dst[i], ctx);
for (size_t i = 0; i < st->drain_srcs.n; i++) visit(st->drain_srcs.src[i], ctx);
@@ -425,6 +514,7 @@ typedef struct ScrStreamTick {
ScrStreamTickOp op;
ScrError *err; /* owned or NULL (SCR_ST_ERROR) */
ScrClosure *cb; /* owned or NULL (SCR_ST_AFTER_WRITE / SCR_ST_WCB_ERR / SCR_ST_WRITE_ERR) */
+ bool hold; /* SCR_ST_AFTER_WRITE: counted in w.after_holds */
struct ScrStreamTick *next;
} ScrStreamTick;
@@ -486,10 +576,20 @@ static ScrError *scr_stream_mkerr(const char *code, const char *msg) {
return e;
}
+/* The pending exception as a stream error (+1): Error values as
+ * themselves, any other thrown value as an internal assertion. */
+static ScrError *scr_stream_take_error(void) {
+ ScrCaught *caught = scr_exc_take();
+ ScrDyn *value = scr_caught_to_dyn(caught);
+ scr_caught_release(caught);
+ ScrError *err = value ? scr_errdyn_err_of(value) : NULL;
+ if (value) scr_dyn_release(value);
+ return err ? err : scr_stream_mkerr("ERR_INTERNAL_ASSERTION", "a stream operation threw a value that is not an Error");
+}
+
/* ── forward decls ────────────────────────────────────────────────────── */
static void scr_stream_flow(ScrStream *s);
-static int scr_stream_add_entry(ScrStream *s, void *entry /*moves*/, bool is_str);
static void scr_stream_readable_listen(ScrStream *s);
static void scr_stream_emit_readable_now(ScrStream *s);
static void scr_stream_maybe_read_more(ScrStream *s);
@@ -498,7 +598,7 @@ static void scr_stream_clear_buffer(ScrStream *s);
static void scr_stream_do_destroy(ScrStream *s, ScrError *err /*borrowed*/);
static void scr_stream_notify_finished(ScrStream *s);
static void *scr_stream_read_n(ScrStream *s, double size);
-static void *scr_stream_read_n_kind(ScrStream *s, double size, bool *is_str);
+static void *scr_stream_read_n_kind(ScrStream *s, double size, ScrEntryKind *kind);
static void scr_stream_settle_next(ScrStream *s);
static ScrStream *scr_stream_alloc(const ScrVt *vt, const char *cls, bool has_r, bool has_w,
double rhwm, double whwm, bool auto_destroy,
@@ -523,10 +623,10 @@ static void scr_stream_error_or_destroy(ScrStream *s, ScrError *err) {
/* ── readable internals ───────────────────────────────────────────────── */
-static void scr_stream_rbuf_push(ScrStreamState *st, void *chunk /*moves*/, bool is_str,
+static void scr_stream_rbuf_push(ScrStreamState *st, void *chunk /*moves*/, ScrEntryKind kind,
bool front) {
- if (is_str != st->r.encoded) {
- /* every entry has the buffer's representation (encoded: ScrStr) */
+ if (kind != scr_stream_rkind(st)) {
+ /* every entry has the buffer's representation (scr_stream_rkind) */
fputs("scriptc: internal error: stream buffer entry of the wrong kind\n", stderr);
abort();
}
@@ -549,7 +649,8 @@ static void scr_stream_rbuf_push(ScrStreamState *st, void *chunk /*moves*/, bool
}
/* Takes n units (n <= length; bytes, or chars once encoded) off the
- * buffer head as one +1 Buffer / string. */
+ * buffer head as one +1 Buffer / string. An object-mode buffer always
+ * takes one whole entry (n is 1). */
static void *scr_stream_rbuf_take(ScrStreamState *st, size_t n) {
void *first = st->r.n > 0 ? st->r.buf[0] : NULL;
if (first && st->r.head_off == 0 && scr_stream_entry_len(st, first) == n) {
@@ -610,17 +711,29 @@ static void *scr_stream_rbuf_take(ScrStreamState *st, size_t n) {
return out;
}
-/* emit('data', chunk) + pipe delivery. Borrows chunk (a Buffer, or a
- * string once encoded). The emit ABI carries BOTH payload slots — the
- * bytes chunk and the string chunk, exactly one non-NULL — and the
- * compiler's stream-data listener thunks dispatch on which (typed
- * listeners unwrap their declared side; dyn listeners box by tag). */
-static void scr_stream_emit_data(ScrStream *s, void *chunk) {
+/* emit('data', chunk) + pipe delivery. Borrows chunk (a Buffer, a string
+ * once encoded, or a dyn value, by kind). The emit ABI carries three
+ * payload slots: the bytes chunk, the string chunk, and the value of a
+ * dyn entry. A dyn entry that is a Buffer or a string also fills that
+ * slot, so at most one of the first two is non-NULL; the compiler's
+ * stream-data listener thunks dispatch on them (typed listeners unwrap
+ * their declared side; dyn listeners box by tag or take the value). */
+static void scr_stream_emit_data(ScrStream *s, void *chunk, ScrEntryKind kind) {
ScrStreamState *st = s->st;
- ScrBytes *b = st->r.encoded ? NULL : (ScrBytes *)chunk;
- ScrStr *str = st->r.encoded ? (ScrStr *)chunk : NULL;
+ ScrBytes *b = NULL;
+ ScrStr *str = NULL;
+ ScrDyn *value = NULL;
+ if (kind == SCR_ENTRY_DYN) {
+ value = chunk;
+ if (value->kind == SCR_DYN_BYTES) b = value->v.bytes;
+ else if (value->kind == SCR_DYN_STR) str = value->v.str;
+ } else if (kind == SCR_ENTRY_STR) {
+ str = chunk;
+ } else {
+ b = chunk;
+ }
ScrStr *n = scr_str_new("data", 4);
- scr_emitter_emit((ScrEmitter *)s, n, b, str);
+ scr_emitter_emit((ScrEmitter *)s, n, b, str, value);
scr_str_release(n);
if (scr_exc_pending()) return;
/* pipe delivery, with backpressure: a full destination pauses us until
@@ -637,7 +750,9 @@ static void scr_stream_emit_data(ScrStream *s, void *chunk) {
if (scr_exc_pending()) return;
continue; /* the pipes array shifted down over i */
}
- bool ok = b ? scr_stream_write(dst, b, NULL) : scr_stream_write_str(dst, str, NULL);
+ bool ok = value ? scr_stream_write_dyn(dst, value, NULL)
+ : b ? scr_stream_write(dst, b, NULL)
+ : scr_stream_write_str(dst, str, NULL);
if (scr_exc_pending()) return;
i++;
if (!ok && !dst->st->destroyed) {
@@ -681,7 +796,7 @@ static void scr_stream_end_readable(ScrStream *s) {
* ERR_METHOD_NOT_IMPLEMENTED, Node's contract. */
static void scr_stream_call_read(ScrStream *s) {
ScrStreamState *st = s->st;
- if (st->r.read_cb == NULL && st->r.object_entries) {
+ if (st->r.read_cb == NULL && st->r.object_entries && !st->r.object_mode_opt) {
/* Readable.from: fully seeded at construction — _read is a no-op */
st->r.in_read_sync = false;
return;
@@ -716,8 +831,21 @@ static void scr_stream_call_read(ScrStream *s) {
* StringDecoder first (Node decodes at push); an empty decode (a partial
* multi-byte tail held pending) adds nothing but still clears the
* reading flag. */
+static int scr_stream_add_entry(ScrStream *s, void *entry /*moves*/, ScrEntryKind kind);
+static void *scr_stream_entry_convert(ScrStreamState *st, void *entry, ScrEntryKind *kind);
+
static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) {
ScrStreamState *st = s->st;
+ if (st->has_r && st->r.object_entries) {
+ /* object mode: the Buffer is one whole entry */
+ if (!front) return scr_stream_add_entry(s, scr_bytes_retain(chunk), SCR_ENTRY_BYTES) > 0;
+ ScrEntryKind kind = SCR_ENTRY_BYTES;
+ void *entry = scr_stream_entry_convert(st, scr_bytes_retain(chunk), &kind);
+ scr_stream_rbuf_push(st, entry, kind, true);
+ if (st->r.need_readable) scr_stream_emit_readable_nt(s);
+ scr_stream_settle_next(s);
+ return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
+ }
if (!st->has_r) {
ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "push on a Writable (no readable side)");
scr_stream_error_or_destroy(s, e);
@@ -753,12 +881,12 @@ static bool scr_stream_add_chunk(ScrStream *s, ScrBytes *chunk, bool front) {
(scr_emitter_has((ScrEmitter *)s, "data") || st->pipes.n > 0)) {
/* the direct-emit fast path: flowing, empty buffer, async push */
if (st->r.emitted_readable) st->r.emitted_readable = false;
- bool is_str = st->r.encoded;
- scr_stream_emit_data(s, entry);
- scr_stream_entry_release_kind(entry, is_str);
+ ScrEntryKind kind = scr_stream_rkind(st);
+ scr_stream_emit_data(s, entry, kind);
+ scr_stream_entry_release_kind(entry, kind);
if (scr_exc_pending()) return false;
} else {
- scr_stream_rbuf_push(st, entry, st->r.encoded, front);
+ scr_stream_rbuf_push(st, entry, scr_stream_rkind(st), front);
if (st->r.need_readable) scr_stream_emit_readable_nt(s);
}
if (!front) scr_stream_maybe_read_more(s);
@@ -781,8 +909,8 @@ static void scr_stream_maybe_read_more(ScrStream *s) {
* Answers a +1 Buffer — or a +1 string once encoded (lengths and the
* size argument then count JS string units, Node's encoded accounting). */
static void *scr_stream_read_n(ScrStream *s, double size) {
- bool is_str;
- return scr_stream_read_n_kind(s, size, &is_str);
+ ScrEntryKind kind;
+ return scr_stream_read_n_kind(s, size, &kind);
}
/* read_n, also answering the chunk's kind as it was taken from the buffer
@@ -790,9 +918,9 @@ static void *scr_stream_read_n(ScrStream *s, double size) {
* inside the read can change the representation, and a 'data' listener
* can call setEncoding() before this returns, so callers that need the
* kind read it here rather than from st->r.encoded afterwards. */
-static void *scr_stream_read_n_kind(ScrStream *s, double size, bool *is_str) {
+static void *scr_stream_read_n_kind(ScrStream *s, double size, ScrEntryKind *kind) {
ScrStreamState *st = s->st;
- *is_str = false;
+ *kind = scr_stream_rkind(st);
bool absent = size < 0;
size_t n = 0;
if (!absent) {
@@ -852,7 +980,7 @@ static void *scr_stream_read_n_kind(ScrStream *s, double size, bool *is_str) {
}
}
}
- *is_str = st->r.encoded;
+ *kind = scr_stream_rkind(st);
void *ret = want > 0 && want <= st->r.length ? scr_stream_rbuf_take(st, want) : NULL;
if (ret == NULL) {
st->r.need_readable = st->r.length <= st->r.hwm;
@@ -865,9 +993,9 @@ static void *scr_stream_read_n_kind(ScrStream *s, double size, bool *is_str) {
if (st->r.ended && absent) scr_stream_end_readable(s);
}
if (ret != NULL && !st->error_emitted && !st->close_emitted) {
- scr_stream_emit_data(s, ret);
+ scr_stream_emit_data(s, ret, *kind);
if (scr_exc_pending()) {
- scr_stream_entry_release_kind(ret, *is_str);
+ scr_stream_entry_release_kind(ret, *kind);
return NULL;
}
}
@@ -877,10 +1005,10 @@ static void *scr_stream_read_n_kind(ScrStream *s, double size, bool *is_str) {
static void scr_stream_flow(ScrStream *s) {
ScrStreamState *st = s->st;
while (st->r.flowing == 1 && !scr_exc_pending()) {
- bool is_str;
- void *b = scr_stream_read_n_kind(s, -1, &is_str);
+ ScrEntryKind kind;
+ void *b = scr_stream_read_n_kind(s, -1, &kind);
if (b == NULL) break;
- scr_stream_entry_release_kind(b, is_str);
+ scr_stream_entry_release_kind(b, kind);
}
}
@@ -950,43 +1078,33 @@ ScrDyn *scr_stream_done_dyn_d(ScrClosure *clo, ScrDyn *const *args, size_t argc)
return scr_stream_done_dyn_ret();
}
-static void scr_stream_dyn_data(ScrDyn *const *args, size_t argc,
- ScrBytes **data, ScrStr **data_str) {
- *data = NULL;
- *data_str = NULL;
- if (argc < 2) return;
- const ScrDyn *a = args[1];
- if (a->kind == SCR_DYN_BYTES) *data = scr_bytes_raw_view(a->v.bytes);
- else if (a->kind == SCR_DYN_STR) *data_str = scr_str_retain(a->v.str);
-}
-
+/* callback(err, data) of an untyped transform or flush: data is pushed
+ * as push(data) pushes it. */
ScrDyn *scr_stream_done_dyn_t(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
ScrStream *s = scr_stream_dyn_recv(clo);
- ScrBytes *data;
- ScrStr *data_str;
- scr_stream_dyn_data(args, argc, &data, &data_str);
- scr_stream_transform_done(s, scr_stream_dyn_err(args, argc), data, data_str);
+ scr_stream_transform_done_value(s, scr_stream_dyn_err(args, argc), argc > 1 ? args[1] : NULL);
scr_stream_release(s);
return scr_stream_done_dyn_ret();
}
ScrDyn *scr_stream_done_dyn_l(ScrClosure *clo, ScrDyn *const *args, size_t argc) {
ScrStream *s = scr_stream_dyn_recv(clo);
- ScrBytes *data;
- ScrStr *data_str;
- scr_stream_dyn_data(args, argc, &data, &data_str);
- scr_stream_flush_done(s, scr_stream_dyn_err(args, argc), data, data_str);
+ scr_stream_flush_done_value(s, scr_stream_dyn_err(args, argc), argc > 1 ? args[1] : NULL);
scr_stream_release(s);
return scr_stream_done_dyn_ret();
}
/* Checked-dynamic chunks (the JS lane's push/write of any-typed values):
* dispatch by dyn tag — bytes copy out, strings convert utf8, null takes
- * the null path (EOF for push; ERR_STREAM_NULL_VALUES for write). Any
- * other write throws Node's ERR_INVALID_ARG_TYPE; any other push throws
- * the null-values TypeError (Node reports ERR_INVALID_ARG_TYPE through
- * 'error'). Borrow d. */
+ * the null path (EOF for push; ERR_STREAM_NULL_VALUES for write). On an
+ * object-mode side every other value is one entry. Otherwise any other
+ * write throws Node's ERR_INVALID_ARG_TYPE, push(undefined) adds nothing,
+ * and any other push reports ERR_INVALID_ARG_TYPE through 'error', as
+ * readableAddChunkPushByteMode does. Borrow d. */
bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d) {
+ ScrStreamState *st = s->st;
+ if (d->kind != SCR_DYN_NULL && st->has_r && st->r.object_entries)
+ return scr_stream_push_object(s, d) > 0;
switch (d->kind) {
case SCR_DYN_BYTES: {
ScrBytes *b = scr_bytes_raw_view(d->v.bytes);
@@ -998,15 +1116,26 @@ bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d) {
return scr_stream_push_str(s, d->v.str);
case SCR_DYN_NULL:
return scr_stream_push_null(s);
+ case SCR_DYN_UNDEF:
+ if (!st->has_r) return false;
+ st->r.reading = false;
+ scr_stream_maybe_read_more(s);
+ return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
default: {
- static const char msg[] = "May not write null values to stream";
- scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_STREAM_NULL_VALUES");
+ scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer, TypedArray, or DataView", d);
+ ScrError *e = scr_stream_take_error();
+ scr_stream_error_or_destroy(s, e);
+ scr_error_release(e);
return false;
}
}
}
+static bool scr_stream_write_value(ScrStream *s, ScrDyn *v, ScrClosure *cb);
+
bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb) {
+ if (d->kind != SCR_DYN_NULL && s->st->has_w && s->st->w.object_mode)
+ return scr_stream_write_value(s, (ScrDyn *)d, cb);
switch (d->kind) {
case SCR_DYN_BYTES: {
ScrBytes *b = scr_bytes_raw_view(d->v.bytes);
@@ -1051,14 +1180,28 @@ static void scr_stream_next_str_fence(void) {
scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
}
-/* Fulfills the parked iterator with a chunk taken from the buffer; is_str
- * is its kind as taken. The typed lane's chunk is a Buffer, so a string
- * chunk rejects with the encoding fence instead. */
+/* Fulfills the parked iterator with a chunk taken from the buffer; kind
+ * is its kind as taken. The typed lane's chunk is a Buffer, so another
+ * chunk rejects with the encoding fence instead. The dyn lane's end is
+ * the done marker (scr_stream_from_done): an object-mode stream can
+ * yield undefined. */
static void scr_stream_next_fulfill(ScrStreamState *st, ScrPromise *w /*moves*/,
- void *chunk /*moves or NULL=EOF*/, bool is_str) {
+ void *chunk /*moves or NULL=EOF*/, ScrEntryKind kind) {
void *payload;
bool dyn = st->r.next_dyn;
- if (!dyn && chunk != NULL && is_str) {
+ if (!dyn && chunk != NULL && kind == SCR_ENTRY_DYN) {
+ ScrDyn *d = chunk;
+ if (d->kind == SCR_DYN_BYTES) {
+ chunk = scr_bytes_raw_view(d->v.bytes);
+ kind = SCR_ENTRY_BYTES;
+ scr_dyn_release(d);
+ } else {
+ chunk = scr_dyn_string_coerce_js(d);
+ kind = SCR_ENTRY_STR;
+ scr_dyn_release(d);
+ }
+ }
+ if (!dyn && chunk != NULL && kind == SCR_ENTRY_STR) {
scr_str_release((ScrStr *)chunk);
scr_stream_next_str_fence();
scr_promise_reject_pending(w);
@@ -1066,17 +1209,7 @@ static void scr_stream_next_fulfill(ScrStreamState *st, ScrPromise *w /*moves*/,
return;
}
if (dyn) {
- ScrDyn *d;
- if (chunk == NULL) {
- d = scr_dyn_retain(scr_dyn_undefined()); /* the EOF sentinel */
- } else if (is_str) {
- d = scr_dyn_new_str((ScrStr *)chunk);
- scr_str_release((ScrStr *)chunk);
- } else {
- d = scr_dyn_new_buffer((ScrBytes *)chunk);
- scr_bytes_release((ScrBytes *)chunk);
- }
- payload = d;
+ payload = chunk == NULL ? scr_stream_from_done() : scr_stream_entry_box(chunk, kind);
} else {
payload = chunk ? (ScrBytes *)chunk : scr_bytes_new(SCR_BYTES_U8, 0);
}
@@ -1108,11 +1241,11 @@ static void scr_stream_iter_step(ScrStream *s) {
if (!st->r.next_waiter) return;
if (!st->destroyed && !st->errored) {
st->r.next_reading = true;
- bool is_str;
- void *chunk = scr_stream_read_n_kind(s, -1, &is_str);
+ ScrEntryKind kind;
+ void *chunk = scr_stream_read_n_kind(s, -1, &kind);
st->r.next_reading = false;
if (scr_exc_pending()) {
- if (chunk) scr_stream_entry_release_kind(chunk, is_str);
+ if (chunk) scr_stream_entry_release_kind(chunk, kind);
ScrPromise *w = st->r.next_waiter;
st->r.next_waiter = NULL;
if (w) {
@@ -1124,7 +1257,7 @@ static void scr_stream_iter_step(ScrStream *s) {
if (chunk) {
ScrPromise *w = st->r.next_waiter;
st->r.next_waiter = NULL;
- scr_stream_next_fulfill(st, w, chunk, is_str);
+ scr_stream_next_fulfill(st, w, chunk, kind);
return;
}
}
@@ -1175,13 +1308,18 @@ static void scr_stream_settle_next(ScrStream *s) {
}
ScrPromise *w = st->r.next_waiter;
st->r.next_waiter = NULL;
- bool is_str = st->r.encoded;
+ ScrEntryKind kind = scr_stream_rkind(st);
void *chunk = scr_stream_rbuf_take(st, st->r.object_entries ? 1 : st->r.length);
- scr_stream_next_fulfill(st, w, chunk, is_str);
+ scr_stream_next_fulfill(st, w, chunk, kind);
if (st->r.ended && st->r.length == 0) scr_stream_end_readable(s);
return;
}
if (st->r.ended || st->destroyed) {
+ if (st->r.ended && st->r.next_close_wait && !st->close_emitted) {
+ if (!st->r.end_emitted) scr_stream_end_readable(s);
+ st->r.next_eof_close = true;
+ return;
+ }
if (st->r.ended && !st->r.end_emitted) {
/* Node's iterator completes AFTER 'end' (and its autoDestroy):
* queue the sentinel BEHIND the pending 'end' tick, so code after
@@ -1192,7 +1330,7 @@ static void scr_stream_settle_next(ScrStream *s) {
}
ScrPromise *w = st->r.next_waiter;
st->r.next_waiter = NULL;
- scr_stream_next_fulfill(st, w, NULL, false);
+ scr_stream_next_fulfill(st, w, NULL, SCR_ENTRY_BYTES);
}
}
@@ -1201,7 +1339,7 @@ static ScrPromise *scr_stream_next_chunk_impl(ScrStream *s, bool dyn) {
ScrPromise *p = scr_promise_new();
if (!st->has_r) {
st->r.next_dyn = dyn;
- scr_stream_next_fulfill(st, scr_promise_retain(p), NULL, false);
+ scr_stream_next_fulfill(st, scr_promise_retain(p), NULL, SCR_ENTRY_BYTES);
return p;
}
if (st->r.next_waiter) {
@@ -1214,6 +1352,11 @@ static ScrPromise *scr_stream_next_chunk_impl(ScrStream *s, bool dyn) {
st->r.next_dyn = dyn;
st->r.next_waiter = scr_promise_retain(p);
st->r.next_parked = false;
+ if (!st->r.next_started) {
+ st->r.next_started = true;
+ st->r.next_close_wait =
+ !st->has_w && st->auto_destroy && st->emit_close && !st->close_scheduled && !st->close_emitted;
+ }
/* createAsyncIterator starts with stream.on('readable', next) */
scr_stream_readable_listen(s);
scr_stream_iter_step(s);
@@ -1268,25 +1411,11 @@ static void scr_from_src_release(ScrFromSrc *f) {
static void *scr_from_src_retain_v(void *f) { return scr_from_src_retain(f); }
static void scr_from_src_release_v(void *f) { scr_from_src_release(f); }
-/* A Readable.from source whose values no longer fit the buffer's kind
- * (strings beside Buffers): destroyed with the "not supported yet" Error. */
-static void scr_from_destroy_mixed(ScrStream *s) {
- static const char m[] = "a Readable.from source that mixes strings and Buffers is not supported yet";
- ScrStr *msg = scr_str_new(m, sizeof m - 1);
- ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
- scr_str_release(msg);
- scr_stream_release(scr_stream_destroy(s, e));
- scr_error_release(e);
-}
-
/* One typed element into the stream: a whole entry in object mode, a
* chunk in byte mode. Moves e. Returns push's answer. */
static bool scr_from_push_typed(ScrStream *s, void *e, bool strings) {
- if (s->st->r.object_entries) {
- int pushed = scr_stream_add_entry(s, e, strings);
- if (pushed < 0) scr_from_destroy_mixed(s);
- return pushed > 0;
- }
+ if (s->st->r.object_entries)
+ return scr_stream_add_entry(s, e, strings ? SCR_ENTRY_STR : SCR_ENTRY_BYTES) > 0;
bool more = strings ? scr_stream_push_str(s, e) : scr_stream_push(s, e);
if (strings) scr_str_release(e);
else scr_bytes_release(e);
@@ -1447,10 +1576,10 @@ ScrStream *scr_stream_from_arr(ScrArr *arr, double flags, double hwm) {
}
/* The JS lane: one checked-dynamic value into the stream. null is
- * ERR_STREAM_NULL_VALUES; in object mode, values other than strings and
- * Buffers (or a mix of the two) are outside the stream model; byte mode
- * rejects them with push()'s ERR_INVALID_ARG_TYPE. Each failure destroys
- * the stream and answers -1. Borrows v. */
+ * ERR_STREAM_NULL_VALUES; in object mode every other value is an entry;
+ * byte mode rejects values other than strings and Buffers with push()'s
+ * ERR_INVALID_ARG_TYPE. Each failure destroys the stream and answers -1.
+ * Borrows v. */
int scr_stream_from_push(ScrStream *s, const ScrDyn *v) {
ScrError *e = NULL;
if (v->kind == SCR_DYN_NULL) {
@@ -1460,14 +1589,7 @@ int scr_stream_from_push(ScrStream *s, const ScrDyn *v) {
scr_str_release(msg);
scr_error_set_code(e, "ERR_STREAM_NULL_VALUES");
} else if (s->st->r.object_entries) {
- int pushed = scr_stream_push_object(s, v);
- if (pushed >= 0) return pushed;
- static const char mixed[] = "a Readable.from source that mixes strings and Buffers is not supported yet";
- static const char other[] = "Readable.from over values other than strings and Buffers is not supported yet";
- const char *m = pushed == -2 ? mixed : other;
- ScrStr *msg = scr_str_new(m, strlen(m));
- e = scr_error_new(SCR_ERR_ERROR, msg);
- scr_str_release(msg);
+ return scr_stream_push_object(s, v);
} else if (v->kind == SCR_DYN_STR || v->kind == SCR_DYN_BYTES) {
return scr_stream_push_dyn(s, v) ? 1 : 0;
} else {
@@ -1598,61 +1720,72 @@ ScrStream *scr_stream_new_object_readable(ScrClosure *read, ScrStreamReadInv rea
return s;
}
-/* An object-mode entry in the buffer's representation: a Buffer on a
- * stream with a decoder decodes through it (Node's decoder.write in
- * readableAddChunkPushObjectMode); an entry of the other kind otherwise
- * mixes strings and Buffers, which the buffer cannot hold. Moves entry;
- * +1, or NULL (entry released). */
-static void *scr_stream_entry_convert(ScrStreamState *st, void *entry, bool is_str) {
- if (is_str == st->r.encoded) return entry;
- if (!is_str && st->r.decoding) {
+/* The object-mode buffer starts holding dyn values: every buffered entry
+ * is boxed. */
+static void scr_stream_rbuf_to_dyn(ScrStreamState *st) {
+ ScrEntryKind kind = scr_stream_rkind(st);
+ for (size_t i = 0; i < st->r.n; i++) st->r.buf[i] = scr_stream_entry_box(st->r.buf[i], kind);
+ st->r.dyn_entries = true;
+ st->r.object_kind_set = true;
+}
+
+/* An object-mode entry in the buffer's representation (moves entry; +1).
+ * A Buffer on a stream with a decoder decodes through it (Node's
+ * decoder.write in readableAddChunkPushObjectMode); an entry of another
+ * kind turns the buffer into dyn entries. */
+static void *scr_stream_entry_convert(ScrStreamState *st, void *entry, ScrEntryKind *kind) {
+ ScrEntryKind want = scr_stream_rkind(st);
+ if (*kind == want) return entry;
+ if (*kind == SCR_ENTRY_BYTES && want == SCR_ENTRY_STR && st->r.decoding) {
ScrBytes *b = entry;
ScrStr *dec = scr_strdec_write(st->r.enc, st->r.dec_pending, b);
st->r.dec_pending = scr_strdec_next(st->r.enc, st->r.dec_pending, b);
scr_bytes_release(b);
+ *kind = SCR_ENTRY_STR;
return dec;
}
- if (is_str) scr_str_release(entry);
- else scr_bytes_release(entry);
- return NULL;
+ if (want != SCR_ENTRY_DYN) scr_stream_rbuf_to_dyn(st);
+ ScrDyn *d = scr_stream_entry_box(entry, *kind);
+ *kind = SCR_ENTRY_DYN;
+ return d;
}
/* readableAddChunk in object mode: every value is one whole entry, empty
- * strings and Buffers included. is_str gives entry's kind. Answers push()'s
- * result, or -2 (entry released) when the kinds cannot mix. */
-static int scr_stream_add_entry(ScrStream *s, void *entry /*moves*/, bool is_str) {
+ * strings and Buffers included. kind gives entry's kind. Answers push()'s
+ * result. */
+static int scr_stream_add_entry(ScrStream *s, void *entry /*moves*/, ScrEntryKind kind) {
ScrStreamState *st = s->st;
if (!st->r.object_kind_set && st->r.length == 0) {
/* the first entry picks the representation */
st->r.object_kind_set = true;
- if (is_str && !st->r.encoded) {
+ if (kind == SCR_ENTRY_STR && !st->r.encoded) {
st->r.encoded = true;
if (!st->r.enc) st->r.enc = scr_str_new("utf8", 4);
+ } else if (kind == SCR_ENTRY_DYN) {
+ st->r.dyn_entries = true;
}
}
- entry = scr_stream_entry_convert(st, entry, is_str);
- if (entry == NULL) return -2;
+ entry = scr_stream_entry_convert(st, entry, &kind);
if (st->r.ended) {
- scr_stream_entry_release(st, entry);
+ scr_stream_entry_release_kind(entry, kind);
ScrError *e = scr_stream_mkerr("ERR_STREAM_PUSH_AFTER_EOF", "stream.push() after EOF");
scr_stream_error_or_destroy(s, e);
scr_error_release(e);
return 0;
}
if (st->destroyed || st->errored) {
- scr_stream_entry_release(st, entry);
+ scr_stream_entry_release_kind(entry, kind);
return 0;
}
st->r.reading = false;
if (st->r.flowing == 1 && st->r.length == 0 && !st->r.in_read_sync &&
(scr_emitter_has((ScrEmitter *)s, "data") || st->pipes.n > 0)) {
if (st->r.emitted_readable) st->r.emitted_readable = false;
- bool is_str = st->r.encoded;
- scr_stream_emit_data(s, entry);
- scr_stream_entry_release_kind(entry, is_str);
+ scr_stream_emit_data(s, entry, kind);
+ scr_stream_entry_release_kind(entry, kind);
if (scr_exc_pending()) return 0;
} else {
- scr_stream_rbuf_push(st, entry, st->r.encoded, false);
+ scr_stream_rbuf_push(st, entry, kind, false);
if (st->r.need_readable) scr_stream_emit_readable_nt(s);
}
scr_stream_maybe_read_more(s);
@@ -1660,12 +1793,22 @@ static int scr_stream_add_entry(ScrStream *s, void *entry /*moves*/, bool is_str
return !st->r.ended && (st->r.length < st->r.hwm || st->r.length == 0);
}
+/* push(value) on an object-mode stream: a string or a Buffer keeps its
+ * kind, any other value is a dyn entry. Borrows v. */
int scr_stream_push_object(ScrStream *s, const ScrDyn *v) {
- bool is_str = v->kind == SCR_DYN_STR;
- if (!is_str && v->kind != SCR_DYN_BYTES) return -1;
- void *entry = is_str ? (void *)scr_str_retain(v->v.str) : (void *)scr_bytes_raw_view(v->v.bytes);
- int pushed = scr_stream_add_entry(s, entry, is_str);
- return pushed < 0 ? pushed : pushed ? 1 : 0;
+ void *entry;
+ ScrEntryKind kind;
+ if (v->kind == SCR_DYN_STR) {
+ entry = scr_str_retain(v->v.str);
+ kind = SCR_ENTRY_STR;
+ } else if (v->kind == SCR_DYN_BYTES) {
+ entry = scr_bytes_raw_view(v->v.bytes);
+ kind = SCR_ENTRY_BYTES;
+ } else {
+ entry = scr_dyn_retain((ScrDyn *)v);
+ kind = SCR_ENTRY_DYN;
+ }
+ return scr_stream_add_entry(s, entry, kind) ? 1 : 0;
}
/* ── the registration hook ('data'/'readable' listeners kick flow) ────── */
@@ -1706,7 +1849,8 @@ static void scr_stream_on_listener(ScrEmitter *em, ScrStr *name) {
/* ── writable internals ───────────────────────────────────────────────── */
-static void scr_stream_wq_push(ScrStreamState *st, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
+static void scr_stream_wq_push(ScrStreamState *st, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/,
+ ScrDyn *enc /*moves*/) {
if (st->w.n == st->w.cap) {
st->w.cap = st->w.cap ? st->w.cap * 2 : 4;
st->w.q = realloc(st->w.q, st->w.cap * sizeof *st->w.q);
@@ -1714,29 +1858,49 @@ static void scr_stream_wq_push(ScrStreamState *st, ScrBytes *chunk /*moves*/, Sc
}
st->w.q[st->w.n].chunk = chunk;
st->w.q[st->w.n].cb = cb;
+ st->w.q[st->w.n].enc = enc;
st->w.n++;
}
static void scr_stream_transform_callback(ScrStream *s);
-static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
+static void scr_stream_do_write_inner(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/);
+
+/* One write reaching _write/_transform; the callback reads the write's
+ * encoding argument through w.cur_enc while it runs. */
+static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/,
+ ScrDyn *enc /*moves*/) {
+ ScrStreamState *st = s->st;
+ if (enc == NULL) {
+ scr_stream_do_write_inner(s, chunk, cb);
+ return;
+ }
+ ScrDyn *outer = st->w.cur_enc;
+ st->w.cur_enc = enc;
+ scr_stream_do_write_inner(s, chunk, cb);
+ st->w.cur_enc = outer;
+ scr_dyn_release(enc);
+}
+
+static void scr_stream_do_write_inner(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClosure *cb /*moves*/) {
ScrStreamState *st = s->st;
st->w.writing = true;
st->w.wsync = true;
- st->w.inflight_len = chunk->len;
+ st->w.inflight_len = scr_stream_wchunk_len(st, chunk);
st->w.inflight_cb = cb;
if (st->is_transform || st->passthrough) st->xf_length = st->r.length;
if (st->passthrough && st->transform_cb == NULL) {
/* identity: push through, complete synchronously */
- scr_stream_add_chunk(s, chunk, false);
- scr_bytes_release(chunk);
+ if (st->w.object_mode) scr_stream_push_dyn(s, (ScrDyn *)chunk);
+ else scr_stream_add_chunk(s, chunk, false);
+ scr_stream_wchunk_release(st, chunk);
if (!scr_exc_pending()) scr_stream_transform_callback(s);
st->w.wsync = false;
return;
}
if (st->is_transform || st->passthrough) {
if (st->transform_cb == NULL) {
- scr_bytes_release(chunk);
+ scr_stream_wchunk_release(st, chunk);
ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _transform() method is not implemented");
st->w.wsync = false;
scr_stream_error_or_destroy(s, e);
@@ -1744,12 +1908,12 @@ static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClos
return;
}
st->transform_inv(st->transform_cb, s, chunk);
- scr_bytes_release(chunk);
+ scr_stream_wchunk_release(st, chunk);
st->w.wsync = false;
return;
}
if (st->w.write_cb == NULL) {
- scr_bytes_release(chunk);
+ scr_stream_wchunk_release(st, chunk);
ScrError *e = scr_stream_mkerr("ERR_METHOD_NOT_IMPLEMENTED", "The _write() method is not implemented");
st->w.wsync = false;
scr_stream_error_or_destroy(s, e);
@@ -1757,7 +1921,7 @@ static void scr_stream_do_write(ScrStream *s, ScrBytes *chunk /*moves*/, ScrClos
return;
}
st->w.write_inv(st->w.write_cb, s, chunk);
- scr_bytes_release(chunk);
+ scr_stream_wchunk_release(st, chunk);
st->w.wsync = false;
}
@@ -1770,8 +1934,9 @@ static void scr_stream_error_buffer(ScrStream *s) {
if (st->w.writing) return;
while (st->w.head < st->w.n && !scr_exc_pending()) {
ScrWEntry e = st->w.q[st->w.head++];
- st->w.length -= e.chunk->len;
- scr_bytes_release(e.chunk);
+ st->w.length -= scr_stream_wchunk_len(st, e.chunk);
+ scr_stream_wchunk_release(st, e.chunk);
+ if (e.enc) scr_dyn_release(e.enc);
if (e.cb) {
((void (*)(ScrClosure *))e.cb->fn)(e.cb);
scr_closure_release(e.cb);
@@ -1863,12 +2028,14 @@ static SCR_COLD void scr_stream_writev_buffer(ScrStream *s) {
}
st->w.head = 0;
st->w.n = 0;
- scr_stream_do_write(s, all, cbs);
+ scr_stream_do_write(s, all, cbs, NULL);
}
/* The buffered writes, one at a time (Node's clearBuffer without _writev). */
static void scr_stream_clear_buffer_each(ScrStream *s) {
ScrStreamState *st = s->st;
+ if (st->w.buffer_processing) return;
+ st->w.buffer_processing = true;
while (!st->w.writing && st->w.head < st->w.n && st->w.corked == 0 &&
!st->destroyed && !scr_exc_pending()) {
ScrWEntry e = st->w.q[st->w.head++];
@@ -1876,8 +2043,9 @@ static void scr_stream_clear_buffer_each(ScrStream *s) {
st->w.head = 0;
st->w.n = 0;
}
- scr_stream_do_write(s, e.chunk, e.cb);
+ scr_stream_do_write(s, e.chunk, e.cb, e.enc);
}
+ st->w.buffer_processing = false;
}
/* The fs streams' clearBuffer: nothing while constructing, and _writev
@@ -1947,6 +2115,11 @@ static void scr_stream_finish_maybe(ScrStream *s) {
scr_stream_emit0(s, "prefinish");
if (scr_exc_pending()) return;
}
+ /* prefinish ran; without a _final the deferred afterWrites still hold
+ * 'finish' (Node's pendingcb), and the last of them comes back here.
+ * A _final's callback puts 'finish' on a tick at once (callFinal's
+ * onFinish), and a Transform's _final is its flush. */
+ if (st->w.after_holds > 0 && !st->w.final_cb && !st->is_transform && !st->passthrough) return;
st->w.finish_scheduled = true;
scr_st_tick(s, SCR_ST_FINISH, NULL, NULL);
}
@@ -2327,10 +2500,10 @@ static void scr_stream_dynopt_read_inv(ScrClosure *cb, ScrStream *s, double size
static void scr_stream_dynopt_chunk3(ScrClosure *cb, ScrStream *s, ScrBytes *chunk,
ScrDynThunk glue, const char *what) {
ScrDyn *args[3];
- args[0] = scr_dyn_new_buffer(chunk);
- ScrStr *enc = scr_str_new("buffer", 6);
- args[1] = scr_dyn_new_str(enc);
- scr_str_release(enc);
+ /* (chunk, encoding, cb): an object-mode side passes the value and the
+ * write's encoding argument, as Node's Writable does */
+ args[0] = scr_stream_chunk_value(s, chunk);
+ args[1] = scr_stream_chunk_encoding_value(s);
args[2] = scr_stream_dynopt_done(s, glue, 1, "(error)");
ScrDyn *fn = scr_stream_dynopt_target(cb);
ScrDyn *r = scr_dyn_call(fn, args, 3, what);
@@ -2345,10 +2518,8 @@ static void scr_stream_dynopt_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *
static void scr_stream_dynopt_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
ScrDyn *args[3];
- args[0] = scr_dyn_new_buffer(chunk);
- ScrStr *enc = scr_str_new("buffer", 6);
- args[1] = scr_dyn_new_str(enc);
- scr_str_release(enc);
+ args[0] = scr_stream_chunk_value(s, chunk);
+ args[1] = scr_stream_chunk_encoding_value(s);
args[2] = scr_stream_dynopt_done(s, &scr_stream_done_dyn_t, 2, "(error,data)");
ScrDyn *fn = scr_stream_dynopt_target(cb);
ScrDyn *r = scr_dyn_call(fn, args, 3, "transform");
@@ -2398,6 +2569,7 @@ typedef struct {
double rhwm, whwm;
bool auto_destroy, emit_close, allow_half_open;
bool readable_side, writable_side;
+ bool readable_object, writable_object; /* objectMode and the sided keys */
ScrStr *enc; /* owned canonical name, or NULL */
/* BORROWED callable fields (owned by the options record). */
ScrDyn *read, *write, *final_fn, *destroy, *transform, *flush;
@@ -2474,17 +2646,17 @@ static bool scr_stream_parse_dyn_opts(const ScrDyn *opts, char base, ScrStreamDy
out->readable_side = true;
out->writable_side = true;
out->enc = NULL;
+ out->readable_object = out->writable_object = false;
out->read = out->write = out->final_fn = out->destroy = out->transform = out->flush = NULL;
if (opts == NULL || opts->kind != SCR_DYN_OBJ) return true; /* Node's `if (options)` */
const bool duplexish = base == 'd' || base == 't' || base == 'p';
const bool transformish = base == 't' || base == 'p';
const bool r_sided = base != 'w';
- if (scr_stream_dynopt_truthy(opts, "objectMode", 10) ||
- scr_stream_dynopt_truthy(opts, "readableObjectMode", 18) ||
- scr_stream_dynopt_truthy(opts, "writableObjectMode", 18)) {
- static const char m[] = "objectMode streams are not supported yet (chunks are Buffers or utf8 strings)";
- return scr_stream_dynopt_unsupported(m, sizeof m - 1);
+ {
+ bool object = scr_stream_dynopt_truthy(opts, "objectMode", 10);
+ out->readable_object = object || (duplexish && scr_stream_dynopt_truthy(opts, "readableObjectMode", 18));
+ out->writable_object = object || (duplexish && scr_stream_dynopt_truthy(opts, "writableObjectMode", 18));
}
if (scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "decodeStrings", 13))) {
static const char m[] = "decodeStrings: false is not supported yet (chunks always decode to Buffers)";
@@ -2527,7 +2699,7 @@ static bool scr_stream_parse_dyn_opts(const ScrDyn *opts, char base, ScrStreamDy
/* highWaterMark wins over the sided keys (Node's getHighWaterMark);
* the readable side is checked first, as Duplex's Readable constructor
- * runs first. */
+ * runs first. An object-mode side defaults to 16 entries. */
if (duplexish) {
out->rhwm = scr_stream_dynopt_hwm(opts, "readableHighWaterMark", "options.readableHighWaterMark");
if (out->rhwm == -2) return false;
@@ -2538,6 +2710,8 @@ static bool scr_stream_parse_dyn_opts(const ScrDyn *opts, char base, ScrStreamDy
if (hwm == -2) return false;
out->rhwm = out->whwm = hwm;
}
+ if (out->rhwm == -1 && out->readable_object) out->rhwm = 16;
+ if (out->whwm == -1 && out->writable_object) out->whwm = 16;
out->auto_destroy = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "autoDestroy", 11));
out->emit_close = !scr_stream_dynopt_false(scr_dyn_obj_get((ScrDyn *)opts, "emitClose", 9));
if (duplexish) {
@@ -2578,6 +2752,10 @@ static ScrClosure *scr_stream_dynopt_pick(ScrDyn *opt, void *dyn_inv, ScrClosure
return fb;
}
+static void scr_stream_dynopt_apply_object(ScrStream *s, const ScrStreamDynOpts *o) {
+ if (s != NULL && s->st != NULL) scr_stream_set_object_mode(s, o->readable_object, o->writable_object);
+}
+
static void scr_stream_dynopt_apply_enc(ScrStream *s, ScrStr *enc) {
if (enc == NULL) return;
ScrStream *chain = scr_stream_set_encoding(s, enc);
@@ -2593,6 +2771,7 @@ ScrStream *scr_stream_new_readable_dyn(ScrDyn *opts) {
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
ScrStream *s = scr_stream_new_readable(o.rhwm, o.auto_destroy, o.emit_close,
read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
return s;
}
@@ -2604,8 +2783,10 @@ ScrStream *scr_stream_new_writable_dyn(ScrDyn *opts) {
ScrClosure *write = scr_stream_dynopt_pick(o.write, (void *)&scr_stream_dynopt_write_inv, NULL, NULL, &winv);
ScrClosure *final_cb = scr_stream_dynopt_pick(o.final_fn, (void *)&scr_stream_dynopt_final_inv, NULL, NULL, &finv);
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, NULL, NULL, &dinv);
- return scr_stream_new_writable(o.whwm, o.auto_destroy, o.emit_close,
+ ScrStream *s = scr_stream_new_writable(o.whwm, o.auto_destroy, o.emit_close,
write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
+ return s;
}
ScrStream *scr_stream_new_duplex_dyn(ScrDyn *opts) {
@@ -2620,6 +2801,7 @@ ScrStream *scr_stream_new_duplex_dyn(ScrDyn *opts) {
o.allow_half_open, o.readable_side, o.writable_side,
read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
return s;
}
@@ -2640,6 +2822,7 @@ static ScrStream *scr_stream_new_transformish_dyn(ScrDyn *opts, char base) {
o.allow_half_open, o.readable_side, o.writable_side,
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
return s;
}
@@ -2666,6 +2849,7 @@ void scr_stream_init_readable_dyn(ScrStream *s, ScrDyn *opts,
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
scr_stream_init_readable(s, o.rhwm, o.auto_destroy, o.emit_close,
read, (ScrStreamReadInv)rinv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
}
@@ -2686,6 +2870,7 @@ void scr_stream_init_writable_dyn(ScrStream *s, ScrDyn *opts,
ScrClosure *destroy = scr_stream_dynopt_pick(o.destroy, (void *)&scr_stream_dynopt_destroy_inv, destroy_fb, (void *)destroy_fb_inv, &dinv);
scr_stream_init_writable(s, o.whwm, o.auto_destroy, o.emit_close,
write, (ScrStreamChunkInv)winv, final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
}
void scr_stream_init_duplex_dyn(ScrStream *s, ScrDyn *opts,
@@ -2710,6 +2895,7 @@ void scr_stream_init_duplex_dyn(ScrStream *s, ScrDyn *opts,
o.allow_half_open, o.readable_side, o.writable_side,
read, (ScrStreamReadInv)rinv, write, (ScrStreamChunkInv)winv,
final_cb, (ScrStreamPlainInv)finv, destroy, (ScrStreamErrInv)dinv);
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
}
@@ -2739,6 +2925,7 @@ static void scr_stream_init_transformish_dyn(ScrStream *s, ScrDyn *opts, char ba
transform, (ScrStreamChunkInv)tinv, flush, (ScrStreamPlainInv)linv,
destroy, (ScrStreamErrInv)dinv);
}
+ scr_stream_dynopt_apply_object(s, &o);
scr_stream_dynopt_apply_enc(s, o.enc);
}
@@ -3299,6 +3486,12 @@ static void scr_stream_done_marker_free(void) {
scr_stream_done_marker = NULL;
}
+/* readable.chunkDone: whether a dyn for-await step answered the end (the
+ * done marker; an object-mode stream can yield undefined). */
+bool scr_stream_chunk_done(const ScrDyn *v) {
+ return v != NULL && v == scr_stream_done_marker;
+}
+
ScrDyn *scr_stream_from_done(void) {
if (!scr_stream_done_marker) {
scr_stream_done_marker = scr_dyn_new_obj();
@@ -4067,25 +4260,20 @@ static void scr_compose_read_inv(ScrClosure *cb, ScrStream *s, double size) {
}
/* tail.on('data'): d.push(chunk), pausing the tail when d is full. The
- * chunk is the tail's Buffer, or its string when the tail is encoded. */
-static void scr_compose_data_l(ScrClosure *cb, ScrBytes *bytes, ScrStr *str) {
+ * chunk is the tail's Buffer, its string when the tail is encoded, or an
+ * object-mode value. */
+static void scr_compose_data_l(ScrClosure *cb, ScrBytes *bytes, ScrStr *str, ScrDyn *value) {
ScrCompose *c = scr_box_get_ref(cb->caps[0]);
ScrStream *d = c->d;
if (d) {
ScrStreamState *dst = d->st;
bool more;
if (dst->r.object_entries) {
- int pushed = scr_stream_add_entry(
- d, str ? (void *)scr_str_retain(str) : (void *)scr_bytes_retain(bytes), str != NULL);
- if (pushed < 0) {
- static const char m[] = "a composed stream whose tail mixes strings and Buffers is not supported yet";
- ScrStr *msg = scr_str_new(m, sizeof m - 1);
- ScrError *e = scr_error_new(SCR_ERR_ERROR, msg);
- scr_str_release(msg);
- scr_stream_release(scr_stream_destroy(d, e));
- scr_error_release(e);
- }
- more = pushed > 0;
+ more = value ? scr_stream_push_object(d, value) > 0
+ : scr_stream_add_entry(d, str ? (void *)scr_str_retain(str) : (void *)scr_bytes_retain(bytes),
+ str ? SCR_ENTRY_STR : SCR_ENTRY_BYTES) > 0;
+ } else if (value && !str && !bytes) {
+ more = scr_stream_push_dyn(d, value);
} else if (str) {
more = scr_stream_push_str(d, str);
} else {
@@ -4206,7 +4394,7 @@ ScrStream *scr_stream_compose(double n_d, ScrStream **streams) {
c->tail, scr_compose_closure((void *)&scr_compose_tail_eos, c), &scr_compose_tail_eos));
}
if (c->readable) {
- scr_compose_on(c->tail, "data", &c->data_l, (void *)&scr_compose_data_l, c, scr_ee_inv_fixed2);
+ scr_compose_on(c->tail, "data", &c->data_l, (void *)&scr_compose_data_l, c, scr_ee_inv_fixed3);
scr_compose_on(c->tail, "end", &c->end_l, (void *)&scr_compose_end_l, c, scr_ee_inv_fixed0);
}
static SCR_TL bool registered = false;
@@ -4379,7 +4567,21 @@ static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
/* The 'data' accumulation (the emit ABI's two payload slots — exactly
* one non-NULL). */
-static void scr_sc_data(ScrClosure *cb, void *b, void *str) {
+/* An object-mode value other than a string or a Buffer reaching a
+ * runtime consumer that reads text or bytes (stream/consumers, readline). */
+static void scr_stream_value_consumer_fence(const char *consumer) {
+ char msg[160];
+ int n = snprintf(msg, sizeof msg,
+ "%s over an object-mode stream value that is not a string or Buffer is not supported yet",
+ consumer);
+ scr_throw_error_msg_code(SCR_ERR_ERROR, msg, (size_t)n, "SC2020");
+}
+
+static void scr_sc_data(ScrClosure *cb, void *b, void *str, void *value) {
+ if (b == NULL && str == NULL && value != NULL) {
+ scr_stream_value_consumer_fence("stream/consumers");
+ return;
+ }
ScrArr *chunks = scr_box_get_ref(cb->caps[1]); /* +1 */
if (b != NULL) {
scr_arr_push_ref(chunks, scr_bytes_retain((ScrBytes *)b));
@@ -4452,7 +4654,7 @@ static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
ScrStr *dn = scr_str_new("data", 4);
scr_emitter_release(scr_emitter_on((ScrEmitter *)s, dn,
scr_sc_closure((void *)&scr_sc_data, p, chunks),
- scr_ee_inv_fixed2, false, false));
+ scr_ee_inv_fixed3, false, false));
scr_str_release(dn);
/* resume() rather than the 'data' hook alone: Node's consumer pulls
* through the iterator, which drains a PAUSED stream too. */
@@ -4643,7 +4845,11 @@ static SCR_TL ScrRlHook *scr_rl_hooks = NULL;
static double scr_stream_rl_id(ScrClosure *cb) { return scr_box_get_f64(cb->caps[0]); }
-static void scr_stream_rl_data(ScrClosure *cb, void *b, void *str) {
+static void scr_stream_rl_data(ScrClosure *cb, void *b, void *str, void *value) {
+ if (b == NULL && str == NULL && value != NULL) {
+ scr_stream_value_consumer_fence("readline");
+ return;
+ }
if (b != NULL) scr_rl_input_bytes(scr_stream_rl_id(cb), (ScrBytes *)b);
else if (str != NULL) scr_rl_input_str(scr_stream_rl_id(cb), (ScrStr *)str);
}
@@ -4797,7 +5003,7 @@ double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double
h->next = scr_rl_hooks;
scr_rl_hooks = h;
scr_stream_rl_on(in, "error", h->error, scr_ee_inv_fixed1);
- scr_stream_rl_on(in, "data", h->data, scr_ee_inv_fixed2);
+ scr_stream_rl_on(in, "data", h->data, scr_ee_inv_fixed3);
scr_stream_rl_on(in, "end", h->end, scr_ee_inv_fixed0);
scr_stream_rl_resume(in);
return id;
@@ -5323,6 +5529,85 @@ void scr_console_bind(ScrDyn *console, double which, ScrStream *stream) {
inst->stream_drop = &scr_console_stream_drop;
}
+/* ── object mode ──────────────────────────────────────────────────────── */
+
+/* The objectMode option (both sides) or a Duplex's readableObjectMode and
+ * writableObjectMode, applied right after construction: the readable
+ * side's entries are whole values (each counts 1, and the first push picks
+ * the representation), and the writable side queues values. The caller
+ * passes the object-mode high-water marks (16 by default). */
+void scr_stream_set_object_mode(ScrStream *s, bool readable, bool writable) {
+ ScrStreamState *st = s->st;
+ if (readable && st->has_r) {
+ st->r.object_entries = true;
+ st->r.object_mode_opt = true;
+ }
+ if (writable && st->has_w) st->w.object_mode = true;
+}
+
+/* stream.objectMode: scr_stream_set_object_mode, answering the receiver
+ * (+1). */
+ScrStream *scr_stream_object_mode(ScrStream *s, bool readable, bool writable) {
+ scr_stream_set_object_mode(s, readable, writable);
+ return scr_stream_retain(s);
+}
+
+/* Whether the write and transform steps receive dyn values (an
+ * object-mode writable side): the compiler's chunk thunks read the chunk
+ * by this. */
+bool scr_stream_writes_values(ScrStream *s) {
+ return s->st->has_w && s->st->w.object_mode;
+}
+
+/* emit('data', value) with a checked-dynamic chunk: the 'data' slots by
+ * the value's tag (scr_stream_emit_data's ABI). Borrows its arguments. */
+bool scr_stream_emit_value(ScrStream *s, ScrStr *name, ScrDyn *v) {
+ ScrBytes *b = v->kind == SCR_DYN_BYTES ? v->v.bytes : NULL;
+ ScrStr *str = v->kind == SCR_DYN_STR ? v->v.str : NULL;
+ return scr_emitter_emit((ScrEmitter *)s, name, b, str, v);
+}
+
+/* The chunk a compiled write or transform callback receives, by its
+ * declared type: as a Buffer (+1; on an object-mode side the value must
+ * be a Buffer, else a TypeError is thrown and NULL answered), as a dyn
+ * value (+1), and the encoding argument: 'buffer' on a byte-mode side,
+ * else what the write passed (undefined, null or a name). A parameter
+ * typed string receives the name, or "" for undefined and null; one typed
+ * otherwise receives the value (+1 each). */
+ScrBytes *scr_stream_chunk_bytes(ScrStream *s, ScrBytes *chunk) {
+ if (!scr_stream_writes_values(s)) return scr_bytes_retain(chunk);
+ ScrDyn *v = (ScrDyn *)chunk;
+ if (v->kind == SCR_DYN_BYTES) return scr_bytes_raw_view(v->v.bytes);
+ static const char msg[] =
+ "a stream callback declaring a Buffer chunk received an object-mode value that is not a Buffer";
+ scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1);
+ return NULL;
+}
+
+ScrDyn *scr_stream_chunk_value(ScrStream *s, ScrBytes *chunk) {
+ if (scr_stream_writes_values(s)) return scr_dyn_retain((ScrDyn *)chunk);
+ return scr_dyn_new_buffer(chunk);
+}
+
+ScrStr *scr_stream_chunk_encoding(ScrStream *s) {
+ if (!scr_stream_writes_values(s)) return scr_str_new("buffer", 6);
+ ScrDyn *enc = s->st->w.cur_enc;
+ if (enc && enc->kind == SCR_DYN_STR) return scr_str_retain(enc->v.str);
+ if (enc == NULL || enc->kind == SCR_DYN_NULL) return scr_str_new("", 0);
+ return scr_dyn_string_coerce_js(enc);
+}
+
+ScrDyn *scr_stream_chunk_encoding_value(ScrStream *s) {
+ if (!scr_stream_writes_values(s)) {
+ ScrStr *name = scr_str_new("buffer", 6);
+ ScrDyn *d = scr_dyn_new_str(name);
+ scr_str_release(name);
+ return d;
+ }
+ ScrDyn *enc = s->st->w.cur_enc;
+ return enc ? scr_dyn_retain(enc) : scr_dyn_undefined();
+}
+
/* ── the readable surface ─────────────────────────────────────────────── */
bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
@@ -5330,6 +5615,9 @@ bool scr_stream_push(ScrStream *s, ScrBytes *chunk) {
}
bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
+ /* object mode: the string is one whole entry, not converted */
+ if (s->st->has_r && s->st->r.object_entries)
+ return scr_stream_add_entry(s, scr_str_retain(str), SCR_ENTRY_STR) > 0;
ScrStr *enc = s->st->has_r ? s->st->r.push_enc : NULL;
ScrBytes *b;
if (enc != NULL) {
@@ -5349,6 +5637,8 @@ bool scr_stream_push_str(ScrStream *s, ScrStr *str) {
/* push(chunk, encoding) with an explicit non-utf8 literal: the per-call
* encoding overrides the stream's default. Borrows both strings. */
bool scr_stream_push_str_enc(ScrStream *s, ScrStr *str, ScrStr *enc) {
+ /* object mode: the string is the chunk and the encoding is not used */
+ if (s->st->has_r && s->st->r.object_entries) return scr_stream_push_str(s, str);
ScrBytes *b = scr_stream_buffer(scr_bytes_from_str(str, enc));
bool ret = scr_stream_add_chunk(s, b, false);
scr_bytes_release(b);
@@ -5374,11 +5664,11 @@ bool scr_stream_push_null(ScrStream *s) {
if (!st->r.ended) {
/* onEofChunk flushes the decoder's held partial first (utf8's
* replacement chars et al — Node's decoder.end() push). */
- if (st->r.encoded) {
+ if (st->r.encoded && !st->r.dyn_entries) {
ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
st->r.dec_pending = 0;
if (scr_str_utf16_len(tail) > 0) {
- scr_stream_rbuf_push(st, tail, true, false);
+ scr_stream_rbuf_push(st, tail, SCR_ENTRY_STR, false);
if (st->r.need_readable) scr_stream_emit_readable_nt(s);
} else {
scr_str_release(tail);
@@ -5408,12 +5698,19 @@ void scr_stream_unshift(ScrStream *s, ScrBytes *chunk) {
if (st->r.head_off > 0) {
/* materialize the part-read head so the front slot is whole */
void *rest = scr_stream_rbuf_take(st, scr_stream_entry_len(st, st->r.buf[0]) - st->r.head_off);
- scr_stream_rbuf_push(st, rest, st->r.encoded, true);
+ scr_stream_rbuf_push(st, rest, scr_stream_rkind(st), true);
}
scr_stream_add_chunk(s, chunk, true);
}
void scr_stream_unshift_str(ScrStream *s, ScrStr *str) {
+ if (s->st->has_r && s->st->r.object_entries) {
+ /* object mode: the string itself goes to the front */
+ ScrDyn *v = scr_dyn_new_str(str);
+ scr_stream_unshift_dyn(s, v);
+ scr_dyn_release(v);
+ return;
+ }
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
memcpy(b->data, str->data, str->len);
if (!str->ascii) scr_wtf8_to_utf8((char *)b->data, str->len);
@@ -5422,6 +5719,46 @@ void scr_stream_unshift_str(ScrStream *s, ScrStr *str) {
scr_bytes_release(b);
}
+/* unshift(value): null is the end of the stream (Node's onEofChunk); an
+ * object-mode stream takes any other value as one entry at the front; a
+ * byte-mode stream takes Buffers and strings, ignores undefined and
+ * reports another value as push does. Borrows d. */
+void scr_stream_unshift_dyn(ScrStream *s, const ScrDyn *d) {
+ ScrStreamState *st = s->st;
+ if (d->kind == SCR_DYN_NULL) {
+ scr_stream_push_null(s);
+ return;
+ }
+ if (st->has_r && st->r.object_entries) {
+ void *entry;
+ ScrEntryKind kind;
+ if (d->kind == SCR_DYN_STR) {
+ entry = scr_str_retain(d->v.str);
+ kind = SCR_ENTRY_STR;
+ } else if (d->kind == SCR_DYN_BYTES) {
+ entry = scr_bytes_raw_view(d->v.bytes);
+ kind = SCR_ENTRY_BYTES;
+ } else {
+ entry = scr_dyn_retain((ScrDyn *)d);
+ kind = SCR_ENTRY_DYN;
+ }
+ entry = scr_stream_entry_convert(st, entry, &kind);
+ scr_stream_rbuf_push(st, entry, kind, true);
+ if (st->r.need_readable) scr_stream_emit_readable_nt(s);
+ scr_stream_settle_next(s);
+ return;
+ }
+ if (d->kind == SCR_DYN_BYTES) {
+ ScrBytes *b = scr_bytes_raw_view(d->v.bytes);
+ scr_stream_unshift(s, b);
+ scr_bytes_release(b);
+ } else if (d->kind == SCR_DYN_STR) {
+ scr_stream_unshift_str(s, d->v.str);
+ } else {
+ scr_stream_push_dyn(s, d);
+ }
+}
+
static void scr_stream_listener_error(ScrClosure *adapter, ScrError *error) {
ScrDyn *fn = scr_dyn_listener_fn(adapter);
ScrDyn *arg = scr_dyn_from_error(error);
@@ -5431,9 +5768,9 @@ static void scr_stream_listener_error(ScrClosure *adapter, ScrError *error) {
scr_dyn_release(fn);
}
-static void scr_stream_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *string) {
+static void scr_stream_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *string, ScrDyn *value) {
ScrDyn *fn = scr_dyn_listener_fn(adapter);
- ScrDyn *arg = string ? scr_dyn_new_str(string) : scr_dyn_new_bytes(bytes);
+ ScrDyn *arg = value ? scr_dyn_retain(value) : string ? scr_dyn_new_str(string) : scr_dyn_new_bytes(bytes);
ScrDyn *answer = scr_dyn_call(fn, &arg, 1, "listener");
scr_dyn_release(answer);
scr_dyn_release(arg);
@@ -5444,7 +5781,7 @@ ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once,
if (name->len == 4 && !memcmp(name->data, "data", 4)) {
return (ScrStream *)scr_emitter_on_dyn((ScrEmitter *)s, name, cb,
scr_dyn_listener_closure_fn(cb, (void *)scr_stream_listener_data),
- scr_ee_inv_fixed2, once, prepend);
+ scr_ee_inv_fixed3, once, prepend);
}
bool error = name->len == 5 && memcmp(name->data, "error", 5) == 0;
bool empty = (name->len == 8 && memcmp(name->data, "readable", 8) == 0) ||
@@ -5468,13 +5805,15 @@ ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once,
ScrDyn *scr_stream_read_dyn(ScrStream *s, ScrDyn *size) {
double n = size && size->kind != SCR_DYN_UNDEF ? scr_dyn_number_coerce(size) : -1;
if (scr_exc_pending()) return scr_dyn_undefined();
- bool is_str = false;
- void *chunk = s->st->has_r ? scr_stream_read_n_kind(s, n, &is_str) : NULL;
+ ScrEntryKind kind = SCR_ENTRY_BYTES;
+ void *chunk = s->st->has_r ? scr_stream_read_n_kind(s, n, &kind) : NULL;
if (!chunk) return scr_dyn_new_null();
- ScrDyn *result = is_str ? scr_dyn_new_str(chunk) : scr_dyn_new_buffer(chunk);
- if (is_str) scr_str_release(chunk);
- else scr_bytes_release(chunk);
- return result;
+ if (kind == SCR_ENTRY_BYTES) {
+ ScrDyn *result = scr_dyn_new_buffer(chunk);
+ scr_bytes_release(chunk);
+ return result;
+ }
+ return scr_stream_entry_box(chunk, kind);
}
/* read() answers STRINGS once encoded: the static result type here is
@@ -5491,13 +5830,28 @@ ScrBytes *scr_stream_read(ScrStream *s, double size) {
/* a destroyed readable still DRAINS its buffer (Node: destroy leaves
* buffered chunks readable; only _read refills are blocked) */
if (!s->st->has_r) return NULL;
- if (s->st->r.encoded) {
+ if (s->st->r.encoded && !s->st->r.dyn_entries) {
scr_stream_read_str_fence();
return NULL;
}
- bool is_str;
- void *chunk = scr_stream_read_n_kind(s, size, &is_str);
- if (chunk && is_str) {
+ ScrEntryKind kind;
+ void *chunk = scr_stream_read_n_kind(s, size, &kind);
+ if (chunk && kind == SCR_ENTRY_DYN) {
+ /* an object-mode value: a Buffer is the result, anything else is not */
+ ScrDyn *d = chunk;
+ if (d->kind == SCR_DYN_BYTES) {
+ ScrBytes *b = scr_bytes_raw_view(d->v.bytes);
+ scr_dyn_release(d);
+ return b;
+ }
+ scr_dyn_release(d);
+ static const char msg[] =
+ "read() of an object-mode stream value that is not a Buffer is not supported yet in typed "
+ "code (consume 'data' events or for await)";
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
+ return NULL;
+ }
+ if (chunk && kind == SCR_ENTRY_STR) {
/* the read itself made the stream hold strings (a lazy Readable.from
* pull's first string, or setEncoding() inside _read): the same
* fence, never a string in the Buffer result */
@@ -5515,11 +5869,19 @@ ScrBytes *scr_stream_read(ScrStream *s, double size) {
ScrStream *scr_stream_set_encoding(ScrStream *s, ScrStr *enc) {
ScrStreamState *st = s->st;
if (!st->has_r) return scr_stream_retain(s);
+ if (st->r.dyn_entries) {
+ /* an object-mode buffer of values: Buffers pushed later decode */
+ if (st->r.enc) scr_str_release(st->r.enc);
+ st->r.enc = scr_str_retain(enc);
+ st->r.dec_pending = 0;
+ st->r.decoding = true;
+ return scr_stream_retain(s);
+ }
if (st->r.encoded) {
/* switching encodings mid-stream: fold the old decoder's pending
* tail into the buffer first (rare; Node re-creates the decoder) */
ScrStr *tail = scr_strdec_end(st->r.enc, st->r.dec_pending);
- if (scr_str_utf16_len(tail) > 0) scr_stream_rbuf_push(st, tail, true, false);
+ if (scr_str_utf16_len(tail) > 0) scr_stream_rbuf_push(st, tail, SCR_ENTRY_STR, false);
else scr_str_release(tail);
scr_str_release(st->r.enc);
st->r.enc = scr_str_retain(enc);
@@ -5575,7 +5937,7 @@ ScrStream *scr_stream_set_encoding(ScrStream *s, ScrStr *enc) {
st->r.decoding = true;
st->r.object_kind_set = true;
if (content != NULL) {
- if (scr_str_utf16_len(content) > 0) scr_stream_rbuf_push(st, content, true, false);
+ if (scr_str_utf16_len(content) > 0) scr_stream_rbuf_push(st, content, SCR_ENTRY_STR, false);
else scr_str_release(content);
}
return scr_stream_retain(s);
@@ -5647,7 +6009,8 @@ ScrStream *scr_stream_unpipe(ScrStream *src, ScrStream *dst) {
/* ── the writable surface ─────────────────────────────────────────────── */
-static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, ScrClosure *cb /*moves*/) {
+static bool scr_stream_write_chunk_enc(ScrStream *s, ScrBytes *chunk /*borrowed*/,
+ ScrClosure *cb /*moves*/, ScrDyn *enc /*borrowed*/) {
ScrStreamState *st = s->st;
if (!st->has_w || st->destroyed || st->w.ending) {
ScrError *e = st->has_w && !st->destroyed
@@ -5658,12 +6021,12 @@ static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, S
scr_error_release(e);
return false;
}
- size_t len = chunk->len;
- st->w.length += len;
+ st->w.length += scr_stream_wchunk_len(st, chunk);
+ if (enc) enc = scr_dyn_retain(enc);
if (st->w.writing || st->w.corked > 0 || st->constructing) {
- scr_stream_wq_push(st, scr_bytes_retain(chunk), cb);
+ scr_stream_wq_push(st, scr_stream_wchunk_retain(st, chunk), cb, enc);
} else {
- scr_stream_do_write(s, scr_bytes_retain(chunk), cb);
+ scr_stream_do_write(s, scr_stream_wchunk_retain(st, chunk), cb, enc);
}
/* Node computes the answer AFTER the (possibly synchronous) write:
* a sync completion has already drained its bytes from length. */
@@ -5672,7 +6035,28 @@ static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, S
return ret;
}
+static bool scr_stream_write_chunk(ScrStream *s, ScrBytes *chunk /*borrowed*/, ScrClosure *cb /*moves*/) {
+ return scr_stream_write_chunk_enc(s, chunk, cb, NULL);
+}
+
+/* The encoding argument of a compiled write(chunk[, cb]) on an object-mode
+ * side: undefined, or null when the callback took its place (Node's
+ * write and end shift it there). */
+static ScrDyn *scr_stream_cb_enc(ScrClosure *cb) { return cb ? scr_dyn_new_null() : NULL; }
+
+/* write(value) on an object-mode writable side: the value is the chunk.
+ * Borrows v. */
+static bool scr_stream_write_value(ScrStream *s, ScrDyn *v, ScrClosure *cb) {
+ return scr_stream_write_chunk_enc(s, (ScrBytes *)v, cb, scr_stream_cb_enc(cb));
+}
+
bool scr_stream_write(ScrStream *s, ScrBytes *chunk, ScrClosure *cb) {
+ if (s->st->has_w && s->st->w.object_mode) {
+ ScrDyn *v = chunk->is_buffer ? scr_dyn_new_buffer(chunk) : scr_dyn_new_bytes(chunk);
+ bool ret = scr_stream_write_value(s, v, cb);
+ scr_dyn_release(v);
+ return ret;
+ }
return scr_stream_write_chunk(s, chunk, cb);
}
@@ -5686,6 +6070,13 @@ bool scr_stream_write_null(ScrStream *s) {
}
bool scr_stream_write_str(ScrStream *s, ScrStr *str, ScrClosure *cb) {
+ if (s->st->has_w && s->st->w.object_mode) {
+ /* object mode: the string itself, not converted */
+ ScrDyn *v = scr_dyn_new_str(str);
+ bool ret = scr_stream_write_value(s, v, cb);
+ scr_dyn_release(v);
+ return ret;
+ }
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)str->len);
memcpy(b->data, str->data, str->len);
if (!str->ascii) scr_wtf8_to_utf8((char *)b->data, str->len);
@@ -5695,10 +6086,65 @@ bool scr_stream_write_str(ScrStream *s, ScrStr *str, ScrClosure *cb) {
return ret;
}
+/* write(chunk, encoding, cb) with the encoding argument as passed (NULL
+ * or undefined when absent): an object-mode side hands it to _write
+ * unchanged; a byte-mode side encodes a string chunk with it. Borrows
+ * chunk and enc. */
+bool scr_stream_write_enc(ScrStream *s, const ScrDyn *chunk, ScrDyn *enc, ScrClosure *cb) {
+ if (enc && enc->kind == SCR_DYN_UNDEF) enc = NULL;
+ ScrStreamState *st = s->st;
+ if (chunk->kind != SCR_DYN_NULL && st->has_w && st->w.object_mode)
+ return scr_stream_write_chunk_enc(s, (ScrBytes *)chunk, cb, enc);
+ /* Node's _write: an encoding other than 'buffer' must be a known one
+ * (ERR_UNKNOWN_ENCODING, thrown), and a string chunk is encoded with
+ * it; an empty or nullish encoding is the default */
+ ScrStr *name = NULL;
+ if (enc && enc->kind != SCR_DYN_NULL && !(enc->kind == SCR_DYN_STR && enc->v.str->len == 0))
+ name = scr_dyn_string_coerce_js(enc);
+ if (name && !(name->len == 6 && memcmp(name->data, "buffer", 6) == 0)) {
+ ScrStr *canonical = scr_bytes_normalize_encoding(name);
+ scr_str_release(name);
+ if (canonical == NULL) {
+ if (cb) scr_closure_release(cb);
+ return false;
+ }
+ if (chunk->kind != SCR_DYN_STR) {
+ scr_str_release(canonical);
+ return scr_stream_write_dyn(s, chunk, cb);
+ }
+ ScrBytes *b = scr_bytes_from_str(chunk->v.str, canonical);
+ scr_str_release(canonical);
+ scr_stream_buffer(b);
+ bool ret = scr_stream_write_chunk(s, b, cb);
+ scr_bytes_release(b);
+ return ret;
+ }
+ if (name && chunk->kind == SCR_DYN_STR) {
+ /* Buffer.from(string, 'buffer') */
+ scr_str_release(name);
+ if (cb) scr_closure_release(cb);
+ static const char msg[] = "Unknown encoding: buffer";
+ scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_UNKNOWN_ENCODING");
+ return false;
+ }
+ if (name) scr_str_release(name);
+ return scr_stream_write_dyn(s, chunk, cb);
+}
+
ScrStream *scr_stream_end(ScrStream *s, ScrBytes *chunk_b, ScrStr *chunk_s, ScrClosure *cb) {
ScrStreamState *st = s->st;
- if (chunk_b) scr_stream_write(s, chunk_b, NULL);
- else if (chunk_s) scr_stream_write_str(s, chunk_s, NULL);
+ if ((chunk_b || chunk_s) && st->has_w && st->w.object_mode) {
+ /* end(chunk[, cb]) writes with the encoding Node's end leaves:
+ * undefined, or null when the callback took its place */
+ ScrDyn *v = chunk_s ? scr_dyn_new_str(chunk_s)
+ : chunk_b->is_buffer ? scr_dyn_new_buffer(chunk_b) : scr_dyn_new_bytes(chunk_b);
+ scr_stream_write_chunk_enc(s, (ScrBytes *)v, NULL, scr_stream_cb_enc(cb));
+ scr_dyn_release(v);
+ } else if (chunk_b) {
+ scr_stream_write(s, chunk_b, NULL);
+ } else if (chunk_s) {
+ scr_stream_write_str(s, chunk_s, NULL);
+ }
if (!st->has_w || st->destroyed) {
if (cb) scr_closure_release(cb);
return scr_stream_retain(s);
@@ -5778,7 +6224,12 @@ void scr_stream_write_done(ScrStream *s, ScrError *err /*moves*/) {
return;
}
if (was_sync) {
+ bool hold = ucb != NULL || (st->w.need_drain && st->w.length == 0) || st->destroyed;
scr_st_tick(s, SCR_ST_AFTER_WRITE, NULL, ucb);
+ if (hold) {
+ scr_st_tail->hold = true;
+ st->w.after_holds++;
+ }
} else {
scr_stream_after_write(s, ucb);
}
@@ -5837,6 +6288,8 @@ void scr_stream_transform_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *
if (!err) {
if (data) {
scr_stream_add_chunk(s, data, false);
+ } else if (data_str && s->st->r.object_entries) {
+ scr_stream_push_str(s, data_str); /* object mode: the string itself */
} else if (data_str) {
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
memcpy(b->data, data_str->data, data_str->len);
@@ -5870,6 +6323,9 @@ void scr_stream_flush_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data
if (data) {
scr_stream_add_chunk(s, data, false);
scr_bytes_release(data);
+ } else if (data_str && st->r.object_entries) {
+ scr_stream_push_str(s, data_str); /* object mode: the string itself */
+ scr_str_release(data_str);
} else if (data_str) {
ScrBytes *b = scr_bytes_new(SCR_BYTES_U8, (double)data_str->len);
memcpy(b->data, data_str->data, data_str->len);
@@ -5885,6 +6341,38 @@ void scr_stream_flush_done(ScrStream *s, ScrError *err /*moves*/, ScrBytes *data
scr_stream_finish_maybe(s);
}
+/* The transform and flush completions with a checked-dynamic value (an
+ * object-mode Transform's data, or an untyped callback's): null and
+ * undefined push nothing (Transform's `val != null`), and any other value
+ * is pushed as push(value) pushes it. v borrowed (NULL: absent). */
+static bool scr_stream_done_pushes(const ScrDyn *v) {
+ return v && v->kind != SCR_DYN_NULL && v->kind != SCR_DYN_UNDEF;
+}
+
+void scr_stream_transform_done_value(ScrStream *s, ScrError *err /*moves*/, ScrDyn *v) {
+ if (!err && scr_stream_done_pushes(v)) scr_stream_push_dyn(s, v);
+ if (scr_exc_pending()) {
+ if (err) scr_error_release(err);
+ return;
+ }
+ if (err) scr_stream_write_done(s, err);
+ else scr_stream_transform_callback(s);
+}
+
+void scr_stream_flush_done_value(ScrStream *s, ScrError *err /*moves*/, ScrDyn *v) {
+ s->st->flush_pending = false;
+ if (err) {
+ scr_stream_error_or_destroy(s, err);
+ scr_error_release(err);
+ return;
+ }
+ if (scr_stream_done_pushes(v)) scr_stream_push_dyn(s, v);
+ if (scr_exc_pending()) return;
+ scr_stream_push_null(s);
+ if (scr_exc_pending()) return;
+ scr_stream_finish_maybe(s);
+}
+
/* ── destroy ──────────────────────────────────────────────────────────── */
/* Node's _destroy step: the destroy hook, or 'error'/'close' on ticks. */
@@ -5970,6 +6458,7 @@ double scr_stream_prop(ScrStream *s, const char *name) {
if (strcmp(name, "readableLength") == 0) return (double)st->r.length;
if (strcmp(name, "readableHighWaterMark") == 0) return (double)st->r.hwm;
if (strcmp(name, "readableObjectMode") == 0) return st->r.object_entries;
+ if (strcmp(name, "writableObjectMode") == 0) return st->w.object_mode;
if (strcmp(name, "writable") == 0) {
return st->has_w && !st->destroyed && !st->w.ending && !st->errored;
}
@@ -6011,7 +6500,7 @@ double scr_stream_prop(ScrStream *s, const char *name) {
if (strcmp(p, "destroyed") == 0) return st->destroyed;
if (strcmp(p, "errorEmitted") == 0) return st->error_emitted;
if (strcmp(p, "needDrain") == 0) return st->w.need_drain;
- if (strcmp(p, "objectMode") == 0) return 0;
+ if (strcmp(p, "objectMode") == 0) return st->w.object_mode;
if (strcmp(p, "constructed") == 0) return 1;
if (strcmp(p, "closed") == 0) return st->close_emitted;
if (strcmp(p, "length") == 0) return (double)st->w.length;
@@ -6209,6 +6698,7 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
break;
case SCR_ST_AFTER_WRITE: {
st->w.after_scheduled = false;
+ if (t->hold) st->w.after_holds--;
ScrClosure *cb = t->cb;
t->cb = NULL;
if (!st->destroyed) {
@@ -6272,6 +6762,13 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
if (!st->close_emitted) {
st->close_emitted = true;
if (st->emit_close) scr_stream_emit0(s, "close");
+ if (st->r.next_eof_close && !scr_exc_pending()) {
+ /* the iterator's eos 'close' listener completes the loop */
+ st->r.next_eof_close = false;
+ ScrPromise *w = st->r.next_waiter;
+ st->r.next_waiter = NULL;
+ if (w) scr_stream_next_fulfill(st, w, NULL, SCR_ENTRY_BYTES);
+ }
/* eos ordering: the finished()/pipeline watchers fire right after
* 'close' (Node's willEmitClose stance). */
scr_stream_notify_finished(s);
@@ -6318,9 +6815,9 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
}
case SCR_ST_READ0: {
if (st->has_r) {
- bool is_str;
- void *chunk = scr_stream_read_n_kind(s, 0, &is_str);
- if (chunk) scr_stream_entry_release_kind(chunk, is_str);
+ ScrEntryKind kind;
+ void *chunk = scr_stream_read_n_kind(s, 0, &kind);
+ if (chunk) scr_stream_entry_release_kind(chunk, kind);
}
break;
}
@@ -6347,10 +6844,13 @@ static void scr_stream_run_tick(ScrStreamTick *t) {
break;
}
case SCR_ST_NEXT_EOF: {
- if (st->r.next_waiter && st->r.length == 0 && (st->r.ended || st->destroyed)) {
+ if (st->r.next_waiter && st->r.length == 0 && st->r.ended && st->r.next_close_wait &&
+ !st->close_emitted) {
+ st->r.next_eof_close = true;
+ } else if (st->r.next_waiter && st->r.length == 0 && (st->r.ended || st->destroyed)) {
ScrPromise *w = st->r.next_waiter;
st->r.next_waiter = NULL;
- scr_stream_next_fulfill(st, w, NULL, false);
+ scr_stream_next_fulfill(st, w, NULL, SCR_ENTRY_BYTES);
} else if (st->r.next_waiter) {
scr_stream_settle_next(s); /* content raced in: deliver it */
}
@@ -6475,16 +6975,6 @@ static const ScrDynHandleTransform *scr_handle_step_ops(ScrClosure *cb) {
return scr_handle_step_handle_ops(cb)->transform;
}
-/* The pending exception as a stream error (+1). */
-static ScrError *scr_handle_step_caught(void) {
- ScrCaught *caught = scr_exc_take();
- ScrDyn *value = scr_caught_to_dyn(caught);
- scr_caught_release(caught);
- ScrError *err = value ? scr_errdyn_err_of(value) : NULL;
- if (value) scr_dyn_release(value);
- return err ? err : scr_stream_mkerr("ERR_INTERNAL_ASSERTION", "a Transform handle threw a value that is not an Error");
-}
-
static void scr_handle_step_transform(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) {
/* A throwing update() leaves the exception pending: it throws out of
* the write, as Node's _transform does. */
@@ -6494,7 +6984,7 @@ static void scr_handle_step_transform(ScrClosure *cb, ScrStream *s, ScrBytes *ch
static void scr_handle_step_flush(ScrClosure *cb, ScrStream *s) {
ScrBytes *out = scr_handle_step_ops(cb)->final(scr_handle_step_handle(cb));
- scr_stream_flush_done(s, out ? NULL : scr_handle_step_caught(), out, NULL);
+ scr_stream_flush_done(s, out ? NULL : scr_stream_take_error(), out, NULL);
}
static void scr_handle_step_destroy(ScrClosure *cb, ScrStream *s, ScrError *err) {
diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h
index 1e5b789e7..8b8a72406 100644
--- a/packages/runtime/src/scr_stream.h
+++ b/packages/runtime/src/scr_stream.h
@@ -272,6 +272,7 @@ ScrStream *scr_stream_set_push_encoding(ScrStream *s, ScrStr *enc);
bool scr_stream_push_null(ScrStream *s);
void scr_stream_unshift(ScrStream *s, ScrBytes *chunk);
void scr_stream_unshift_str(ScrStream *s, ScrStr *str);
+void scr_stream_unshift_dyn(ScrStream *s, const ScrDyn *d);
ScrBytes *scr_stream_read(ScrStream *s, double size);
ScrStream *scr_stream_on_dyn(ScrStream *s, ScrStr *name, ScrDyn *cb, bool once, bool prepend);
ScrDyn *scr_stream_read_dyn(ScrStream *s, ScrDyn *size); /* +1 or NULL; size < 0 = all/first; throws once encoded (the static type is Buffer|null) */
@@ -291,6 +292,8 @@ typedef struct ScrPromise ScrPromise; /* full declaration below (C11 repeat) */
* each chunk before asking again). */
bool scr_stream_push_dyn(ScrStream *s, const ScrDyn *d); /* borrows d; tag dispatch */
bool scr_stream_write_dyn(ScrStream *s, const ScrDyn *d, ScrClosure *cb); /* cb moves */
+/* write(chunk, encoding, cb): enc borrowed, NULL or undefined when absent */
+bool scr_stream_write_enc(ScrStream *s, const ScrDyn *chunk, ScrDyn *enc, ScrClosure *cb);
ScrPromise *scr_stream_next_chunk(ScrStream *s);
ScrPromise *scr_stream_next_chunk_dyn(ScrStream *s);
/* node:stream/promises — the promise forms over the finished/pipeline
@@ -475,6 +478,28 @@ void scr_stream_final_done(ScrStream *s, ScrError *err);
void scr_stream_destroy_done(ScrStream *s, ScrError *err);
void scr_stream_transform_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
void scr_stream_flush_done(ScrStream *s, ScrError *err, ScrBytes *data, ScrStr *data_str);
+/* The same completions with a checked-dynamic data value (borrowed; NULL,
+ * null and undefined push nothing): an object-mode Transform's data, or an
+ * untyped callback's. err moves. */
+/* objectMode: scr_stream_set_object_mode applies the option's sides right
+ * after construction; scr_stream_writes_values answers whether the write
+ * and transform steps receive dyn values instead of Buffers. */
+void scr_stream_set_object_mode(ScrStream *s, bool readable, bool writable);
+ScrStream *scr_stream_object_mode(ScrStream *s, bool readable, bool writable);
+bool scr_stream_writes_values(ScrStream *s);
+/* A compiled write or transform callback's chunk by its declared type: a
+ * Buffer (+1, or NULL with a TypeError for another object-mode value), a
+ * dyn value (+1), and the encoding argument (+1). */
+ScrBytes *scr_stream_chunk_bytes(ScrStream *s, ScrBytes *chunk);
+struct ScrDyn *scr_stream_chunk_value(ScrStream *s, ScrBytes *chunk);
+ScrStr *scr_stream_chunk_encoding(ScrStream *s);
+ScrDyn *scr_stream_chunk_encoding_value(ScrStream *s);
+/* emit('data', value) with a checked-dynamic chunk (borrowed). */
+bool scr_stream_emit_value(ScrStream *s, ScrStr *name, struct ScrDyn *v);
+/* Whether a dyn for-await step (readable.nextChunkDyn) answered the end. */
+bool scr_stream_chunk_done(const struct ScrDyn *v);
+void scr_stream_transform_done_value(ScrStream *s, ScrError *err, struct ScrDyn *v);
+void scr_stream_flush_done_value(ScrStream *s, ScrError *err, struct ScrDyn *v);
/* Runtime-backed streams (the fs streams in scr_fs_stream.c).
* - construct: Node's _construct. The hook runs on a tick after
diff --git a/packages/runtime/src/scr_stream_ops.c b/packages/runtime/src/scr_stream_ops.c
index d32dd5d67..8fd220168 100644
--- a/packages/runtime/src/scr_stream_ops.c
+++ b/packages/runtime/src/scr_stream_ops.c
@@ -305,7 +305,7 @@ static ScrPromise *src_next(OpSrc *src) {
}
static bool src_done(const OpSrc *src, const ScrDyn *v) {
- return src->s ? v->kind == SCR_DYN_UNDEF : v == op_k_done;
+ return src->s ? scr_stream_chunk_done(v) : v == op_k_done;
}
/* The Readable iterator's return(): the iterator generator resumes at its
@@ -1492,16 +1492,6 @@ static void op_from_step_k(void *self, ScrDyn *v) {
}
int pushed = scr_stream_push_object(out, v);
scr_dyn_release(v);
- if (pushed == -2) {
- op_from_destroy_with(out, op_unsupported(
- "a stream operator output that mixes strings and Buffers"));
- return;
- }
- if (pushed < 0) {
- op_from_destroy_with(out, op_unsupported(
- "a stream operator value that is not a string or Buffer (object-mode streams of other values)"));
- return;
- }
if (pushed) {
from_next(f, out);
return;
diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c
index b4d125cb2..e7436ab69 100644
--- a/packages/runtime/src/scr_tls.c
+++ b/packages/runtime/src/scr_tls.c
@@ -368,6 +368,10 @@ typedef struct ScrTlsSrv {
/* maxVersion TLSv1.2: the ClientHello is pre-parsed so a hello that
* offers only later versions gets OpenSSL's protocol_version alert. */
bool max_tls12;
+ /* new tls.TLSSocket(socket, { secureContext }): the context whose
+ * certificate and key this config serves (+1; NULL when cert/key are the
+ * config's own) */
+ ScrSecureCtx *ctx;
} ScrTlsSrv;
/* The ALPN answers (static storage — mbedTLS keeps the pointers). Plain
@@ -405,6 +409,28 @@ static ScrTlsSrv *scr_tls_srv_new(const char *cert, size_t cert_len, const char
return s;
}
+/* A server config that serves a SecureContext's certificate and key. */
+static ScrTlsSrv *scr_tls_srv_from_ctx(ScrSecureCtx *ctx /*borrowed*/) {
+ scr_tls_rng_init();
+ ScrTlsSrv *s = calloc(1, sizeof *s);
+ if (!s) scr_tls_oom();
+ s->rc = 1;
+ mbedtls_x509_crt_init(&s->cert);
+ mbedtls_pk_init(&s->pk);
+ mbedtls_ssl_config_init(&s->conf);
+ if (mbedtls_ssl_config_defaults(&s->conf, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT) != 0) {
+ scr_tls_oom();
+ }
+ mbedtls_ssl_conf_rng(&s->conf, mbedtls_ctr_drbg_random, &scr_tls_drbg);
+ mbedtls_ssl_conf_authmode(&s->conf, MBEDTLS_SSL_VERIFY_NONE);
+ s->ctx = scr_secure_ctx_retain(ctx);
+ if (mbedtls_ssl_conf_own_cert(&s->conf, &ctx->cert, &ctx->pk) != 0) {
+ scr_tls_bad_material("certificate/key pair", 0);
+ }
+ return s;
+}
+
static ScrTlsSrv *scr_tls_srv_retain(ScrTlsSrv *s) {
s->rc++;
return s;
@@ -419,6 +445,7 @@ static void scr_tls_srv_release(ScrTlsSrv *s) {
mbedtls_x509_crt_free(&s->cert);
mbedtls_pk_free(&s->pk);
scr_tls_alpn_free(s->alpn);
+ scr_secure_ctx_release(s->ctx);
free(s);
}
@@ -629,6 +656,15 @@ typedef struct ScrTlsSock {
unsigned char *hello; /* the raw ClientHello flight, replayed at DONE */
size_t hello_len, hello_cap;
ScrSecureCtx *sni_ctx; /* owned; the answer (NULL = default cert) */
+ /* new tls.TLSSocket(socket, { isServer: true }): the program holds this
+ * socket before the handshake, so a failed handshake is its 'error'
+ * (tls.createServer's sockets fail without one) */
+ bool wrapped;
+ unsigned char head[5]; /* the first bytes the peer sent (the error's wording) */
+ size_t head_len;
+ /* the 'secure' event's listeners */
+ ScrNetLs secure_ls;
+ bool settled; /* the socket closed: listeners added later never fire */
} ScrTlsSock;
/* checkServerIdentity for an IP literal: the address must be one of the
@@ -703,6 +739,16 @@ static int scr_tls_bio_send(void *ctx, const unsigned char *buf, size_t len) {
return MBEDTLS_ERR_NET_SEND_FAILED;
}
+/* Keeps the first bytes a wrapped socket's peer sends: a handshake that
+ * fails on them reports what they looked like, as OpenSSL does. */
+static void scr_tls_note_head(ScrTlsSock *t, const unsigned char *buf, size_t n) {
+ if (!t->wrapped || t->head_len == sizeof t->head) return;
+ size_t take = sizeof t->head - t->head_len;
+ if (take > n) take = n;
+ memcpy(t->head + t->head_len, buf, take);
+ t->head_len += take;
+}
+
static int scr_tls_bio_recv(void *ctx, unsigned char *buf, size_t len) {
ScrTlsSock *t = (ScrTlsSock *)ctx;
int fd = scr_net_sock_fd(t->sock);
@@ -710,8 +756,12 @@ static int scr_tls_bio_recv(void *ctx, unsigned char *buf, size_t len) {
/* bytes a consumer peeked and unshifted (the demux path) re-enter the
* stream here, ahead of the kernel's */
size_t took = scr_net_sock_take_buffered(t->sock, (char *)buf, len);
- if (took > 0) return (int)took;
+ if (took > 0) {
+ scr_tls_note_head(t, buf, took);
+ return (int)took;
+ }
ssize_t n = read(fd, buf, len);
+ if (n > 0) scr_tls_note_head(t, buf, (size_t)n);
if (n >= 0) return (int)n; /* 0 = EOF; mbedTLS maps it to CONN_EOF */
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
return MBEDTLS_ERR_SSL_WANT_READ;
@@ -1026,6 +1076,43 @@ void scr_tls_sni_answer(ScrClosure *self, bool has_err, ScrSecureCtx *ctx /*move
scr_net_sock_release(sock);
}
+/* A wrapped server socket's failed handshake, reported as Node reports it
+ * on the TLSSocket: a peer that closed first ends the stream, a reset is
+ * ECONNRESET, and bytes that are not a TLS record name what they were
+ * (OpenSSL's reasons without the per-process prefix and source location,
+ * SEMANTICS.md divergence 55). */
+static void scr_tls_wrapped_failure(ScrTlsSock *t, int r) {
+ if (r == MBEDTLS_ERR_SSL_CONN_EOF) {
+ scr_net_sock_transport_eof(t->sock);
+ return;
+ }
+ if (r == MBEDTLS_ERR_NET_CONN_RESET) {
+ scr_net_sock_transport_error(t->sock, "read ECONNRESET");
+ return;
+ }
+ const unsigned char *h = t->head;
+ bool record = t->head_len >= 3 && h[0] >= 20 && h[0] <= 23 && h[1] == 3;
+ if (t->head_len >= 5 && !record) {
+ static const char *const http[] = { "GET ", "POST ", "HEAD ", "PATCH", "OPTIO",
+ "DELET", "TRACE", "PUT ", NULL };
+ const char *reason = "error:0A00010B:SSL routines:tls_validate_record_header:wrong version number";
+ const char *code = "ERR_SSL_WRONG_VERSION_NUMBER";
+ for (size_t i = 0; http[i] != NULL; i++) {
+ if (memcmp(h, http[i], strlen(http[i])) == 0) {
+ reason = "error:0A00009C:SSL routines:tls_validate_record_header:http request";
+ code = "ERR_SSL_HTTP_REQUEST";
+ }
+ }
+ if (memcmp(h, "CONNE", 5) == 0) {
+ reason = "error:0A00009B:SSL routines:tls_validate_record_header:https proxy request";
+ code = "ERR_SSL_HTTPS_PROXY_REQUEST";
+ }
+ scr_net_sock_transport_error_code(t->sock, reason, code);
+ return;
+ }
+ scr_net_sock_transport_error(t->sock, "server TLS handshake failed");
+}
+
static int scr_tls_handshake(void *tctx) {
ScrTlsSock *t = (ScrTlsSock *)tctx;
if (t->cli != NULL && t->cli->no_protocols) {
@@ -1046,6 +1133,10 @@ static int scr_tls_handshake(void *tctx) {
scr_tls_current = NULL;
if (r == 0) return 1;
if (r == MBEDTLS_ERR_SSL_WANT_READ || r == MBEDTLS_ERR_SSL_WANT_WRITE) return 0;
+ if (t->srv != NULL && t->wrapped) {
+ scr_tls_wrapped_failure(t, r);
+ return -1;
+ }
if (t->srv != NULL) {
/* Node's server default: 'tlsClientError' with no listener destroys
* the socket without an error event — exactly what portless (no
@@ -1133,11 +1224,31 @@ static void scr_tls_free(void *tctx) {
if (t->srv) scr_tls_srv_release(t->srv);
if (t->cli) scr_tls_cli_free(t->cli);
scr_net_ls_drop(&t->session_ls);
+ scr_net_ls_drop(&t->secure_ls);
free(t->hello);
scr_secure_ctx_release(t->sni_ctx);
free(t);
}
+/* The 'secure' event: after a client's secureConnect listeners, as Node
+ * emits it. */
+static void scr_tls_after_established(void *tctx) {
+ ScrTlsSock *t = (ScrTlsSock *)tctx;
+ scr_net_fire0_this(&t->secure_ls, t->sock, SCR_DYNH_NET_SOCKET);
+ scr_net_ls_drop(&t->secure_ls);
+}
+
+/* The closed socket settles: its 'secure' and 'session' listeners can no
+ * longer fire, and a listener that captures the socket would otherwise
+ * keep it, and this transport, alive (a handshake that fails never fires
+ * 'secure'). */
+static void scr_tls_settle(void *tctx) {
+ ScrTlsSock *t = (ScrTlsSock *)tctx;
+ t->settled = true;
+ scr_net_ls_drop(&t->secure_ls);
+ scr_net_ls_drop(&t->session_ls);
+}
+
static const ScrNetTransportOps SCR_TLS_OPS = {
.xread = scr_tls_xread,
.xwrite = scr_tls_xwrite,
@@ -1146,6 +1257,8 @@ static const ScrNetTransportOps SCR_TLS_OPS = {
.pending = scr_tls_pending,
.shutdown_write = scr_tls_shutdown_write,
.free = scr_tls_free,
+ .settle = scr_tls_settle,
+ .after_established = scr_tls_after_established,
};
/* ── the server surface ──────────────────────────────────────────────── */
@@ -2088,6 +2201,129 @@ ScrNetServer *scr_https_create_server_dyn(const ScrDyn *opts /*borrowed*/,
return s;
}
+/* ── new tls.TLSSocket(socket, options) ───────────────────────────────── */
+
+/* The server-mode wrap: a fresh TLS socket takes over raw's connection
+ * (scr_net_sock_adopt) and runs the server handshake over it. It serves
+ * `ctx` (options.secureContext, which the lowering passes apart from the
+ * record; borrowed, nullable) or the record's cert and key. The record
+ * reads at runtime like tls.createServer's: ALPNProtocols, minVersion and
+ * maxVersion apply, and options without a lowering throw the fence. Node
+ * never starts the handshake of a client-mode TLSSocket (no isServer), so
+ * that form throws the fence too. +1, or NULL with the exception pending. */
+static const char *const SCR_TLS_WRAP_FENCED_OPTIONS[] = {
+ "server", "session", "requestOCSP", "pskCallback", "enableTrace", NULL,
+};
+
+/* The socket-level options of a wrap that serves a secureContext, which
+ * the server-options walk would otherwise check: requestCert (client
+ * certificates are not modeled), SNICallback and ALPNCallback fence.
+ * False with the exception pending. */
+static bool scr_tls_wrap_socket_opts_check(const ScrDyn *record, const char *api) {
+ for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
+ const ScrDynEntry *e = &record->v.obj.entries[i];
+ if (e->value->kind == SCR_DYN_UNDEF) continue;
+ if (strcmp(e->key, "requestCert") == 0) {
+ if (!scr_tls_dyn_truthy(e->value)) continue;
+ scr_tls_opt_fence(api, "requestCert",
+ "client-certificate handshakes are not modeled (SEMANTICS.md divergence 55) — requestCert stays at Node's false default");
+ return false;
+ }
+ if (strcmp(e->key, "SNICallback") == 0 || strcmp(e->key, "ALPNCallback") == 0) {
+ scr_tls_opt_fence(api, e->key,
+ "isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
+ return false;
+ }
+ }
+ return true;
+}
+
+ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrowed*/,
+ ScrSecureCtx *ctx /*borrowed, nullable*/) {
+ static const char api[] = "new tls.TLSSocket";
+ bool is_server = false;
+ const ScrDyn *record = opts != NULL && opts->kind == SCR_DYN_OBJ ? opts : NULL;
+ for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
+ const ScrDynEntry *e = &record->v.obj.entries[i];
+ if (e->value->kind == SCR_DYN_UNDEF) continue;
+ if (strcmp(e->key, "isServer") == 0) {
+ is_server = scr_tls_dyn_truthy(e->value);
+ continue;
+ }
+ if (strcmp(e->key, "secureContext") == 0) {
+ scr_tls_opt_fence(api, "secureContext",
+ "secureContext lowers as a member of an options object literal");
+ return NULL;
+ }
+ for (size_t k = 0; SCR_TLS_WRAP_FENCED_OPTIONS[k] != NULL; k++) {
+ if (strcmp(e->key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
+ scr_tls_opt_fence(api, e->key,
+ "isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
+ return NULL;
+ }
+ }
+ }
+ if (!is_server) {
+ scr_tls_opt_fence(api, "isServer",
+ "Node starts no handshake for a client-mode TLSSocket; tls.connect({ socket }) runs the client handshake over an open socket");
+ return NULL;
+ }
+ /* Node serves a secureContext as it is: the record's cert, key and
+ * other context options are not read then. The socket's own options
+ * still apply, so the ones without a lowering fence either way:
+ * requestCert asks for a client certificate this runtime cannot
+ * verify. */
+ int min_version = 12, max_version = 13;
+ ScrBytes *cert = NULL, *key = NULL;
+ if (ctx == NULL) {
+ if (!scr_tls_versions_dyn(record, &min_version, &max_version)) return NULL;
+ if (!scr_tls_srv_opts_walk_ex(record, api, &cert, &key, true)) return NULL;
+ } else if (!scr_tls_wrap_socket_opts_check(record, api)) {
+ return NULL;
+ }
+ char **alpn = NULL;
+ const ScrDyn *protocols = record != NULL ? scr_dyn_obj_get(record, "ALPNProtocols", 13) : NULL;
+ if (protocols != NULL && protocols->kind != SCR_DYN_UNDEF && !scr_tls_alpn_dyn(protocols, &alpn)) {
+ scr_bytes_release(cert);
+ scr_bytes_release(key);
+ return NULL;
+ }
+ ScrTlsSrv *srv = ctx != NULL ? scr_tls_srv_from_ctx(ctx)
+ : scr_tls_srv_new((const char *)cert->data, cert->len,
+ (const char *)key->data, key->len, false);
+ scr_bytes_release(cert);
+ scr_bytes_release(key);
+ srv->alpn = alpn; /* moves */
+ if (alpn != NULL) mbedtls_ssl_conf_alpn_protocols(&srv->conf, (const char **)srv->alpn);
+ if (!scr_tls_conf_versions(&srv->conf, min_version, max_version)) {
+ scr_tls_srv_release(srv);
+ scr_tls_opt_fence(api, "maxVersion",
+ "a version range with neither TLSv1.2 nor TLSv1.3 has no lowering");
+ return NULL;
+ }
+ srv->max_tls12 = max_version == 12;
+ ScrNetSocket *t = scr_net_sock_adopt(raw);
+ if (t == NULL) {
+ scr_tls_srv_release(srv);
+ static const char msg[] =
+ "new tls.TLSSocket over a socket that is not connected, or is already wrapped, is not supported yet";
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
+ return NULL;
+ }
+ ScrTlsSock *ts = calloc(1, sizeof *ts);
+ if (!ts) scr_tls_oom();
+ mbedtls_ssl_init(&ts->ssl);
+ if (mbedtls_ssl_setup(&ts->ssl, &srv->conf) != 0) scr_tls_oom();
+ ts->sock = t;
+ ts->srv = srv; /* moves */
+ ts->wrapped = true;
+ if (srv->max_tls12) ts->sni_state = SCR_SNI_COLLECT; /* pre-parse the hello */
+ mbedtls_ssl_set_bio(&ts->ssl, ts, scr_tls_bio_send, scr_tls_bio_recv, NULL);
+ scr_net_sock_set_transport(t, &SCR_TLS_OPS, ts);
+ scr_net_sock_adopt_start(t);
+ return t;
+}
+
/* ── tls.connect (the TLS client socket) ──────────────────────────────── */
/* Node's inspect of a Buffer inside ERR_INVALID_ARG_VALUE's message:
@@ -2239,7 +2475,7 @@ static const char *const SCR_TLS_CONNECT_FENCED_OPTIONS[] = {
"localAddress", "localPort", "lookup", "minDHSize", "noDelay", "onread", "passphrase", "path", "pfx",
"privateKeyEngine", "privateKeyIdentifier", "pskCallback", "readable",
"secureContext", "secureOptions", "secureProtocol", "session",
- "sessionIdContext", "sigalgs", "signal", "socket", "ticketKeys",
+ "sessionIdContext", "sigalgs", "signal", "ticketKeys",
"writable", NULL,
};
@@ -2255,6 +2491,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
const ScrDyn *alpn_value = NULL;
const ScrDyn *timeout_value = NULL;
int min_version = 12, max_version = 13;
+ ScrNetSocket *wrap = NULL; /* options.socket: the open socket to take over */
bool ok = true;
if (opts != NULL && opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_NULL) {
if (opts->kind != SCR_DYN_OBJ) {
@@ -2322,6 +2559,15 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
timeout_value = scr_tls_dyn_truthy(v) ? v : NULL;
continue;
}
+ if (strcmp(e->key, "socket") == 0) {
+ if (v->kind == SCR_DYN_HANDLE && v->v.handle.tag == SCR_DYNH_NET_SOCKET) {
+ wrap = (ScrNetSocket *)v->v.handle.ptr;
+ } else {
+ scr_tls_opt_fence("tls.connect", "socket", "a net.Socket is the implemented socket value");
+ ok = false;
+ }
+ continue;
+ }
if (strcmp(e->key, "servername") == 0) {
if (v->kind == SCR_DYN_STR) servername = v->v.str;
else {
@@ -2333,7 +2579,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
for (size_t k = 0; SCR_TLS_CONNECT_FENCED_OPTIONS[k] != NULL; k++) {
if (strcmp(e->key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence("tls.connect", e->key,
- "port/host/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
+ "port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
ok = false;
break;
}
@@ -2361,7 +2607,7 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
timeout_ms = timeout_value->v.num;
}
}
- if (ok && port < 0) {
+ if (ok && port < 0 && wrap == NULL) {
scr_dyn_arg_type_fail("options.port", "of type number", scr_dyn_undefined());
ok = false;
}
@@ -2377,6 +2623,9 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
* name otherwise (Node's servername default); "" sends no SNI. */
ScrStr *verify_host = dial_host;
ScrStr *localhost_fallback = NULL;
+ /* over options.socket, the host that socket dialed (Node's socket._host) */
+ ScrStr *socket_host = verify_host == NULL && wrap != NULL ? scr_net_sock_dial_name(wrap) : NULL;
+ if (socket_host != NULL) verify_host = socket_host;
if (verify_host == NULL) {
localhost_fallback = scr_str_new("localhost", 9);
verify_host = localhost_fallback;
@@ -2384,9 +2633,25 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
/* Resolve for the dial only (the http client's rule): verify_host above
* already holds the NAME, which is what SNI and certificate verification
* must see. A null dial_host stays null — scr_net_connect's own arm. */
- ScrStr *dial_addr = dial_host != NULL ? scr_net_blocking_lookup(dial_host) : NULL;
- ScrNetSocket *s = scr_net_connect(port, dial_addr, cb); /* cb fires post-handshake — secureConnect */
- if (dial_addr) scr_str_release(dial_addr);
+ ScrNetSocket *s;
+ if (wrap != NULL) {
+ s = scr_net_sock_adopt(wrap);
+ if (s == NULL) {
+ scr_tls_alpn_free(alpn);
+ scr_bytes_release(ca);
+ scr_closure_release(cb);
+ scr_str_release(localhost_fallback);
+ scr_str_release(socket_host);
+ static const char msg[] =
+ "tls.connect over a socket that is not connected, or is already wrapped, is not supported yet";
+ scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1);
+ return NULL;
+ }
+ } else {
+ ScrStr *dial_addr = dial_host != NULL ? scr_net_blocking_lookup(dial_host) : NULL;
+ s = scr_net_connect(port, dial_addr, cb); /* cb fires post-handshake — secureConnect */
+ if (dial_addr) scr_str_release(dial_addr);
+ }
ScrTlsCli *cli = scr_tls_cli_new2(verify_host, reject,
ca != NULL ? (const char *)ca->data : NULL,
ca != NULL ? ca->len : 0, !reject);
@@ -2397,8 +2662,13 @@ ScrNetSocket *scr_tls_connect_dyn(double port, ScrStr *host /*borrowed, nullable
}
cli->no_protocols = !scr_tls_conf_versions(&cli->conf, min_version, max_version);
scr_tls_cli_wrap(s, cli);
+ if (wrap != NULL) {
+ if (cb != NULL) scr_net_sock_on_established(s, cb, true); /* secureConnect */
+ scr_net_sock_adopt_start(s);
+ }
if (timeout_ms > 0) scr_net_sock_set_timeout(s, timeout_ms);
scr_str_release(localhost_fallback);
+ scr_str_release(socket_host);
scr_bytes_release(ca);
return s;
}
@@ -2610,12 +2880,39 @@ ScrDyn *scr_tls_sock_alpn_protocol(ScrNetSocket *s) {
return d;
}
+/* secureConnect: a client socket's 'connect' list fires at establishment
+ * (scr_net.c's transport pump), Node's secureConnect timing. Once TCP is up
+ * (a socket tls.connect({ socket }) took over is up from the start) the
+ * listener waits for the handshake instead. Server-side sockets never emit
+ * it. */
+static void scr_tls_add_secure_connect(ScrNetSocket *s, ScrTlsSock *t, ScrClosure *cb, bool once) {
+ if (t->srv != NULL) {
+ scr_closure_release(cb);
+ return;
+ }
+ if (scr_net_sock_connecting(s)) scr_net_sock_on_connect(s, cb, once);
+ else scr_net_sock_on_established(s, cb, once);
+}
+
void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
- if (scr_tls_sock_of(s) == NULL) {
+ ScrTlsSock *t = scr_tls_sock_of(s);
+ if (t == NULL) {
scr_closure_release(cb); /* a plain socket never fires it, like Node */
return;
}
- scr_net_sock_on_connect(s, cb, once); /* fires at establishment — secureConnect timing */
+ scr_tls_add_secure_connect(s, t, cb, once);
+}
+
+/* The 'secure' event: the handshake completed (after secureConnect on a
+ * client). A plain socket never emits it, and a registration after the
+ * handshake comes too late, as in Node. */
+void scr_tls_sock_on_secure(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once) {
+ ScrTlsSock *t = scr_tls_sock_of(s);
+ if (t == NULL || t->established || t->settled) {
+ scr_closure_release(cb);
+ return;
+ }
+ scr_net_ls_add(&t->secure_ls, cb, NULL, once);
}
void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn, bool once) {
@@ -2624,6 +2921,10 @@ void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn
scr_closure_release(cb); /* a plain socket never fires it, like Node */
return;
}
+ if (t->settled) {
+ scr_closure_release(cb);
+ return;
+ }
scr_net_ls_add(&t->session_ls, cb, fn, once);
if (t->established) scr_tls_fire_session(t); /* the ticket already arrived */
}
@@ -2660,9 +2961,11 @@ static bool scr_tls_dynh_on(ScrNetSocket *s, const char *event, const ScrDyn *cb
ScrTlsSock *t = scr_tls_sock_of(s);
if (t == NULL) return false;
if (strcmp(event, "secureConnect") == 0) {
- /* a TLS socket's 'connect' list fires at establishment — exactly
- * Node's secureConnect timing (scr_net.c's transport pump) */
- scr_net_sock_on_connect(s, scr_dyn_listener_closure0(cb), once);
+ scr_tls_add_secure_connect(s, t, scr_dyn_listener_closure0(cb), once);
+ return true;
+ }
+ if (strcmp(event, "secure") == 0) {
+ scr_tls_sock_on_secure(s, scr_dyn_listener_closure0(cb), once);
return true;
}
if (strcmp(event, "session") == 0) {
diff --git a/packages/runtime/src/scr_tls.h b/packages/runtime/src/scr_tls.h
index fcb221b42..cc8e3336e 100644
--- a/packages/runtime/src/scr_tls.h
+++ b/packages/runtime/src/scr_tls.h
@@ -62,6 +62,10 @@ ScrDyn *scr_tls_sock_get_cipher(ScrNetSocket *s); /* +1: object, undefined or
ScrDyn *scr_tls_sock_alpn_protocol(ScrNetSocket *s); /* +1: string, false or null */
void scr_tls_sock_on_secure_connect(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
void scr_tls_sock_on_session(ScrNetSocket *s, ScrClosure *cb /*moves*/, void *fn, bool once);
+/* The 'secure' event: fires when the handshake completes (after the
+ * secureConnect listeners on a client); released unread on a plain socket
+ * or after the handshake. */
+void scr_tls_sock_on_secure(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
/* ── h2 over TLS (scr_tls.c ↔ scr_http2.c): the ALPN=h2 server wrap and
* the https-authority client wrap. scr_http2.c calls both directly —
@@ -93,6 +97,11 @@ ScrSecureCtx *scr_tls_create_secure_context(const char *cert, size_t cert_len, c
* validations first, then the pem walk (+1, or NULL with the exception
* pending). Borrowed. */
ScrSecureCtx *scr_tls_create_secure_context_dyn(const ScrDyn *opts);
+/* new tls.TLSSocket(socket, options) in server mode: a fresh TLS socket
+ * takes over socket's connection and runs the server handshake, serving
+ * ctx (options.secureContext; borrowed, nullable) or the options record's
+ * cert and key. +1, or NULL with the exception pending. */
+ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrowed*/, ScrSecureCtx *ctx /*borrowed, nullable*/);
/* tls.getCACertificates(type): validateString + the documented name set,
* then the compiler-rendered fence — always leaves an exception pending. */
void scr_tls_ca_certs_chk(const ScrDyn *type, const ScrStr *fence);
diff --git a/packages/runtime/src/scr_webcrypto.c b/packages/runtime/src/scr_webcrypto.c
index fc6941e36..1b4207349 100644
--- a/packages/runtime/src/scr_webcrypto.c
+++ b/packages/runtime/src/scr_webcrypto.c
@@ -23,6 +23,7 @@
* compiler links the unit only into programs that call these methods
* (the webcrypto feature). */
#include "scr_runtime.h"
+#include "scr_webcrypto.h"
#include
#include
@@ -58,13 +59,12 @@ static const char *const scr_wc_key_names[] = {
"MLKEM768-X25519", "MLKEM1024-P384", "RSA-OAEP", "RSA-PSS", "RSASSA-PKCS1-v1_5",
"X25519", "X448",
};
-static const char *const scr_wc_digest_names[] = {
+const char *const scr_wc_digest_names[] = {
"SHA-1", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-384",
"SHA3-512", "cSHAKE128", "cSHAKE256", "KT128", "KT256", "TurboSHAKE128",
"TurboSHAKE256",
};
-#define SCR_WC_COUNT(names) (sizeof(names) / sizeof((names)[0]))
/* The implemented hashes: WebCrypto name and node:crypto digest name. */
static const struct {
@@ -90,7 +90,7 @@ static bool scr_wc_ascii_ieq(const ScrStr *s, const char *name) {
}
/* The canonical registered spelling of `name`, or NULL. */
-static const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name) {
+const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name) {
for (size_t i = 0; i < count; i++)
if (scr_wc_ascii_ieq(name, names[i])) return names[i];
return NULL;
@@ -104,11 +104,17 @@ static const char *scr_wc_native_hash(const char *name) {
/* ── errors ────────────────────────────────────────────────────────── */
-static const char *const scr_wc_ordinals[] = {"1st", "2nd", "3rd", "4th", "5th", "6th", "7th"};
+const char *const scr_wc_ordinals[] = {"1st", "2nd", "3rd", "4th", "5th"};
-static void scr_wc_type_error(const char *code, const char *fmt, ...)
- __attribute__((format(printf, 2, 3)));
-static void scr_wc_type_error(const char *code, const char *fmt, ...) {
+/* An argument's ordinal: unwrapKey's seven arguments run past the shared
+ * table. */
+static const char *scr_wc_ordinal(size_t index) {
+ static const char *const later[] = {"6th", "7th"};
+ size_t shared = SCR_WC_COUNT(scr_wc_ordinals);
+ return index < shared ? scr_wc_ordinals[index] : later[index - shared];
+}
+
+void scr_wc_type_error(const char *code, const char *fmt, ...) {
char message[512];
va_list ap;
va_start(ap, fmt);
@@ -120,7 +126,7 @@ static void scr_wc_type_error(const char *code, const char *fmt, ...) {
}
/* An operation or algorithm Node supports that this slice does not. */
-static void scr_wc_unsupported(const char *method, const char *what) {
+void scr_wc_unsupported(const char *method, const char *what) {
char message[256];
int n = snprintf(message, sizeof message, "crypto.subtle.%s with %s is not supported yet", method,
what);
@@ -128,7 +134,7 @@ static void scr_wc_unsupported(const char *method, const char *what) {
scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)n, "SC2020");
}
-static void scr_wc_unsupported_algorithm(const char *method, const char *name) {
+void scr_wc_unsupported_algorithm(const char *method, const char *name) {
char what[96];
snprintf(what, sizeof what, "the %s algorithm", name);
scr_wc_unsupported(method, what);
@@ -138,7 +144,7 @@ static void scr_wc_unsupported_algorithm(const char *method, const char *name) {
* Each answers NULL/false with the TypeError pending. `prefix` is
* "Failed to execute '' on 'SubtleCrypto'". */
-static bool scr_wc_is_object(const ScrDyn *v) {
+bool scr_wc_is_object(const ScrDyn *v) {
switch (v->kind) {
case SCR_DYN_UNDEF:
case SCR_DYN_NULL:
@@ -154,14 +160,14 @@ static bool scr_wc_is_object(const ScrDyn *v) {
}
/* A typed reference capsule as its plain view; other values retained. */
-static ScrDyn *scr_wc_plain(ScrDyn *v) {
+ScrDyn *scr_wc_plain(ScrDyn *v) {
if (v->kind == SCR_DYN_TYPED_REF) return scr_dyn_typed_ref_materialize(v);
return scr_dyn_retain(v);
}
/* BufferSource: the bytes of a TypedArray, DataView, Buffer or
* ArrayBuffer as a u8 view (+1). */
-static ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value) {
+ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value) {
ScrDyn *plain = scr_wc_plain(value);
if (!plain) return NULL;
ScrBytes *view = NULL;
@@ -171,21 +177,21 @@ static ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *
scr_wc_type_error("ERR_INVALID_ARG_TYPE",
"%s: %s argument is not instance of ArrayBuffer, Buffer, TypedArray, or "
"DataView.",
- prefix, scr_wc_ordinals[index]);
+ prefix, scr_wc_ordinal(index));
}
scr_dyn_release(plain);
return view;
}
/* DOMString conversion (+1). */
-static ScrStr *scr_wc_dom_string(const ScrDyn *value) { return scr_dyn_string_coerce_js(value); }
+ScrStr *scr_wc_dom_string(const ScrDyn *value) { return scr_dyn_string_coerce_js(value); }
static const char *const scr_wc_key_formats[] = {
"raw", "raw-public", "raw-seed", "raw-secret", "raw-private", "pkcs8", "spki", "jwk",
};
/* KeyFormat: the canonical enum spelling, or NULL. */
-static const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
+const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
ScrStr *s = scr_wc_dom_string(value);
if (!s) return NULL;
for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_key_formats); i++) {
@@ -203,22 +209,11 @@ static const char *scr_wc_key_format(const char *prefix, ScrDyn *value) {
}
/* Node's canonical usage order; the bit is the index. */
-static const char *const scr_wc_usages[] = {
+const char *const scr_wc_usages[] = {
"encrypt", "decrypt", "sign", "verify",
"deriveKey", "deriveBits", "wrapKey", "unwrapKey",
"encapsulateKey", "encapsulateBits", "decapsulateKey", "decapsulateBits",
};
-enum {
- SCR_WC_USAGE_ENCRYPT = 1u << 0,
- SCR_WC_USAGE_DECRYPT = 1u << 1,
- SCR_WC_USAGE_SIGN = 1u << 2,
- SCR_WC_USAGE_VERIFY = 1u << 3,
- SCR_WC_USAGE_DERIVE_KEY = 1u << 4,
- SCR_WC_USAGE_DERIVE_BITS = 1u << 5,
- SCR_WC_USAGE_WRAP_KEY = 1u << 6,
- SCR_WC_USAGE_UNWRAP_KEY = 1u << 7,
-};
-
/* The usage index of a KeyUsage string, or -1. */
static int scr_wc_usage_index(const ScrStr *s) {
for (size_t k = 0; k < SCR_WC_COUNT(scr_wc_usages); k++)
@@ -228,12 +223,12 @@ static int scr_wc_usage_index(const ScrStr *s) {
}
/* sequence over an array: the usage mask (-1 on failure). */
-static long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
+long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
ScrDyn *plain = scr_wc_plain(value);
if (!plain) return -1;
if (plain->kind != SCR_DYN_ARR) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: %s argument cannot be converted to sequence.",
- prefix, scr_wc_ordinals[index]);
+ prefix, scr_wc_ordinal(index));
scr_dyn_release(plain);
return -1;
}
@@ -251,7 +246,7 @@ static long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value) {
if (k < 0) {
scr_wc_type_error("ERR_INVALID_ARG_VALUE",
"%s: %s argument[%.0f] '%.*s' is not a valid enum value of type KeyUsage.",
- prefix, scr_wc_ordinals[index], i, (int)(s->len > 200 ? 200 : s->len),
+ prefix, scr_wc_ordinal(index), i, (int)(s->len > 200 ? 200 : s->len),
s->data);
scr_str_release(s);
scr_dyn_release(plain);
@@ -274,9 +269,9 @@ typedef struct {
double length;
} ScrWcAlgorithm;
-static const char scr_wc_normalize_prefix[] = "Failed to normalize algorithm";
+const char scr_wc_normalize_prefix[] = "Failed to normalize algorithm";
-static void scr_wc_missing_member(const char *dict, const char *member) {
+void scr_wc_missing_member(const char *dict, const char *member) {
scr_wc_type_error("ERR_MISSING_OPTION",
"%s: passed algorithm cannot be converted to '%s' because '%s' is required in "
"'%s'.",
@@ -284,7 +279,7 @@ static void scr_wc_missing_member(const char *dict, const char *member) {
}
/* Reads `key` off an algorithm dictionary (+1; undefined when absent). */
-static ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key) {
+ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key) {
if (dict->kind != SCR_DYN_OBJ) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *v = scr_dyn_obj_read(dict, key, strlen(key));
return v ? v : scr_dyn_retain(scr_dyn_undefined());
@@ -315,7 +310,7 @@ static bool scr_wc_enforce_range(const char *prefix, const char *context, const
/* The name an AlgorithmIdentifier carries (+1), with the Algorithm
* dictionary's missing-name TypeError. `dict` names the dictionary the
* value converts to. */
-static ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
+ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
if (!scr_wc_is_object(identifier)) return scr_wc_dom_string(identifier);
ScrDyn *plain = scr_wc_plain(identifier);
if (!plain) return NULL;
@@ -330,7 +325,7 @@ static ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict) {
/* The digest a HashAlgorithmIdentifier names: canonical name, or NULL
* with NotSupportedError / the SC2020 refusal pending. */
-static const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method) {
+const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method) {
ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
if (!name) return NULL;
const char *canonical = scr_wc_lookup(scr_wc_digest_names, SCR_WC_COUNT(scr_wc_digest_names), name);
@@ -528,10 +523,10 @@ static ScrCryptoKey *scr_crypto_key_of(const ScrDyn *value) {
bool scr_webcrypto_is_crypto_key(const ScrDyn *value) {
if (value->kind == SCR_DYN_TYPED_REF) return false;
- return scr_crypto_key_of(value) != NULL;
+ return scr_crypto_key_of(value) != NULL || (scr_wc_asymmetric && scr_wc_asymmetric->is_key(value));
}
-static ScrDyn *scr_wc_cstr(const char *s) {
+ScrDyn *scr_wc_cstr(const char *s) {
ScrStr *str = scr_str_new(s, strlen(s));
ScrDyn *out = scr_dyn_new_str(str);
scr_str_release(str);
@@ -871,7 +866,55 @@ static ScrBytes *scr_wc_jwk_secret(const ScrWcJwk *jwk, bool extractable, long u
* Each takes its argument vector as one dyn array (borrowed) and answers
* a +1 promise. */
-static ScrPromise *scr_wc_rejected(void) {
+const ScrWcAsymmetric *scr_wc_asymmetric = NULL;
+
+static const char *const scr_wc_asymmetric_names[] = {"ECDSA", "Ed25519", "RSA-PSS", "RSASSA-PKCS1-v1_5"};
+
+/* Whether the AlgorithmIdentifier at args[index] names ECDSA,
+ * RSASSA-PKCS1-v1_5, RSA-PSS or Ed25519, read without side effects or errors: a
+ * string, or the name member of an object (a statically typed one through
+ * its plain view). A value that does not read cleanly takes the HMAC path,
+ * which reports its errors. */
+static const char *scr_wc_asymmetric_name(const ScrDyn *args, size_t index) {
+ if ((size_t)scr_dyn_arr_len(args) <= index) return NULL;
+ ScrDyn *identifier = scr_dyn_arr_at(args, (double)index);
+ ScrDyn *plain = scr_wc_plain(identifier);
+ if (!plain) scr_exc_clear();
+ const char *found = NULL;
+ if (plain) {
+ const ScrDyn *name = plain;
+ if (plain->kind == SCR_DYN_OBJ) name = scr_dyn_obj_get(plain, "name", 4);
+ if (name && name->kind == SCR_DYN_STR)
+ found = scr_wc_lookup(scr_wc_asymmetric_names, SCR_WC_COUNT(scr_wc_asymmetric_names),
+ name->v.str);
+ scr_dyn_release(plain);
+ }
+ scr_dyn_release(identifier);
+ return found;
+}
+
+/* Whether `value` is an ECDSA, Ed25519 or RSA CryptoKey, which
+ * scr_webcrypto_pki.c holds; wrapKey and unwrapKey wrap secret keys only. */
+static bool scr_wc_is_asymmetric_key(const ScrDyn *value) {
+ return scr_wc_asymmetric && scr_wc_asymmetric->is_key(value);
+}
+
+static bool scr_wc_is_asymmetric_algorithm(const char *name) {
+ for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_asymmetric_names); i++)
+ if (strcmp(name, scr_wc_asymmetric_names[i]) == 0) return true;
+ return false;
+}
+
+/* The asymmetric algorithms run in scr_webcrypto_pki.c, which the compiler
+ * links when a string literal in the program names one of them. */
+static ScrPromise *scr_wc_asymmetric_missing(const char *method, const char *name) {
+ char what[160];
+ snprintf(what, sizeof what, "the %s algorithm named only by a computed string", name);
+ scr_wc_unsupported(method, what);
+ return scr_wc_rejected();
+}
+
+ScrPromise *scr_wc_rejected(void) {
ScrPromise *p = scr_promise_new();
scr_promise_reject_pending(p);
return p;
@@ -892,7 +935,7 @@ static ScrPromise *scr_wc_deferred(ScrDyn *value) {
/* The argument count check every method starts with (after the receiver
* check the compiler's call shape already guarantees). */
-static bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required) {
+bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required) {
size_t argc = (size_t)scr_dyn_arr_len(args);
if (argc >= required) return true;
scr_wc_type_error("ERR_MISSING_ARGS", "%s: %zu arguments required, but only %zu present.", prefix,
@@ -900,15 +943,12 @@ static bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t requi
return false;
}
-typedef struct {
- ScrDyn *v[5];
-} ScrWcArgs;
-static void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n) {
+void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n) {
for (size_t i = 0; i < n; i++) a->v[i] = scr_dyn_arr_at(args, (double)i);
}
-static void scr_wc_args_drop(ScrWcArgs *a, size_t n) {
+void scr_wc_args_drop(ScrWcArgs *a, size_t n) {
for (size_t i = 0; i < n; i++) scr_dyn_release(a->v[i]);
}
@@ -1098,6 +1138,11 @@ done:
}
ScrPromise *scr_webcrypto_import_key(ScrDyn *args) {
+ const char *asymmetric = scr_wc_asymmetric_name(args, 2);
+ if (asymmetric) {
+ return scr_wc_asymmetric ? scr_wc_asymmetric->import_key(args)
+ : scr_wc_asymmetric_missing("importKey", asymmetric);
+ }
ScrDyn *key = scr_wc_import(args);
if (!key) return scr_wc_rejected();
return scr_wc_resolved(key);
@@ -1106,8 +1151,14 @@ ScrPromise *scr_webcrypto_import_key(ScrDyn *args) {
/* generateKey(algorithm, extractable, keyUsages) for HMAC and AES keys:
* random bytes of the requested length (an HMAC key defaults to its
* hash's block size; a length that is not a whole number of bytes clears
- * the final byte's unused low bits). */
+ * the final byte's unused low bits). ECDSA, Ed25519, RSASSA-PKCS1-v1_5 and
+ * RSA-PSS key pairs generate in scr_webcrypto_pki.c. */
ScrPromise *scr_webcrypto_generate_key(ScrDyn *args) {
+ const char *asymmetric = scr_wc_asymmetric_name(args, 0);
+ if (asymmetric) {
+ return scr_wc_asymmetric ? scr_wc_asymmetric->generate_key(args)
+ : scr_wc_asymmetric_missing("generateKey", asymmetric);
+ }
static const char prefix[] = "Failed to execute 'generateKey' on 'SubtleCrypto'";
if (!scr_wc_required(prefix, args, 3)) return scr_wc_rejected();
ScrWcArgs a;
@@ -1193,8 +1244,10 @@ static ScrStr *scr_wc_jwk_k(const ScrCryptoKey *key) {
}
/* exportKeySync for secret keys: raw and raw-secret answer an ArrayBuffer
- * of the key bytes, jwk an object { key_ops, ext, alg, kty, k }. */
-static ScrDyn *scr_wc_export(const ScrDyn *args) {
+ * of the key bytes, jwk an object { key_ops, ext, alg, kty, k }. An ECDSA,
+ * Ed25519 or RSA key sets *asymmetric after the format check instead
+ * (scr_webcrypto_pki.c exports it). */
+static ScrDyn *scr_wc_export(const ScrDyn *args, bool *asymmetric) {
static const char prefix[] = "Failed to execute 'exportKey' on 'SubtleCrypto'";
if (!scr_wc_required(prefix, args, 2)) return NULL;
ScrWcArgs a;
@@ -1203,6 +1256,10 @@ static ScrDyn *scr_wc_export(const ScrDyn *args) {
const char *format = scr_wc_key_format(prefix, a.v[0]);
if (!format) goto done;
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
+ if (!key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1])) {
+ *asymmetric = true;
+ goto done;
+ }
if (!key) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
@@ -1230,7 +1287,9 @@ done:
}
ScrPromise *scr_webcrypto_export_key(ScrDyn *args) {
- ScrDyn *result = scr_wc_export(args);
+ bool asymmetric = false;
+ ScrDyn *result = scr_wc_export(args, &asymmetric);
+ if (asymmetric) return scr_wc_asymmetric->export_key(args);
if (!result) return scr_wc_rejected();
return scr_wc_resolved(result);
}
@@ -1255,7 +1314,8 @@ static bool scr_wc_sign_verify(const ScrDyn *args, bool verify, unsigned char ma
scr_str_release(s);
}
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
- if (!key) {
+ bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
+ if (!key && !asymmetric_key) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
}
@@ -1273,7 +1333,7 @@ static bool scr_wc_sign_verify(const ScrDyn *args, bool verify, unsigned char ma
scr_wc_unsupported_algorithm(method, algorithm.name);
goto done;
}
- if (strcmp(key->name, algorithm.name) != 0) {
+ if (asymmetric_key || strcmp(key->name, algorithm.name) != 0) {
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
goto done;
}
@@ -1297,6 +1357,11 @@ done:
}
ScrPromise *scr_webcrypto_sign(ScrDyn *args) {
+ const char *asymmetric = scr_wc_asymmetric_name(args, 0);
+ if (asymmetric) {
+ return scr_wc_asymmetric ? scr_wc_asymmetric->sign(args)
+ : scr_wc_asymmetric_missing("sign", asymmetric);
+ }
unsigned char mac[64];
size_t mac_len = 0;
if (!scr_wc_sign_verify(args, false, mac, &mac_len, NULL)) return scr_wc_rejected();
@@ -1319,6 +1384,11 @@ static void scr_wc_settle_verified(ScrClosure *cb, ScrError *error, ScrBytes *va
}
ScrPromise *scr_webcrypto_verify(ScrDyn *args) {
+ const char *asymmetric = scr_wc_asymmetric_name(args, 0);
+ if (asymmetric) {
+ return scr_wc_asymmetric ? scr_wc_asymmetric->verify(args)
+ : scr_wc_asymmetric_missing("verify", asymmetric);
+ }
unsigned char mac[64];
size_t mac_len = 0;
ScrBytes *signature = NULL;
@@ -1619,7 +1689,9 @@ ScrPromise *scr_webcrypto_cipher(ScrDyn *args, bool decrypt, ScrWebCryptoAesFn a
ScrPromise *result = NULL;
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
- if (!key) {
+ /* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) is no cipher key */
+ bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
+ if (!key && !asymmetric_key) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
}
@@ -1627,7 +1699,7 @@ ScrPromise *scr_webcrypto_cipher(ScrDyn *args, bool decrypt, ScrWebCryptoAesFn a
if (!data) goto done;
const char *name = scr_wc_normalize_cipher(a.v[0], method, ¶ms);
if (!name) goto done;
- if (strcmp(key->name, name) != 0) {
+ if (asymmetric_key || strcmp(key->name, name) != 0) {
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
goto done;
}
@@ -1717,19 +1789,20 @@ ScrPromise *scr_webcrypto_wrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
const char *format = scr_wc_key_format(prefix, a.v[0]);
if (!format) goto done;
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
- if (!key) {
+ if (!key && !scr_wc_is_asymmetric_key(a.v[1])) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
}
ScrCryptoKey *wrapping = scr_crypto_key_of(a.v[2]);
- if (!wrapping) {
+ if (!wrapping && !scr_wc_is_asymmetric_key(a.v[2])) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 3rd argument is not of type CryptoKey.", prefix);
goto done;
}
if (!scr_wc_algorithm_identifier(a.v[3])) goto done;
const char *name = scr_wc_normalize_wrap(a.v[3], "wrapKey", ¶ms);
if (!name) goto done;
- if (strcmp(wrapping->name, name) != 0) {
+ /* An ECDSA, Ed25519 or RSA key never has an AES algorithm. */
+ if (!wrapping || strcmp(wrapping->name, name) != 0) {
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
goto done;
}
@@ -1737,6 +1810,10 @@ ScrPromise *scr_webcrypto_wrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
scr_throw_domex("InvalidAccessError", "Unable to use this key to wrapKey");
goto done;
}
+ if (!key) {
+ scr_wc_unsupported("wrapKey", "an ECDSA, Ed25519 or RSA key");
+ goto done;
+ }
if (!scr_wc_exportable(format, key)) goto done;
payload = scr_wc_wrap_payload(format, key, strcmp(name, "AES-KW") == 0);
result = scr_wc_run_cipher(aes, name, false, wrapping, ¶ms, payload);
@@ -1782,7 +1859,7 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
wrapped = scr_wc_buffer_source(prefix, 1, v[1]);
if (!wrapped) goto done;
ScrCryptoKey *unwrapping = scr_crypto_key_of(v[2]);
- if (!unwrapping) {
+ if (!unwrapping && !scr_wc_is_asymmetric_key(v[2])) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 3rd argument is not of type CryptoKey.", prefix);
goto done;
}
@@ -1796,7 +1873,7 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
if (!scr_wc_normalize(v[4], scr_wc_import_names, SCR_WC_COUNT(scr_wc_import_names),
SCR_WC_OP_IMPORT, &imported))
goto done;
- if (strcmp(unwrapping->name, name) != 0) {
+ if (!unwrapping || strcmp(unwrapping->name, name) != 0) {
scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
goto done;
}
@@ -1804,6 +1881,10 @@ ScrPromise *scr_webcrypto_unwrap(ScrDyn *args, ScrWebCryptoAesFn aes) {
scr_throw_domex("InvalidAccessError", "Unable to use this key to unwrapKey");
goto done;
}
+ if (scr_wc_is_asymmetric_algorithm(imported.name)) {
+ scr_wc_unsupported_algorithm("unwrapKey", imported.name);
+ goto done;
+ }
if (!scr_wc_max_length(wrapped, "data")) goto done;
ScrBytes *bytes = scr_wc_cipher_bytes(aes, name, true, unwrapping, ¶ms, wrapped);
if (!bytes) {
@@ -2002,7 +2083,8 @@ static ScrBytes *scr_wc_kdf_derive(const ScrWcKdfParams *params, const ScrCrypto
/* The base key checks deriveBits and deriveKey share. */
static bool scr_wc_check_base_key(const ScrCryptoKey *key, const char *algorithm, unsigned usage,
const char *usage_name) {
- if (!(key->usages & usage)) {
+ /* NULL: an asymmetric key, which never has a derive usage */
+ if (key == NULL || !(key->usages & usage)) {
char message[64];
snprintf(message, sizeof message, "baseKey does not have %s usage", usage_name);
scr_throw_domex("InvalidAccessError", message);
@@ -2028,7 +2110,9 @@ ScrPromise *scr_webcrypto_derive_bits(ScrDyn *args) {
ScrPromise *result = NULL;
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
- if (!key) {
+ /* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) has no derive usage */
+ bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
+ if (!key && !asymmetric_key) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
}
@@ -2038,7 +2122,9 @@ ScrPromise *scr_webcrypto_derive_bits(ScrDyn *args) {
if (!null_length && !scr_wc_enforce_range(prefix, "3rd argument", a.v[2], 4294967295.0, &length))
goto done;
if (!scr_wc_normalize_kdf(a.v[0], "deriveBits", ¶ms)) goto done;
- if (!scr_wc_check_base_key(key, params.name, SCR_WC_USAGE_DERIVE_BITS, "deriveBits")) goto done;
+ if (!scr_wc_check_base_key(asymmetric_key ? NULL : key, params.name, SCR_WC_USAGE_DERIVE_BITS,
+ "deriveBits"))
+ goto done;
if (!scr_wc_kdf_length(¶ms, null_length, length)) goto done;
if (length == 0) {
ScrBytes *empty = scr_bytes_new(SCR_BYTES_U8, 0);
@@ -2099,7 +2185,9 @@ ScrPromise *scr_webcrypto_derive_key(ScrDyn *args) {
ScrPromise *result = NULL;
if (!scr_wc_algorithm_identifier(a.v[0])) goto done;
ScrCryptoKey *key = scr_crypto_key_of(a.v[1]);
- if (!key) {
+ /* an ECDSA, Ed25519 or RSA key (scr_webcrypto_pki.c) has no derive usage */
+ bool asymmetric_key = !key && scr_wc_asymmetric && scr_wc_asymmetric->is_key(a.v[1]);
+ if (!key && !asymmetric_key) {
scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
goto done;
}
@@ -2120,7 +2208,9 @@ ScrPromise *scr_webcrypto_derive_key(ScrDyn *args) {
scr_wc_unsupported_algorithm("deriveKey", imported.name);
goto done;
}
- if (!scr_wc_check_base_key(key, params.name, SCR_WC_USAGE_DERIVE_KEY, "deriveKey")) goto done;
+ if (!scr_wc_check_base_key(asymmetric_key ? NULL : key, params.name, SCR_WC_USAGE_DERIVE_KEY,
+ "deriveKey"))
+ goto done;
if (!scr_wc_kdf_length(¶ms, null_length, length)) goto done;
ScrBytes *bits = scr_wc_kdf_derive(¶ms, key, length);
if (!bits) goto done;
diff --git a/packages/runtime/src/scr_webcrypto.h b/packages/runtime/src/scr_webcrypto.h
new file mode 100644
index 000000000..1eb84af9b
--- /dev/null
+++ b/packages/runtime/src/scr_webcrypto.h
@@ -0,0 +1,67 @@
+/* The Web IDL and error helpers of scr_webcrypto.c that the asymmetric
+ * algorithms (scr_webcrypto_pki.c) share, and the table through which
+ * scr_webcrypto.c reaches them when that unit is linked. */
+#ifndef SCR_WEBCRYPTO_H
+#define SCR_WEBCRYPTO_H
+
+#include
+#include
+
+#define SCR_WC_COUNT(names) (sizeof(names) / sizeof((names)[0]))
+
+enum {
+ SCR_WC_USAGE_ENCRYPT = 1u << 0,
+ SCR_WC_USAGE_DECRYPT = 1u << 1,
+ SCR_WC_USAGE_SIGN = 1u << 2,
+ SCR_WC_USAGE_VERIFY = 1u << 3,
+ SCR_WC_USAGE_DERIVE_KEY = 1u << 4,
+ SCR_WC_USAGE_DERIVE_BITS = 1u << 5,
+ SCR_WC_USAGE_WRAP_KEY = 1u << 6,
+ SCR_WC_USAGE_UNWRAP_KEY = 1u << 7,
+};
+
+/* Node's canonical usage order; the bit is the index. */
+extern const char *const scr_wc_usages[12];
+extern const char *const scr_wc_ordinals[5];
+extern const char scr_wc_normalize_prefix[];
+extern const char *const scr_wc_digest_names[13];
+
+typedef struct {
+ ScrDyn *v[5];
+} ScrWcArgs;
+
+void scr_wc_type_error(const char *code, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+void scr_wc_unsupported(const char *method, const char *what);
+void scr_wc_unsupported_algorithm(const char *method, const char *name);
+bool scr_wc_is_object(const ScrDyn *v);
+ScrDyn *scr_wc_plain(ScrDyn *v);
+ScrBytes *scr_wc_buffer_source(const char *prefix, size_t index, ScrDyn *value);
+ScrStr *scr_wc_dom_string(const ScrDyn *value);
+const char *scr_wc_key_format(const char *prefix, ScrDyn *value);
+long scr_wc_key_usages(const char *prefix, size_t index, ScrDyn *value);
+void scr_wc_missing_member(const char *dict, const char *member);
+ScrDyn *scr_wc_member(const ScrDyn *dict, const char *key);
+ScrStr *scr_wc_algorithm_name(ScrDyn *identifier, const char *dict);
+const char *scr_wc_digest_algorithm(ScrDyn *identifier, const char *method);
+const char *scr_wc_lookup(const char *const *names, size_t count, const ScrStr *name);
+ScrDyn *scr_wc_cstr(const char *s);
+ScrPromise *scr_wc_rejected(void);
+bool scr_wc_required(const char *prefix, const ScrDyn *args, size_t required);
+void scr_wc_args_take(ScrWcArgs *a, const ScrDyn *args, size_t n);
+void scr_wc_args_drop(ScrWcArgs *a, size_t n);
+
+/* ECDSA, RSASSA-PKCS1-v1_5 and RSA-PSS (scr_webcrypto_pki.c, linked with
+ * the pki unit): each takes the method's argument vector (borrowed) and
+ * answers a +1 promise. NULL until that unit installs it. */
+typedef struct {
+ ScrPromise *(*generate_key)(const ScrDyn *args);
+ ScrPromise *(*import_key)(const ScrDyn *args);
+ ScrPromise *(*export_key)(const ScrDyn *args);
+ ScrPromise *(*sign)(const ScrDyn *args);
+ ScrPromise *(*verify)(const ScrDyn *args);
+ bool (*is_key)(const ScrDyn *value);
+} ScrWcAsymmetric;
+
+extern const ScrWcAsymmetric *scr_wc_asymmetric;
+
+#endif
diff --git a/packages/runtime/src/scr_webcrypto_pki.c b/packages/runtime/src/scr_webcrypto_pki.c
new file mode 100644
index 000000000..7439a12db
--- /dev/null
+++ b/packages/runtime/src/scr_webcrypto_pki.c
@@ -0,0 +1,841 @@
+/* WebCrypto's ECDSA, RSASSA-PKCS1-v1_5, RSA-PSS and Ed25519 algorithms:
+ * crypto.subtle.generateKey, importKey (spki, pkcs8, jwk, and raw for ECDSA
+ * and Ed25519 public keys), exportKey, sign and verify, and the CryptoKey
+ * values of these keys. ECDSA keys are on P-256, P-384 and P-521; ECDSA
+ * signatures are IEEE P1363 r || s.
+ *
+ * Argument conversion, algorithm normalization and the DOMException
+ * shapes follow Node 24's lib/internal/crypto/webcrypto.js, ec.js and
+ * rsa.js, through the Web IDL helpers of scr_webcrypto.c. The keys and
+ * signatures come from the pki unit (scr_pki.c). This unit installs
+ * itself into scr_webcrypto.c's dispatch table at startup; the compiler
+ * links it when a program uses WebCrypto and names one of these
+ * algorithms. All arguments are borrowed. */
+#include "scr_runtime.h"
+
+#include
+#include
+#include
+#include
+
+#include "scr_pki.h"
+#include "scr_webcrypto.h"
+
+typedef enum { SW_ECDSA, SW_RSASSA, SW_PSS, SW_ED25519 } SwKind;
+
+static const char *const sw_names[] = {"ECDSA", "RSASSA-PKCS1-v1_5", "RSA-PSS", "Ed25519"};
+
+static const char *const sw_curves[] = {"P-256", "P-384", "P-521"};
+
+/* ── CryptoKey ────────────────────────────────────────────────────────── */
+
+typedef struct SwKey {
+ size_t rc;
+ ScrPkiKey *key;
+ SwKind kind;
+ const char *hash; /* RSA keys: the WebCrypto hash name */
+ unsigned usages;
+ bool extractable;
+ ScrDyn *algorithm; /* cached getter results, or NULL */
+ ScrDyn *usage_list;
+} SwKey;
+
+static void *sw_key_retain(void *p) {
+ ((SwKey *)p)->rc++;
+ return p;
+}
+
+static void sw_key_release(void *p) {
+ SwKey *k = p;
+ if (!k || --k->rc != 0) return;
+ scr_pki_key_release(k->key);
+ if (k->algorithm) scr_dyn_release(k->algorithm);
+ if (k->usage_list) scr_dyn_release(k->usage_list);
+ free(k);
+}
+
+static SwKey *sw_key_of(const ScrDyn *value) {
+ if (value->kind != SCR_DYN_HANDLE || value->v.handle.tag != SCR_DYNH_ASYMMETRIC_CRYPTO_KEY) return NULL;
+ return value->v.handle.ptr;
+}
+
+static bool sw_is_key(const ScrDyn *value) { return sw_key_of(value) != NULL; }
+
+static ScrDyn *sw_key_algorithm(SwKey *k) {
+ if (!k->algorithm) {
+ ScrDyn *algorithm = scr_dyn_new_obj();
+ scr_dyn_obj_set(algorithm, "name", 4, scr_wc_cstr(sw_names[k->kind]));
+ if (k->kind == SW_ED25519) {
+ /* { name } only */
+ } else if (k->kind == SW_ECDSA) {
+ scr_dyn_obj_set(algorithm, "namedCurve", 10, scr_wc_cstr(scr_pki_key_curve(k->key)));
+ } else {
+ scr_dyn_obj_set(algorithm, "modulusLength", 13, scr_dyn_new_num((double)scr_pki_key_bits(k->key)));
+ ScrBytes *exponent = scr_pki_key_exponent(k->key);
+ scr_dyn_obj_set(algorithm, "publicExponent", 14, scr_dyn_new_bytes(exponent));
+ scr_bytes_release(exponent);
+ ScrDyn *hash = scr_dyn_new_obj();
+ scr_dyn_obj_set(hash, "name", 4, scr_wc_cstr(k->hash));
+ scr_dyn_obj_set(algorithm, "hash", 4, hash);
+ }
+ k->algorithm = algorithm;
+ }
+ return scr_dyn_retain(k->algorithm);
+}
+
+static ScrDyn *sw_usage_array(unsigned usages) {
+ ScrDyn *list = scr_dyn_new_arr();
+ for (size_t i = 0; i < SCR_WC_COUNT(scr_wc_usages); i++)
+ if (usages & (1u << i)) scr_dyn_arr_push(list, scr_wc_cstr(scr_wc_usages[i]));
+ return list;
+}
+
+static ScrDyn *sw_key_usages(SwKey *k) {
+ if (!k->usage_list) k->usage_list = sw_usage_array(k->usages);
+ return scr_dyn_retain(k->usage_list);
+}
+
+static bool sw_name_is(const char *name, size_t len, const char *literal) {
+ return len == strlen(literal) && memcmp(name, literal, len) == 0;
+}
+
+static ScrDyn *sw_key_get(void *h, const char *name, size_t len) {
+ SwKey *k = h;
+ if (sw_name_is(name, len, "type"))
+ return scr_wc_cstr(scr_pki_key_is_private(k->key) ? "private" : "public");
+ if (sw_name_is(name, len, "extractable")) return scr_dyn_new_bool(k->extractable);
+ if (sw_name_is(name, len, "algorithm")) return sw_key_algorithm(k);
+ if (sw_name_is(name, len, "usages")) return sw_key_usages(k);
+ return NULL;
+}
+
+static bool sw_key_set(void *h, const char *name, size_t len, const ScrDyn *value) {
+ (void)h;
+ (void)value;
+ if (!sw_name_is(name, len, "type") && !sw_name_is(name, len, "extractable") &&
+ !sw_name_is(name, len, "algorithm") && !sw_name_is(name, len, "usages"))
+ return false;
+ scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "Cannot set property %.*s of # which has only a getter", (int)len,
+ name);
+ return true;
+}
+
+static ScrDyn *sw_key_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc,
+ const char *what) {
+ (void)h;
+ (void)self;
+ (void)args;
+ (void)argc;
+ if (strcmp(method, "toString") == 0) return scr_wc_cstr("[object CryptoKey]");
+ scr_pki_throw_fmt(SCR_ERR_TYPE, NULL, "%s is not a function", what);
+ return NULL;
+}
+
+static ScrDyn *sw_key_symbol_get(void *h, const ScrDyn *key) {
+ (void)h;
+ if (key->kind != SCR_DYN_SYMBOL || !key->v.symbol.description) return NULL;
+ ScrStr *description = key->v.symbol.description(key->v.symbol.value);
+ bool tag = description && description->len == 18 && memcmp(description->data, "Symbol.toStringTag", 18) == 0;
+ if (description) scr_str_release(description);
+ return tag ? scr_wc_cstr("CryptoKey") : NULL;
+}
+
+/* As for HMAC CryptoKeys, util.inspect refuses these keys rather than
+ * printing a different shape than Node's custom inspector. */
+static const ScrDynHandleOps sw_key_ops = {
+ .cls = "CryptoKey",
+ .retain = sw_key_retain,
+ .release = sw_key_release,
+ .invoke = sw_key_invoke,
+ .get = sw_key_get,
+ .set = sw_key_set,
+ .json_empty_object = true,
+ .to_string_tag = "CryptoKey",
+ .symbol_get = sw_key_symbol_get,
+};
+
+/* Boxes a CryptoKey over `key` (consumed). */
+static ScrDyn *sw_key_new(ScrPkiKey *key, SwKind kind, const char *hash, unsigned usages, bool extractable) {
+ static SCR_TL bool installed;
+ if (!installed) {
+ installed = true;
+ scr_dyn_handle_install(SCR_DYNH_ASYMMETRIC_CRYPTO_KEY, &sw_key_ops);
+ }
+ SwKey *k = calloc(1, sizeof *k);
+ if (!k) scr_trap("scriptc: out of memory\n");
+ k->rc = 1;
+ k->key = key;
+ k->kind = kind;
+ k->hash = hash;
+ k->usages = usages;
+ k->extractable = extractable;
+ ScrDyn *box = scr_dyn_new_handle(k, SCR_DYNH_ASYMMETRIC_CRYPTO_KEY);
+ sw_key_release(k);
+ return box;
+}
+
+/* ── algorithm parameters ─────────────────────────────────────────────── */
+
+static SwKind sw_kind_of(const char *name) {
+ if (strcmp(name, "ECDSA") == 0) return SW_ECDSA;
+ if (strcmp(name, "Ed25519") == 0) return SW_ED25519;
+ return strcmp(name, "RSA-PSS") == 0 ? SW_PSS : SW_RSASSA;
+}
+
+static bool sw_is_rsa_kind(SwKind kind) { return kind == SW_RSASSA || kind == SW_PSS; }
+
+/* The canonical algorithm name of an AlgorithmIdentifier the dispatcher
+ * already recognized as one of this unit's algorithms, or NULL with the
+ * error pending. */
+static const char *sw_algorithm_name(ScrDyn *identifier) {
+ ScrStr *name = scr_wc_algorithm_name(identifier, "Algorithm");
+ if (!name) return NULL;
+ const char *found = scr_wc_lookup(sw_names, SCR_WC_COUNT(sw_names), name);
+ scr_str_release(name);
+ if (!found) scr_throw_domex("NotSupportedError", "Unrecognized algorithm name");
+ return found;
+}
+
+/* A required dictionary member (+1), or NULL with ERR_MISSING_OPTION. */
+static ScrDyn *sw_required(ScrDyn *plain, const char *dict, const char *member) {
+ ScrDyn *v = scr_wc_member(plain, member);
+ if (v->kind == SCR_DYN_UNDEF) {
+ scr_dyn_release(v);
+ scr_wc_missing_member(dict, member);
+ return NULL;
+ }
+ return v;
+}
+
+/* namedCurve (DOMString, required): one of the implemented curves, or
+ * NULL with the error pending. */
+static const char *sw_named_curve(ScrDyn *plain, const char *dict) {
+ ScrDyn *value = sw_required(plain, dict, "namedCurve");
+ if (!value) return NULL;
+ ScrStr *s = scr_wc_dom_string(value);
+ scr_dyn_release(value);
+ if (!s) return NULL;
+ const char *curve = NULL;
+ for (size_t i = 0; i < SCR_WC_COUNT(sw_curves); i++)
+ if (s->len == strlen(sw_curves[i]) && memcmp(s->data, sw_curves[i], s->len) == 0) curve = sw_curves[i];
+ scr_str_release(s);
+ if (!curve) scr_throw_domex("NotSupportedError", "Unrecognized namedCurve");
+ return curve;
+}
+
+/* hash (HashAlgorithmIdentifier, required): with `sha_only`, Node's
+ * ensureSHA check first ("Only SHA hashes are supported in "). */
+static const char *sw_hash(ScrDyn *plain, const char *dict, bool sha_only, const char *method) {
+ ScrDyn *hash = sw_required(plain, dict, "hash");
+ if (!hash) return NULL;
+ if (sha_only) {
+ ScrStr *name = NULL;
+ if (scr_wc_is_object(hash)) {
+ ScrDyn *hp = scr_wc_plain(hash);
+ ScrDyn *member = hp ? scr_wc_member(hp, "name") : NULL;
+ if (hp) scr_dyn_release(hp);
+ if (member && member->kind == SCR_DYN_STR) name = scr_str_retain(member->v.str);
+ if (member) scr_dyn_release(member);
+ } else {
+ name = scr_wc_dom_string(hash);
+ if (!name) {
+ scr_dyn_release(hash);
+ return NULL;
+ }
+ }
+ bool sha = name && name->len >= 3 && (name->data[0] | 32) == 's' && (name->data[1] | 32) == 'h' &&
+ (name->data[2] | 32) == 'a';
+ if (name) scr_str_release(name);
+ if (!sha) {
+ char message[96];
+ snprintf(message, sizeof message, "Only SHA hashes are supported in %s", dict);
+ scr_throw_domex("NotSupportedError", message);
+ scr_dyn_release(hash);
+ return NULL;
+ }
+ }
+ const char *canonical = scr_wc_digest_algorithm(hash, method);
+ scr_dyn_release(hash);
+ return canonical;
+}
+
+/* An [EnforceRange] unsigned long member; false with the TypeError
+ * pending. */
+static bool sw_unsigned_long(ScrDyn *value, const char *member, double *out) {
+ double x;
+ if (!scr_dyn_number_coerce_js(value, &x)) return false;
+ if (x != x || x == 1.0 / 0.0 || x == -1.0 / 0.0) {
+ scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: %s in passed algorithm is not a finite number.",
+ scr_wc_normalize_prefix, member);
+ return false;
+ }
+ x = trunc(x);
+ if (x < 0 || x > 4294967295.0) {
+ scr_wc_type_error("ERR_OUT_OF_RANGE",
+ "%s: %s in passed algorithm is outside the expected range of 0 to 4294967295.",
+ scr_wc_normalize_prefix, member);
+ return false;
+ }
+ *out = x == 0 ? 0 : x;
+ return true;
+}
+
+static void sw_unsupported_usage(SwKind kind) {
+ char message[96];
+ snprintf(message, sizeof message, "Unsupported key usage for %s key", sw_names[kind]);
+ scr_throw_domex("SyntaxError", message);
+}
+
+/* ── generateKey ──────────────────────────────────────────────────────── */
+
+static ScrPromise *sw_generate_key(const ScrDyn *args) {
+ static const char prefix[] = "Failed to execute 'generateKey' on 'SubtleCrypto'";
+ if (!scr_wc_required(prefix, args, 3)) return scr_wc_rejected();
+ ScrWcArgs a;
+ scr_wc_args_take(&a, args, 3);
+ ScrDyn *pair = NULL;
+ ScrDyn *plain = NULL;
+ if (!scr_wc_is_object(a.v[0])) {
+ ScrStr *s = scr_wc_dom_string(a.v[0]);
+ if (!s) goto done;
+ scr_str_release(s);
+ }
+ bool extractable = scr_dyn_truthy(a.v[1]);
+ long usages = scr_wc_key_usages(prefix, 2, a.v[2]);
+ if (usages < 0) goto done;
+ const char *name = sw_algorithm_name(a.v[0]);
+ if (!name) goto done;
+ SwKind kind = sw_kind_of(name);
+ plain = scr_wc_plain(a.v[0]);
+ if (!plain) goto done;
+ const char *curve = NULL;
+ const char *hash = NULL;
+ double bits = 0;
+ ScrBytes *exponent = NULL;
+ if (kind == SW_ED25519) {
+ /* no parameters */
+ } else if (kind == SW_ECDSA) {
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member("EcKeyGenParams", "namedCurve");
+ goto done;
+ }
+ curve = sw_named_curve(plain, "EcKeyGenParams");
+ if (!curve) goto done;
+ } else {
+ static const char dict[] = "RsaHashedKeyGenParams";
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member(dict, "hash");
+ goto done;
+ }
+ /* Dictionary members convert in name order: hash, modulusLength,
+ * publicExponent. */
+ hash = sw_hash(plain, dict, true, "generateKey");
+ if (!hash) goto done;
+ ScrDyn *length = sw_required(plain, dict, "modulusLength");
+ if (!length) goto done;
+ bool ok = sw_unsigned_long(length, "modulusLength", &bits);
+ scr_dyn_release(length);
+ if (!ok) goto done;
+ ScrDyn *e = sw_required(plain, dict, "publicExponent");
+ if (!e) goto done;
+ if (e->kind != SCR_DYN_BYTES || e->v.bytes->elem != SCR_BYTES_U8) {
+ scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: publicExponent in passed algorithm is not instance of Uint8Array.",
+ scr_wc_normalize_prefix);
+ scr_dyn_release(e);
+ goto done;
+ }
+ exponent = scr_bytes_buffer_source(e);
+ scr_dyn_release(e);
+ }
+ if (usages & ~(long)(SCR_WC_USAGE_SIGN | SCR_WC_USAGE_VERIFY)) {
+ sw_unsupported_usage(kind);
+ if (exponent) scr_bytes_release(exponent);
+ goto done;
+ }
+ ScrPkiKey *key;
+ if (kind == SW_ED25519) {
+ key = scr_pki_generate_ed25519();
+ if (!key) goto done;
+ } else if (kind == SW_ECDSA) {
+ key = scr_pki_generate_ec(curve);
+ } else {
+ /* publicExponent: big-endian, at most a 32-bit value. */
+ size_t n = (size_t)scr_bytes_byte_len(exponent);
+ uint64_t value = 0;
+ bool fits = true;
+ for (size_t i = 0; i < n; i++) {
+ if (value > 0xffffffu && fits) fits = false;
+ value = (value << 8) | exponent->data[i];
+ }
+ scr_bytes_release(exponent);
+ if (!fits || value > 0xffffffffu) {
+ scr_throw_domex("OperationError", "The publicExponent must be equivalent to an unsigned 32-bit value");
+ goto done;
+ }
+ if (bits < 512) {
+ scr_throw_domex("OperationError", "algorithm.modulusLength must be at least 512");
+ goto done;
+ }
+ if ((value & 1) == 0) {
+ scr_throw_domex("OperationError", "algorithm.publicExponent must be odd");
+ goto done;
+ }
+ if (bits > 16384 || value < 3) {
+ scr_wc_unsupported("generateKey", "this RSA modulus length or public exponent");
+ goto done;
+ }
+ key = scr_pki_generate_rsa((unsigned)bits, value);
+ }
+ if (!key) {
+ scr_throw_domex("OperationError", "Key generation failed");
+ goto done;
+ }
+ unsigned private_usages = (unsigned)usages & SCR_WC_USAGE_SIGN;
+ unsigned public_usages = (unsigned)usages & SCR_WC_USAGE_VERIFY;
+ if (private_usages == 0) {
+ scr_pki_key_release(key);
+ scr_throw_domex("SyntaxError", "Usages cannot be empty when creating a key.");
+ goto done;
+ }
+ ScrPkiKey *public_key = scr_pki_key_public_of(key);
+ if (!public_key) {
+ scr_pki_key_release(key);
+ scr_throw_domex("OperationError", "Key generation failed");
+ goto done;
+ }
+ pair = scr_dyn_new_obj();
+ scr_dyn_obj_set(pair, "publicKey", 9, sw_key_new(public_key, kind, hash, public_usages, true));
+ scr_dyn_obj_set(pair, "privateKey", 10, sw_key_new(key, kind, hash, private_usages, extractable));
+done:
+ if (plain) scr_dyn_release(plain);
+ scr_wc_args_drop(&a, 3);
+ if (!pair) return scr_wc_rejected();
+ ScrPromise *p = scr_promise_new();
+ scr_crypto_defer_dyn(pair, NULL, scr_crypto_promise_callback(p), scr_crypto_promise_settle_dyn);
+ return p;
+}
+
+/* ── importKey ────────────────────────────────────────────────────────── */
+
+/* A JWK string member, or NULL. */
+static const ScrDyn *sw_jwk_member(const ScrDyn *jwk, const char *key) {
+ const ScrDyn *v = scr_dyn_obj_get(jwk, key, strlen(key));
+ return v && v->kind != SCR_DYN_UNDEF ? v : NULL;
+}
+
+static bool sw_jwk_is(const ScrDyn *v, const char *text) {
+ return v && v->kind == SCR_DYN_STR && v->v.str->len == strlen(text) &&
+ memcmp(v->v.str->data, text, v->v.str->len) == 0;
+}
+
+/* The JWK checks Node runs before reading the key: use, key_ops and ext. */
+static bool sw_jwk_checks(const ScrDyn *jwk, unsigned usages, bool extractable) {
+ const ScrDyn *use = sw_jwk_member(jwk, "use");
+ if (use && !sw_jwk_is(use, "sig")) {
+ scr_throw_domex("DataError", "Invalid JWK \"use\" Parameter");
+ return false;
+ }
+ const ScrDyn *ops = sw_jwk_member(jwk, "key_ops");
+ if (ops) {
+ if (ops->kind != SCR_DYN_ARR) {
+ scr_throw_domex("DataError", "Key operations and usage mismatch");
+ return false;
+ }
+ for (size_t u = 0; u < SCR_WC_COUNT(scr_wc_usages); u++) {
+ if (!(usages & (1u << u))) continue;
+ bool listed = false;
+ double n = scr_dyn_arr_len(ops);
+ for (double i = 0; i < n && !listed; i++) {
+ ScrDyn *item = scr_dyn_arr_at(ops, i);
+ listed = sw_jwk_is(item, scr_wc_usages[u]);
+ scr_dyn_release(item);
+ }
+ if (!listed) {
+ scr_throw_domex("DataError", "Key operations and usage mismatch");
+ return false;
+ }
+ }
+ }
+ const ScrDyn *ext = sw_jwk_member(jwk, "ext");
+ if (ext && ext->kind == SCR_DYN_BOOL && !ext->v.b && extractable) {
+ scr_throw_domex("DataError", "JWK \"ext\" Parameter and extractable mismatch");
+ return false;
+ }
+ return true;
+}
+
+/* The JWK "alg" an RSA key with this hash carries ("RS256", "PS384"). */
+static void sw_rsa_jwk_alg(SwKind kind, const char *hash, char out[8]) {
+ const char *bits = strcmp(hash, "SHA-1") == 0 ? "1" : hash + 4;
+ snprintf(out, 8, "%s%s", kind == SW_PSS ? "PS" : "RS", bits);
+}
+
+static ScrDyn *sw_import(const ScrDyn *args) {
+ static const char prefix[] = "Failed to execute 'importKey' on 'SubtleCrypto'";
+ if (!scr_wc_required(prefix, args, 5)) return NULL;
+ ScrWcArgs a;
+ scr_wc_args_take(&a, args, 5);
+ ScrDyn *result = NULL;
+ ScrBytes *data = NULL;
+ ScrDyn *jwk = NULL;
+ ScrDyn *plain = NULL;
+ const char *format = scr_wc_key_format(prefix, a.v[0]);
+ if (!format) goto done;
+ bool is_jwk = strcmp(format, "jwk") == 0;
+ if (is_jwk) {
+ if (!scr_wc_is_object(a.v[1])) {
+ scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type JsonWebKey.", prefix);
+ goto done;
+ }
+ jwk = scr_wc_plain(a.v[1]);
+ if (!jwk) goto done;
+ } else {
+ data = scr_wc_buffer_source(prefix, 1, a.v[1]);
+ if (!data) goto done;
+ }
+ if (!scr_wc_is_object(a.v[2])) {
+ ScrStr *s = scr_wc_dom_string(a.v[2]);
+ if (!s) goto done;
+ scr_str_release(s);
+ }
+ bool extractable = scr_dyn_truthy(a.v[3]);
+ long usages = scr_wc_key_usages(prefix, 4, a.v[4]);
+ if (usages < 0) goto done;
+ const char *name = sw_algorithm_name(a.v[2]);
+ if (!name) goto done;
+ SwKind kind = sw_kind_of(name);
+ plain = scr_wc_plain(a.v[2]);
+ if (!plain) goto done;
+ const char *curve = NULL;
+ const char *hash = NULL;
+ if (kind == SW_ED25519) {
+ /* no parameters */
+ } else if (kind == SW_ECDSA) {
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member("EcKeyImportParams", "namedCurve");
+ goto done;
+ }
+ curve = sw_named_curve(plain, "EcKeyImportParams");
+ if (!curve) goto done;
+ } else {
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member("RsaHashedImportParams", "hash");
+ goto done;
+ }
+ hash = sw_hash(plain, "RsaHashedImportParams", true, "importKey");
+ if (!hash) goto done;
+ }
+ bool private_key;
+ if (is_jwk) {
+ private_key = sw_jwk_member(jwk, "d") != NULL;
+ } else if (strcmp(format, "pkcs8") == 0) {
+ private_key = true;
+ } else if (strcmp(format, "spki") == 0 ||
+ ((kind == SW_ECDSA || kind == SW_ED25519) && strcmp(format, "raw") == 0)) {
+ private_key = false;
+ } else {
+ char message[96];
+ snprintf(message, sizeof message, "Unable to import %s using %s format", sw_names[kind], format);
+ scr_throw_domex("NotSupportedError", message);
+ goto done;
+ }
+ unsigned allowed = private_key ? SCR_WC_USAGE_SIGN : SCR_WC_USAGE_VERIFY;
+ ScrPkiKey *key = NULL;
+ /* An Ed25519 private key derives its public key in the Ed25519 unit. */
+ if (kind == SW_ED25519 && private_key && !scr_pki_ed25519_ready()) goto done;
+ if (is_jwk) {
+ const ScrDyn *kty = sw_jwk_member(jwk, "kty");
+ if (!kty) {
+ scr_throw_domex("DataError", "Invalid keyData");
+ goto done;
+ }
+ if (!sw_jwk_is(kty, kind == SW_ECDSA ? "EC" : kind == SW_ED25519 ? "OKP" : "RSA")) {
+ scr_throw_domex("DataError", "Invalid JWK \"kty\" Parameter");
+ goto done;
+ }
+ if (kind == SW_ECDSA && !sw_jwk_is(sw_jwk_member(jwk, "crv"), curve)) {
+ scr_throw_domex("DataError", "JWK \"crv\" does not match the requested algorithm");
+ goto done;
+ }
+ if (kind == SW_ED25519 && !sw_jwk_is(sw_jwk_member(jwk, "crv"), "Ed25519")) {
+ scr_throw_domex("DataError", "JWK \"crv\" Parameter and algorithm name mismatch");
+ goto done;
+ }
+ if ((unsigned)usages & ~allowed) {
+ sw_unsupported_usage(kind);
+ goto done;
+ }
+ if (!sw_jwk_checks(jwk, (unsigned)usages, extractable)) goto done;
+ const ScrDyn *alg = sw_jwk_member(jwk, "alg");
+ if (kind == SW_ED25519 && alg && !sw_jwk_is(alg, "Ed25519") && !sw_jwk_is(alg, "EdDSA")) {
+ scr_throw_domex("DataError", "JWK \"alg\" does not match the requested algorithm");
+ goto done;
+ }
+ if (sw_is_rsa_kind(kind) && alg) {
+ char expected[8];
+ sw_rsa_jwk_alg(kind, hash, expected);
+ if (!sw_jwk_is(alg, expected)) {
+ scr_throw_domex("DataError", "JWK \"alg\" does not match the requested algorithm");
+ goto done;
+ }
+ }
+ key = scr_pki_key_from_jwk(jwk, private_key);
+ } else {
+ if ((unsigned)usages & ~allowed) {
+ sw_unsupported_usage(kind);
+ goto done;
+ }
+ ScrBytes *view = data;
+ SCR_BYTES_SNAPSHOT(view);
+ size_t len = (size_t)scr_bytes_byte_len(view);
+ if (strcmp(format, "raw") == 0)
+ key = kind == SW_ED25519 ? scr_pki_key_from_ed25519_raw(view->data, len)
+ : scr_pki_key_from_point(curve, view->data, len);
+ else key = scr_pki_key_parse_der(view->data, len, private_key);
+ }
+ if (!key) {
+ scr_throw_domex("DataError", "Invalid keyData");
+ goto done;
+ }
+ bool key_matches = kind == SW_ED25519 ? scr_pki_key_is_ed25519(key)
+ : kind == SW_ECDSA ? !scr_pki_key_is_rsa(key) && !scr_pki_key_is_ed25519(key)
+ : scr_pki_key_is_rsa(key);
+ if (!key_matches) {
+ scr_pki_key_release(key);
+ scr_throw_domex("DataError", "Invalid key type");
+ goto done;
+ }
+ if (kind == SW_ECDSA && strcmp(scr_pki_key_curve(key), curve) != 0) {
+ scr_pki_key_release(key);
+ scr_throw_domex("DataError", "Named curve mismatch");
+ goto done;
+ }
+ if (private_key && usages == 0) {
+ scr_pki_key_release(key);
+ scr_throw_domex("SyntaxError", "Usages cannot be empty when importing a private key.");
+ goto done;
+ }
+ result = sw_key_new(key, kind, hash, (unsigned)usages, extractable);
+done:
+ if (data) scr_bytes_release(data);
+ if (jwk) scr_dyn_release(jwk);
+ if (plain) scr_dyn_release(plain);
+ scr_wc_args_drop(&a, 5);
+ return result;
+}
+
+static ScrPromise *sw_import_key(const ScrDyn *args) {
+ ScrDyn *key = sw_import(args);
+ if (!key) return scr_wc_rejected();
+ return scr_promise_settled_ref(key, &scr_dyn_retain_v, &scr_dyn_release_v, NULL);
+}
+
+/* ── exportKey ────────────────────────────────────────────────────────── */
+
+static ScrDyn *sw_array_buffer(ScrBytes *bytes) {
+ ScrDyn *out = scr_array_buffer_from_bytes(bytes);
+ scr_bytes_release(bytes);
+ return out;
+}
+
+static ScrDyn *sw_export(const ScrDyn *args) {
+ ScrWcArgs a;
+ scr_wc_args_take(&a, args, 2);
+ ScrDyn *result = NULL;
+ const char *format = scr_wc_key_format("Failed to execute 'exportKey' on 'SubtleCrypto'", a.v[0]);
+ SwKey *k = sw_key_of(a.v[1]);
+ bool private_key = scr_pki_key_is_private(k->key);
+ if (!format) goto done;
+ if (!k->extractable) {
+ scr_throw_domex("InvalidAccessError", "key is not extractable");
+ goto done;
+ }
+ if (strcmp(format, "spki") == 0 || strcmp(format, "pkcs8") == 0) {
+ bool want_private = strcmp(format, "pkcs8") == 0;
+ if (want_private != private_key) {
+ scr_throw_domex("InvalidAccessError", want_private ? "Key must be a private key" : "Key must be a public key");
+ goto done;
+ }
+ ScrBytes *der = scr_pki_export_der(k->key);
+ if (!der) {
+ scr_throw_domex("OperationError", "Failed to export key");
+ goto done;
+ }
+ result = sw_array_buffer(der);
+ } else if (strcmp(format, "raw") == 0 && (k->kind == SW_ECDSA || k->kind == SW_ED25519) &&
+ !private_key) {
+ ScrBytes *point = k->kind == SW_ED25519 ? scr_pki_export_ed25519_raw(k->key) : scr_pki_export_point(k->key);
+ if (!point) {
+ scr_throw_domex("OperationError", "Failed to export key");
+ goto done;
+ }
+ result = sw_array_buffer(point);
+ } else if (strcmp(format, "jwk") == 0) {
+ ScrDyn *members = scr_pki_export_jwk(k->key);
+ ScrDyn *jwk = scr_dyn_new_obj();
+ scr_dyn_obj_set(jwk, "key_ops", 7, sw_usage_array(k->usages));
+ scr_dyn_obj_set(jwk, "ext", 3, scr_dyn_new_bool(k->extractable));
+ if (sw_is_rsa_kind(k->kind)) {
+ char alg[8];
+ sw_rsa_jwk_alg(k->kind, k->hash, alg);
+ scr_dyn_obj_set(jwk, "alg", 3, scr_wc_cstr(alg));
+ } else if (k->kind == SW_ED25519) {
+ scr_dyn_obj_set(jwk, "alg", 3, scr_wc_cstr("Ed25519"));
+ }
+ /* The key members in Node's order for each key type. */
+ static const char *const rsa_names[] = {"kty", "n", "e", "d", "p", "q", "dp", "dq", "qi"};
+ static const char *const ec_names[] = {"kty", "x", "y", "crv", "d"};
+ static const char *const okp_names[] = {"kty", "crv", "x", "d"};
+ const char *const *names = sw_is_rsa_kind(k->kind) ? rsa_names : k->kind == SW_ED25519 ? okp_names : ec_names;
+ size_t count = sw_is_rsa_kind(k->kind) ? SCR_WC_COUNT(rsa_names)
+ : k->kind == SW_ED25519 ? SCR_WC_COUNT(okp_names)
+ : SCR_WC_COUNT(ec_names);
+ for (size_t i = 0; i < count; i++) {
+ const ScrDyn *v = scr_dyn_obj_get(members, names[i], strlen(names[i]));
+ if (v) scr_dyn_obj_set(jwk, names[i], strlen(names[i]), scr_dyn_retain((ScrDyn *)v));
+ }
+ scr_dyn_release(members);
+ result = jwk;
+ } else {
+ char message[128];
+ snprintf(message, sizeof message, "Unable to export %s %s key using %s format", sw_names[k->kind],
+ private_key ? "private" : "public", format);
+ scr_throw_domex("NotSupportedError", message);
+ }
+done:
+ scr_wc_args_drop(&a, 2);
+ return result;
+}
+
+static ScrPromise *sw_export_key(const ScrDyn *args) {
+ ScrDyn *value = sw_export(args);
+ if (!value) return scr_wc_rejected();
+ return scr_promise_settled_ref(value, &scr_dyn_retain_v, &scr_dyn_release_v, NULL);
+}
+
+/* ── sign and verify ──────────────────────────────────────────────────── */
+
+/* The deferred verify result: a dyn boolean. */
+static void sw_settle_verified(ScrClosure *cb, ScrError *error, ScrBytes *value) {
+ (void)error;
+ ScrPromise *p;
+ memcpy(&p, &cb->caps[0]->slot, sizeof p);
+ ScrDyn *verified = (ScrDyn *)value;
+ scr_promise_fulfill_bool(p, scr_dyn_truthy(verified));
+ scr_dyn_release(verified);
+}
+
+static ScrPromise *sw_sign_verify(const ScrDyn *args, bool verify) {
+ const char *prefix = verify ? "Failed to execute 'verify' on 'SubtleCrypto'"
+ : "Failed to execute 'sign' on 'SubtleCrypto'";
+ const char *method = verify ? "verify" : "sign";
+ size_t n = verify ? 4 : 3;
+ if (!scr_wc_required(prefix, args, n)) return scr_wc_rejected();
+ ScrWcArgs a;
+ scr_wc_args_take(&a, args, n);
+ ScrPromise *result = NULL;
+ ScrBytes *data = NULL;
+ ScrBytes *signature = NULL;
+ ScrDyn *plain = NULL;
+ if (!scr_wc_is_object(a.v[0])) {
+ ScrStr *s = scr_wc_dom_string(a.v[0]);
+ if (!s) goto done;
+ scr_str_release(s);
+ }
+ SwKey *k = sw_key_of(a.v[1]);
+ if (!k && !scr_webcrypto_is_crypto_key(a.v[1])) {
+ scr_wc_type_error("ERR_INVALID_ARG_TYPE", "%s: 2nd argument is not of type CryptoKey.", prefix);
+ goto done;
+ }
+ if (verify) {
+ signature = scr_wc_buffer_source(prefix, 2, a.v[2]);
+ if (!signature) goto done;
+ }
+ data = scr_wc_buffer_source(prefix, verify ? 3 : 2, a.v[verify ? 3 : 2]);
+ if (!data) goto done;
+ const char *name = sw_algorithm_name(a.v[0]);
+ if (!name) goto done;
+ SwKind kind = sw_kind_of(name);
+ plain = scr_wc_plain(a.v[0]);
+ if (!plain) goto done;
+ const char *hash = NULL;
+ int salt = 0;
+ if (kind == SW_ED25519) {
+ /* no parameters */
+ } else if (kind == SW_ECDSA) {
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member("EcdsaParams", "hash");
+ goto done;
+ }
+ hash = sw_hash(plain, "EcdsaParams", false, method);
+ if (!hash) goto done;
+ } else if (kind == SW_PSS) {
+ if (!scr_wc_is_object(plain)) {
+ scr_wc_missing_member("RsaPssParams", "saltLength");
+ goto done;
+ }
+ ScrDyn *value = sw_required(plain, "RsaPssParams", "saltLength");
+ if (!value) goto done;
+ double x;
+ bool ok = sw_unsigned_long(value, "saltLength", &x);
+ scr_dyn_release(value);
+ if (!ok) goto done;
+ salt = x > 65536 ? 65536 : (int)x;
+ }
+ if (!k || k->kind != kind) {
+ scr_throw_domex("InvalidAccessError", "Key algorithm mismatch");
+ goto done;
+ }
+ if (!(k->usages & (verify ? SCR_WC_USAGE_VERIFY : SCR_WC_USAGE_SIGN))) {
+ char message[64];
+ snprintf(message, sizeof message, "Unable to use this key to %s", method);
+ scr_throw_domex("InvalidAccessError", message);
+ goto done;
+ }
+ if (sw_is_rsa_kind(kind)) hash = k->hash;
+ if (kind == SW_ED25519 && !scr_pki_ed25519_ready()) goto done;
+ {
+ ScrBytes *view = data;
+ SCR_BYTES_SNAPSHOT(view);
+ size_t len = (size_t)scr_bytes_byte_len(view);
+ if (verify) {
+ ScrBytes *sig = signature;
+ SCR_BYTES_SNAPSHOT(sig);
+ bool ok = scr_pki_verify(k->key, hash, kind == SW_PSS, salt, view->data, len, sig->data,
+ (size_t)scr_bytes_byte_len(sig));
+ result = scr_promise_new();
+ scr_crypto_defer_dyn(scr_dyn_new_bool(ok), NULL, scr_crypto_promise_callback(result), sw_settle_verified);
+ } else {
+ ScrBytes *out = scr_pki_sign(k->key, hash, kind == SW_PSS, salt, view->data, len);
+ if (!out) {
+ scr_throw_domex("OperationError", "Failed to sign");
+ goto done;
+ }
+ result = scr_promise_new();
+ scr_crypto_defer_bytes(out, NULL, scr_crypto_promise_callback(result), scr_crypto_promise_settle_array_buffer);
+ }
+ }
+done:
+ if (data) scr_bytes_release(data);
+ if (signature) scr_bytes_release(signature);
+ if (plain) scr_dyn_release(plain);
+ scr_wc_args_drop(&a, n);
+ return result ? result : scr_wc_rejected();
+}
+
+static ScrPromise *sw_sign(const ScrDyn *args) { return sw_sign_verify(args, false); }
+
+static ScrPromise *sw_verify(const ScrDyn *args) { return sw_sign_verify(args, true); }
+
+static const ScrWcAsymmetric sw_table = {
+ .generate_key = sw_generate_key,
+ .import_key = sw_import_key,
+ .export_key = sw_export_key,
+ .sign = sw_sign,
+ .verify = sw_verify,
+ .is_key = sw_is_key,
+};
+
+/* Installed before main: scr_webcrypto.c dispatches these algorithms
+ * here. */
+__attribute__((constructor)) static void sw_install(void) { scr_wc_asymmetric = &sw_table; }
diff --git a/packages/runtime/vendor/README.md b/packages/runtime/vendor/README.md
index 977179609..7ab8cf6b0 100644
--- a/packages/runtime/vendor/README.md
+++ b/packages/runtime/vendor/README.md
@@ -48,3 +48,15 @@ The objects build lazily on the first zlib-using cross compile into the per-user
The TLS provider behind node:tls/node:https (see the design note atop packages/runtime/src/scr_tls.c for why mbedTLS over SecureTransport/BoringSSL/libtls). Only `include/` and `library/` are vendored (LICENSE beside them) — tests, docs, programs, scripts, and CMake machinery are not. No vendored file is modified and no custom config is applied (the stock `mbedtls_config.h` builds every `library/*.c` standalone with clang); to update, re-fetch the release tarball, copy the same two directories, and bump `MBEDTLS_VERSION` in packages/compiler/src/backend/vendor-archives.ts.
The archive (libmbedtls.a) is prebuilt best-effort during npm installation (or explicitly by `scriptc cache warm`) and otherwise built lazily on the first TLS-using compile. It lives under the per-user build cache's `vendor/mbedtls----/` directory — one flavor per lane (plain, asan), native platform/architecture or explicit cross target, and compiler environment/identity, the libqjs.a pattern. TLS-free binaries never compile or link any of this.
+
+## libsodium/
+
+- Project: https://github.com/jedisct1/libsodium
+- Version: 1.0.20 (release tarball libsodium-1.0.20.tar.gz, sha256 ebb65ef6ca439333c2bb41a0c1990587288da07f6c7fd07cb3a18cc18d30ce19)
+- License: ISC (see libsodium/LICENSE)
+
+The Ed25519 arithmetic behind node:crypto and WebCrypto Ed25519 keys (see packages/runtime/src/scr_ed25519.c). Its verification matches Node 24: it rejects a non-canonical S and the small-order R and public-key encodings, then checks the cofactorless equation. Only the ref10 Ed25519 subset is vendored, flat in one directory: `crypto_core/ed25519/ref10/ed25519_ref10.c` with its `fe_25_5/` headers, `crypto_sign/ed25519/ref10/{sign,open,keypair}.c` and `sign_ed25519_ref10.h`, `crypto_hash/sha512/cp/hash_sha512_cp.c`, `crypto_verify/verify.c`, and the public and private headers they include. The 64-bit `fe_51` field code (used only with `HAVE_TI_MODE`) and the rest of the library are not vendored.
+
+The only modification to upstream files: `#include "private/x.h"` flattened to `#include "x.h"` in ed25519_ref10.c, sign.c, open.c, keypair.c, hash_sha512_cp.c and ed25519_ref10_fe_25_5.h, so the directory is self-contained. scr_ed25519.c textually includes the .c files and supplies `sodium_memzero`, `sodium_memcmp`, `sodium_is_zero` and `randombytes_buf` (from `arc4random_buf`) in place of libsodium's utils.c and randombytes.c. To update, re-fetch the release tarball, copy the same files, and re-apply that include flattening.
+
+The unit compiles only into programs that use asymmetric keys and name Ed25519 in a string literal (the `ed25519` runtime feature); other binaries never compile or link any of this.
diff --git a/packages/runtime/vendor/libsodium/LICENSE b/packages/runtime/vendor/libsodium/LICENSE
new file mode 100644
index 000000000..6bce7f058
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/LICENSE
@@ -0,0 +1,18 @@
+/*
+ * ISC License
+ *
+ * Copyright (c) 2013-2024
+ * Frank Denis
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
diff --git a/packages/runtime/vendor/libsodium/README.md b/packages/runtime/vendor/libsodium/README.md
new file mode 100644
index 000000000..5207d8ad6
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/README.md
@@ -0,0 +1,7 @@
+# libsodium 1.0.20 (Ed25519 subset)
+
+- Source: https://download.libsodium.org/libsodium/releases/libsodium-1.0.20.tar.gz (the same tarball as the GitHub release 1.0.20-RELEASE)
+- sha256: ebb65ef6ca439333c2bb41a0c1990587288da07f6c7fd07cb3a18cc18d30ce19
+- License: ISC, see LICENSE
+
+See ../README.md for the files vendored, the one modification, and how to update.
diff --git a/packages/runtime/vendor/libsodium/common.h b/packages/runtime/vendor/libsodium/common.h
new file mode 100644
index 000000000..63f0d2b2d
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/common.h
@@ -0,0 +1,285 @@
+#ifndef common_H
+#define common_H 1
+
+#if !defined(_MSC_VER) && !defined(DEV_MODE) && 0
+# warning *** This is unstable, untested, development code.
+# warning It might not compile. It might not work as expected.
+# warning It might be totally insecure.
+# warning Do not use this except if you are planning to contribute code.
+# warning Use releases available at https://download.libsodium.org/libsodium/releases/ instead.
+# warning Alternatively, use the "stable" branch in the git repository.
+#endif
+
+#if !defined(_MSC_VER) && (!defined(CONFIGURED) || CONFIGURED != 1)
+# warning *** The library is being compiled using an undocumented method.
+# warning This is not supported. It has not been tested, it might not
+# warning work as expected, and performance is likely to be suboptimal.
+#endif
+
+#include
+#include
+#include
+
+#define COMPILER_ASSERT(X) (void) sizeof(char[(X) ? 1 : -1])
+
+#ifdef HAVE_TI_MODE
+# if defined(__SIZEOF_INT128__)
+typedef unsigned __int128 uint128_t;
+# else
+typedef unsigned uint128_t __attribute__((mode(TI)));
+# endif
+#endif
+
+#define ROTL32(X, B) rotl32((X), (B))
+static inline uint32_t
+rotl32(const uint32_t x, const int b)
+{
+ return (x << b) | (x >> (32 - b));
+}
+
+#define ROTL64(X, B) rotl64((X), (B))
+static inline uint64_t
+rotl64(const uint64_t x, const int b)
+{
+ return (x << b) | (x >> (64 - b));
+}
+
+#define ROTR32(X, B) rotr32((X), (B))
+static inline uint32_t
+rotr32(const uint32_t x, const int b)
+{
+ return (x >> b) | (x << (32 - b));
+}
+
+#define ROTR64(X, B) rotr64((X), (B))
+static inline uint64_t
+rotr64(const uint64_t x, const int b)
+{
+ return (x >> b) | (x << (64 - b));
+}
+
+#define LOAD64_LE(SRC) load64_le(SRC)
+static inline uint64_t
+load64_le(const uint8_t src[8])
+{
+#ifdef NATIVE_LITTLE_ENDIAN
+ uint64_t w;
+ memcpy(&w, src, sizeof w);
+ return w;
+#else
+ uint64_t w = (uint64_t) src[0];
+ w |= (uint64_t) src[1] << 8;
+ w |= (uint64_t) src[2] << 16;
+ w |= (uint64_t) src[3] << 24;
+ w |= (uint64_t) src[4] << 32;
+ w |= (uint64_t) src[5] << 40;
+ w |= (uint64_t) src[6] << 48;
+ w |= (uint64_t) src[7] << 56;
+ return w;
+#endif
+}
+
+#define STORE64_LE(DST, W) store64_le((DST), (W))
+static inline void
+store64_le(uint8_t dst[8], uint64_t w)
+{
+#ifdef NATIVE_LITTLE_ENDIAN
+ memcpy(dst, &w, sizeof w);
+#else
+ dst[0] = (uint8_t) w; w >>= 8;
+ dst[1] = (uint8_t) w; w >>= 8;
+ dst[2] = (uint8_t) w; w >>= 8;
+ dst[3] = (uint8_t) w; w >>= 8;
+ dst[4] = (uint8_t) w; w >>= 8;
+ dst[5] = (uint8_t) w; w >>= 8;
+ dst[6] = (uint8_t) w; w >>= 8;
+ dst[7] = (uint8_t) w;
+#endif
+}
+
+#define LOAD32_LE(SRC) load32_le(SRC)
+static inline uint32_t
+load32_le(const uint8_t src[4])
+{
+#ifdef NATIVE_LITTLE_ENDIAN
+ uint32_t w;
+ memcpy(&w, src, sizeof w);
+ return w;
+#else
+ uint32_t w = (uint32_t) src[0];
+ w |= (uint32_t) src[1] << 8;
+ w |= (uint32_t) src[2] << 16;
+ w |= (uint32_t) src[3] << 24;
+ return w;
+#endif
+}
+
+#define STORE32_LE(DST, W) store32_le((DST), (W))
+static inline void
+store32_le(uint8_t dst[4], uint32_t w)
+{
+#ifdef NATIVE_LITTLE_ENDIAN
+ memcpy(dst, &w, sizeof w);
+#else
+ dst[0] = (uint8_t) w; w >>= 8;
+ dst[1] = (uint8_t) w; w >>= 8;
+ dst[2] = (uint8_t) w; w >>= 8;
+ dst[3] = (uint8_t) w;
+#endif
+}
+
+/* ----- */
+
+#define LOAD64_BE(SRC) load64_be(SRC)
+static inline uint64_t
+load64_be(const uint8_t src[8])
+{
+#ifdef NATIVE_BIG_ENDIAN
+ uint64_t w;
+ memcpy(&w, src, sizeof w);
+ return w;
+#else
+ uint64_t w = (uint64_t) src[7];
+ w |= (uint64_t) src[6] << 8;
+ w |= (uint64_t) src[5] << 16;
+ w |= (uint64_t) src[4] << 24;
+ w |= (uint64_t) src[3] << 32;
+ w |= (uint64_t) src[2] << 40;
+ w |= (uint64_t) src[1] << 48;
+ w |= (uint64_t) src[0] << 56;
+ return w;
+#endif
+}
+
+#define STORE64_BE(DST, W) store64_be((DST), (W))
+static inline void
+store64_be(uint8_t dst[8], uint64_t w)
+{
+#ifdef NATIVE_BIG_ENDIAN
+ memcpy(dst, &w, sizeof w);
+#else
+ dst[7] = (uint8_t) w; w >>= 8;
+ dst[6] = (uint8_t) w; w >>= 8;
+ dst[5] = (uint8_t) w; w >>= 8;
+ dst[4] = (uint8_t) w; w >>= 8;
+ dst[3] = (uint8_t) w; w >>= 8;
+ dst[2] = (uint8_t) w; w >>= 8;
+ dst[1] = (uint8_t) w; w >>= 8;
+ dst[0] = (uint8_t) w;
+#endif
+}
+
+#define LOAD32_BE(SRC) load32_be(SRC)
+static inline uint32_t
+load32_be(const uint8_t src[4])
+{
+#ifdef NATIVE_BIG_ENDIAN
+ uint32_t w;
+ memcpy(&w, src, sizeof w);
+ return w;
+#else
+ uint32_t w = (uint32_t) src[3];
+ w |= (uint32_t) src[2] << 8;
+ w |= (uint32_t) src[1] << 16;
+ w |= (uint32_t) src[0] << 24;
+ return w;
+#endif
+}
+
+#define STORE32_BE(DST, W) store32_be((DST), (W))
+static inline void
+store32_be(uint8_t dst[4], uint32_t w)
+{
+#ifdef NATIVE_BIG_ENDIAN
+ memcpy(dst, &w, sizeof w);
+#else
+ dst[3] = (uint8_t) w; w >>= 8;
+ dst[2] = (uint8_t) w; w >>= 8;
+ dst[1] = (uint8_t) w; w >>= 8;
+ dst[0] = (uint8_t) w;
+#endif
+}
+
+#define XOR_BUF(OUT, IN, N) xor_buf((OUT), (IN), (N))
+static inline void
+xor_buf(unsigned char *out, const unsigned char *in, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ out[i] ^= in[i];
+ }
+}
+
+#if !defined(__clang__) && !defined(__GNUC__)
+# ifdef __attribute__
+# undef __attribute__
+# endif
+# define __attribute__(a)
+#endif
+
+#ifndef CRYPTO_ALIGN
+# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
+# define CRYPTO_ALIGN(x) __declspec(align(x))
+# else
+# define CRYPTO_ALIGN(x) __attribute__ ((aligned(x)))
+# endif
+#endif
+
+#ifdef _MSC_VER
+
+# if defined(_M_X64) || defined(_M_IX86)
+# include
+
+# define HAVE_INTRIN_H 1
+# define HAVE_MMINTRIN_H 1
+# define HAVE_EMMINTRIN_H 1
+# define HAVE_PMMINTRIN_H 1
+# define HAVE_TMMINTRIN_H 1
+# define HAVE_SMMINTRIN_H 1
+# define HAVE_AVXINTRIN_H 1
+# if _MSC_VER >= 1600
+# define HAVE_WMMINTRIN_H 1
+# endif
+# if _MSC_VER >= 1700 && defined(_M_X64)
+# define HAVE_AVX2INTRIN_H 1
+# endif
+# if _MSC_VER >= 1910 && defined(_M_X64)
+# define HAVE_AVX512FINTRIN_H 1
+# endif
+
+# elif defined(_M_ARM64)
+
+# ifndef __ARM_ARCH
+# define __ARM_ARCH 1
+# endif
+# ifndef __ARM_NEON
+# define __ARM_NEON 1
+# endif
+# define HAVE_ARMCRYPTO 1
+
+# endif /* _MSC_VER */
+
+#elif defined(HAVE_INTRIN_H)
+# include
+#endif
+
+#ifdef HAVE_LIBCTGRIND
+extern void ct_poison (const void *, size_t);
+extern void ct_unpoison(const void *, size_t);
+# define POISON(X, L) ct_poison((X), (L))
+# define UNPOISON(X, L) ct_unpoison((X), (L))
+#else
+# define POISON(X, L) (void) 0
+# define UNPOISON(X, L) (void) 0
+#endif
+
+#ifdef HAVE_GCC_MEMORY_FENCES
+# define ACQUIRE_FENCE __atomic_thread_fence(__ATOMIC_ACQUIRE)
+#elif defined(HAVE_C11_MEMORY_FENCES)
+# define ACQUIRE_FENCE atomic_thread_fence(memory_order_acquire)
+#else
+# define ACQUIRE_FENCE (void) 0
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_hash_sha512.h b/packages/runtime/vendor/libsodium/crypto_hash_sha512.h
new file mode 100644
index 000000000..5b690fb25
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_hash_sha512.h
@@ -0,0 +1,60 @@
+#ifndef crypto_hash_sha512_H
+#define crypto_hash_sha512_H
+
+/*
+ * WARNING: Unless you absolutely need to use SHA512 for interoperability,
+ * purposes, you might want to consider crypto_generichash() instead.
+ * Unlike SHA512, crypto_generichash() is not vulnerable to length
+ * extension attacks.
+ */
+
+#include
+#include
+#include
+
+#include "export.h"
+
+#ifdef __cplusplus
+# ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wlong-long"
+# endif
+extern "C" {
+#endif
+
+typedef struct crypto_hash_sha512_state {
+ uint64_t state[8];
+ uint64_t count[2];
+ uint8_t buf[128];
+} crypto_hash_sha512_state;
+
+SODIUM_EXPORT
+size_t crypto_hash_sha512_statebytes(void);
+
+#define crypto_hash_sha512_BYTES 64U
+SODIUM_EXPORT
+size_t crypto_hash_sha512_bytes(void);
+
+SODIUM_EXPORT
+int crypto_hash_sha512(unsigned char *out, const unsigned char *in,
+ unsigned long long inlen) __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int crypto_hash_sha512_init(crypto_hash_sha512_state *state)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_hash_sha512_update(crypto_hash_sha512_state *state,
+ const unsigned char *in,
+ unsigned long long inlen)
+ __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int crypto_hash_sha512_final(crypto_hash_sha512_state *state,
+ unsigned char *out)
+ __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_scalarmult_curve25519.h b/packages/runtime/vendor/libsodium/crypto_scalarmult_curve25519.h
new file mode 100644
index 000000000..60e9d0c5a
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_scalarmult_curve25519.h
@@ -0,0 +1,42 @@
+#ifndef crypto_scalarmult_curve25519_H
+#define crypto_scalarmult_curve25519_H
+
+#include
+
+#include "export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define crypto_scalarmult_curve25519_BYTES 32U
+SODIUM_EXPORT
+size_t crypto_scalarmult_curve25519_bytes(void);
+
+#define crypto_scalarmult_curve25519_SCALARBYTES 32U
+SODIUM_EXPORT
+size_t crypto_scalarmult_curve25519_scalarbytes(void);
+
+/*
+ * NOTE: Do not use the result of this function directly for key exchange.
+ *
+ * Hash the result with the public keys in order to compute a shared
+ * secret key: H(q || client_pk || server_pk)
+ *
+ * Or unless this is not an option, use the crypto_kx() API instead.
+ */
+SODIUM_EXPORT
+int crypto_scalarmult_curve25519(unsigned char *q, const unsigned char *n,
+ const unsigned char *p)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_scalarmult_curve25519_base(unsigned char *q,
+ const unsigned char *n)
+ __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_sign_ed25519.h b/packages/runtime/vendor/libsodium/crypto_sign_ed25519.h
new file mode 100644
index 000000000..0fdac42d3
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_sign_ed25519.h
@@ -0,0 +1,124 @@
+#ifndef crypto_sign_ed25519_H
+#define crypto_sign_ed25519_H
+
+#include
+#include "crypto_hash_sha512.h"
+#include "export.h"
+
+#ifdef __cplusplus
+# ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wlong-long"
+# endif
+extern "C" {
+#endif
+
+typedef struct crypto_sign_ed25519ph_state {
+ crypto_hash_sha512_state hs;
+} crypto_sign_ed25519ph_state;
+
+SODIUM_EXPORT
+size_t crypto_sign_ed25519ph_statebytes(void);
+
+#define crypto_sign_ed25519_BYTES 64U
+SODIUM_EXPORT
+size_t crypto_sign_ed25519_bytes(void);
+
+#define crypto_sign_ed25519_SEEDBYTES 32U
+SODIUM_EXPORT
+size_t crypto_sign_ed25519_seedbytes(void);
+
+#define crypto_sign_ed25519_PUBLICKEYBYTES 32U
+SODIUM_EXPORT
+size_t crypto_sign_ed25519_publickeybytes(void);
+
+#define crypto_sign_ed25519_SECRETKEYBYTES (32U + 32U)
+SODIUM_EXPORT
+size_t crypto_sign_ed25519_secretkeybytes(void);
+
+#define crypto_sign_ed25519_MESSAGEBYTES_MAX (SODIUM_SIZE_MAX - crypto_sign_ed25519_BYTES)
+SODIUM_EXPORT
+size_t crypto_sign_ed25519_messagebytes_max(void);
+
+SODIUM_EXPORT
+int crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen_p,
+ const unsigned char *m, unsigned long long mlen,
+ const unsigned char *sk)
+ __attribute__ ((nonnull(1, 5)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen_p,
+ const unsigned char *sm, unsigned long long smlen,
+ const unsigned char *pk)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(3, 5)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_detached(unsigned char *sig,
+ unsigned long long *siglen_p,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *sk)
+ __attribute__ ((nonnull(1, 5)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_verify_detached(const unsigned char *sig,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *pk)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull(1, 4)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_seed_keypair(unsigned char *pk, unsigned char *sk,
+ const unsigned char *seed)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_pk_to_curve25519(unsigned char *curve25519_pk,
+ const unsigned char *ed25519_pk)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_sk_to_curve25519(unsigned char *curve25519_sk,
+ const unsigned char *ed25519_sk)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_sk_to_seed(unsigned char *seed,
+ const unsigned char *sk)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519_sk_to_pk(unsigned char *pk, const unsigned char *sk)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519ph_init(crypto_sign_ed25519ph_state *state)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519ph_update(crypto_sign_ed25519ph_state *state,
+ const unsigned char *m,
+ unsigned long long mlen)
+ __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519ph_final_create(crypto_sign_ed25519ph_state *state,
+ unsigned char *sig,
+ unsigned long long *siglen_p,
+ const unsigned char *sk)
+ __attribute__ ((nonnull(1, 2, 4)));
+
+SODIUM_EXPORT
+int crypto_sign_ed25519ph_final_verify(crypto_sign_ed25519ph_state *state,
+ const unsigned char *sig,
+ const unsigned char *pk)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_verify_16.h b/packages/runtime/vendor/libsodium/crypto_verify_16.h
new file mode 100644
index 000000000..7b9c8077a
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_verify_16.h
@@ -0,0 +1,23 @@
+#ifndef crypto_verify_16_H
+#define crypto_verify_16_H
+
+#include
+#include "export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define crypto_verify_16_BYTES 16U
+SODIUM_EXPORT
+size_t crypto_verify_16_bytes(void);
+
+SODIUM_EXPORT
+int crypto_verify_16(const unsigned char *x, const unsigned char *y)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_verify_32.h b/packages/runtime/vendor/libsodium/crypto_verify_32.h
new file mode 100644
index 000000000..9b0f4529f
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_verify_32.h
@@ -0,0 +1,23 @@
+#ifndef crypto_verify_32_H
+#define crypto_verify_32_H
+
+#include
+#include "export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define crypto_verify_32_BYTES 32U
+SODIUM_EXPORT
+size_t crypto_verify_32_bytes(void);
+
+SODIUM_EXPORT
+int crypto_verify_32(const unsigned char *x, const unsigned char *y)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/crypto_verify_64.h b/packages/runtime/vendor/libsodium/crypto_verify_64.h
new file mode 100644
index 000000000..c83b73025
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/crypto_verify_64.h
@@ -0,0 +1,23 @@
+#ifndef crypto_verify_64_H
+#define crypto_verify_64_H
+
+#include
+#include "export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define crypto_verify_64_BYTES 64U
+SODIUM_EXPORT
+size_t crypto_verify_64_bytes(void);
+
+SODIUM_EXPORT
+int crypto_verify_64(const unsigned char *x, const unsigned char *y)
+ __attribute__ ((warn_unused_result)) __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/ed25519_ref10.c b/packages/runtime/vendor/libsodium/ed25519_ref10.c
new file mode 100644
index 000000000..ac7a41492
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/ed25519_ref10.c
@@ -0,0 +1,2858 @@
+#include
+#include
+#include
+#include
+
+#include "crypto_verify_32.h"
+#include "common.h"
+#include "ed25519_ref10.h"
+#include "utils.h"
+
+static inline uint64_t
+load_3(const unsigned char *in)
+{
+ uint64_t result;
+
+ result = (uint64_t) in[0];
+ result |= ((uint64_t) in[1]) << 8;
+ result |= ((uint64_t) in[2]) << 16;
+
+ return result;
+}
+
+static inline uint64_t
+load_4(const unsigned char *in)
+{
+ uint64_t result;
+
+ result = (uint64_t) in[0];
+ result |= ((uint64_t) in[1]) << 8;
+ result |= ((uint64_t) in[2]) << 16;
+ result |= ((uint64_t) in[3]) << 24;
+
+ return result;
+}
+
+/*
+ * Field arithmetic:
+ * Use 5*51 bit limbs on 64-bit systems with support for 128 bit arithmetic,
+ * and 10*25.5 bit limbs elsewhere.
+ *
+ * Functions used elsewhere that are candidates for inlining are defined
+ * via "private/curve25519_ref10.h".
+ */
+
+#ifdef HAVE_TI_MODE
+# include "fe_51/constants.h"
+# include "fe_51/fe.h"
+#else
+# include "fe_25_5/constants.h"
+# include "fe_25_5/fe.h"
+#endif
+
+void
+fe25519_invert(fe25519 out, const fe25519 z)
+{
+ fe25519 t0;
+ fe25519 t1;
+ fe25519 t2;
+ fe25519 t3;
+ int i;
+
+ fe25519_sq(t0, z);
+ fe25519_sq(t1, t0);
+ fe25519_sq(t1, t1);
+ fe25519_mul(t1, z, t1);
+ fe25519_mul(t0, t0, t1);
+ fe25519_sq(t2, t0);
+ fe25519_mul(t1, t1, t2);
+ fe25519_sq(t2, t1);
+ for (i = 1; i < 5; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t1, t2, t1);
+ fe25519_sq(t2, t1);
+ for (i = 1; i < 10; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t2, t2, t1);
+ fe25519_sq(t3, t2);
+ for (i = 1; i < 20; ++i) {
+ fe25519_sq(t3, t3);
+ }
+ fe25519_mul(t2, t3, t2);
+ for (i = 1; i < 11; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t1, t2, t1);
+ fe25519_sq(t2, t1);
+ for (i = 1; i < 50; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t2, t2, t1);
+ fe25519_sq(t3, t2);
+ for (i = 1; i < 100; ++i) {
+ fe25519_sq(t3, t3);
+ }
+ fe25519_mul(t2, t3, t2);
+ for (i = 1; i < 51; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t1, t2, t1);
+ for (i = 1; i < 6; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(out, t1, t0);
+}
+
+static void
+fe25519_pow22523(fe25519 out, const fe25519 z)
+{
+ fe25519 t0;
+ fe25519 t1;
+ fe25519 t2;
+ int i;
+
+ fe25519_sq(t0, z);
+ fe25519_sq(t1, t0);
+ fe25519_sq(t1, t1);
+ fe25519_mul(t1, z, t1);
+ fe25519_mul(t0, t0, t1);
+ fe25519_sq(t0, t0);
+ fe25519_mul(t0, t1, t0);
+ fe25519_sq(t1, t0);
+ for (i = 1; i < 5; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(t0, t1, t0);
+ fe25519_sq(t1, t0);
+ for (i = 1; i < 10; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(t1, t1, t0);
+ fe25519_sq(t2, t1);
+ for (i = 1; i < 20; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t1, t2, t1);
+ for (i = 1; i < 11; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(t0, t1, t0);
+ fe25519_sq(t1, t0);
+ for (i = 1; i < 50; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(t1, t1, t0);
+ fe25519_sq(t2, t1);
+ for (i = 1; i < 100; ++i) {
+ fe25519_sq(t2, t2);
+ }
+ fe25519_mul(t1, t2, t1);
+ for (i = 1; i < 51; ++i) {
+ fe25519_sq(t1, t1);
+ }
+ fe25519_mul(t0, t1, t0);
+ fe25519_sq(t0, t0);
+ fe25519_sq(t0, t0);
+ fe25519_mul(out, t0, z);
+}
+
+static inline void
+fe25519_cneg(fe25519 h, const fe25519 f, unsigned int b)
+{
+ fe25519 negf;
+
+ fe25519_neg(negf, f);
+ fe25519_copy(h, f);
+ fe25519_cmov(h, negf, b);
+}
+
+static inline void
+fe25519_abs(fe25519 h, const fe25519 f)
+{
+ fe25519_cneg(h, f, fe25519_isnegative(f));
+}
+
+static inline void
+fe25519_sqmul(fe25519 s, const int n, const fe25519 a)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ fe25519_sq(s, s);
+ }
+ fe25519_mul(s, s, a);
+}
+
+static unsigned int
+fe25519_notsquare(const fe25519 x)
+{
+ fe25519 _10, _11, _1100, _1111, _11110000, _11111111;
+ fe25519 t, u, v;
+ unsigned char s[32];
+
+ /* Jacobi symbol - x^((p-1)/2) */
+ fe25519_mul(_10, x, x);
+ fe25519_mul(_11, x, _10);
+ fe25519_sq(_1100, _11);
+ fe25519_sq(_1100, _1100);
+ fe25519_mul(_1111, _11, _1100);
+ fe25519_sq(_11110000, _1111);
+ fe25519_sq(_11110000, _11110000);
+ fe25519_sq(_11110000, _11110000);
+ fe25519_sq(_11110000, _11110000);
+ fe25519_mul(_11111111, _1111, _11110000);
+ fe25519_copy(t, _11111111);
+ fe25519_sqmul(t, 2, _11);
+ fe25519_copy(u, t);
+ fe25519_sqmul(t, 10, u);
+ fe25519_sqmul(t, 10, u);
+ fe25519_copy(v, t);
+ fe25519_sqmul(t, 30, v);
+ fe25519_copy(v, t);
+ fe25519_sqmul(t, 60, v);
+ fe25519_copy(v, t);
+ fe25519_sqmul(t, 120, v);
+ fe25519_sqmul(t, 10, u);
+ fe25519_sqmul(t, 3, _11);
+ fe25519_sq(t, t);
+
+ fe25519_tobytes(s, t);
+
+ return s[1] & 1;
+}
+
+/*
+ r = p + q
+ */
+
+void
+ge25519_add(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_cached *q)
+{
+ fe25519 t0;
+
+ fe25519_add(r->X, p->Y, p->X);
+ fe25519_sub(r->Y, p->Y, p->X);
+ fe25519_mul(r->Z, r->X, q->YplusX);
+ fe25519_mul(r->Y, r->Y, q->YminusX);
+ fe25519_mul(r->T, q->T2d, p->T);
+ fe25519_mul(r->X, p->Z, q->Z);
+ fe25519_add(t0, r->X, r->X);
+ fe25519_sub(r->X, r->Z, r->Y);
+ fe25519_add(r->Y, r->Z, r->Y);
+ fe25519_add(r->Z, t0, r->T);
+ fe25519_sub(r->T, t0, r->T);
+}
+
+static void
+slide_vartime(signed char *r, const unsigned char *a)
+{
+ int i;
+ int b;
+ int k;
+ int ribs;
+ int cmp;
+
+ for (i = 0; i < 256; ++i) {
+ r[i] = 1 & (a[i >> 3] >> (i & 7));
+ }
+ for (i = 0; i < 256; ++i) {
+ if (! r[i]) {
+ continue;
+ }
+ for (b = 1; b <= 6 && i + b < 256; ++b) {
+ if (! r[i + b]) {
+ continue;
+ }
+ ribs = r[i + b] << b;
+ cmp = r[i] + ribs;
+ if (cmp <= 15) {
+ r[i] = cmp;
+ r[i + b] = 0;
+ } else {
+ cmp = r[i] - ribs;
+ if (cmp < -15) {
+ break;
+ }
+ r[i] = cmp;
+ for (k = i + b; k < 256; ++k) {
+ if (! r[k]) {
+ r[k] = 1;
+ break;
+ }
+ r[k] = 0;
+ }
+ }
+ }
+ }
+}
+
+int
+ge25519_frombytes(ge25519_p3 *h, const unsigned char *s)
+{
+ fe25519 u;
+ fe25519 v;
+ fe25519 vxx;
+ fe25519 m_root_check, p_root_check;
+ fe25519 negx;
+ fe25519 x_sqrtm1;
+ int has_m_root, has_p_root;
+
+ fe25519_frombytes(h->Y, s);
+ fe25519_1(h->Z);
+ fe25519_sq(u, h->Y);
+ fe25519_mul(v, u, d);
+ fe25519_sub(u, u, h->Z); /* u = y^2-1 */
+ fe25519_add(v, v, h->Z); /* v = dy^2+1 */
+
+ fe25519_mul(h->X, u, v);
+ fe25519_pow22523(h->X, h->X);
+ fe25519_mul(h->X, u, h->X); /* u((uv)^((q-5)/8)) */
+
+ fe25519_sq(vxx, h->X);
+ fe25519_mul(vxx, vxx, v);
+ fe25519_sub(m_root_check, vxx, u); /* vx^2-u */
+ fe25519_add(p_root_check, vxx, u); /* vx^2+u */
+ has_m_root = fe25519_iszero(m_root_check);
+ has_p_root = fe25519_iszero(p_root_check);
+ fe25519_mul(x_sqrtm1, h->X, sqrtm1); /* x*sqrt(-1) */
+ fe25519_cmov(h->X, x_sqrtm1, 1 - has_m_root);
+
+ fe25519_neg(negx, h->X);
+ fe25519_cmov(h->X, negx, fe25519_isnegative(h->X) ^ (s[31] >> 7));
+ fe25519_mul(h->T, h->X, h->Y);
+
+ return (has_m_root | has_p_root) - 1;
+}
+
+int
+ge25519_frombytes_negate_vartime(ge25519_p3 *h, const unsigned char *s)
+{
+ fe25519 u;
+ fe25519 v;
+ fe25519 v3;
+ fe25519 vxx;
+ fe25519 m_root_check, p_root_check;
+
+ fe25519_frombytes(h->Y, s);
+ fe25519_1(h->Z);
+ fe25519_sq(u, h->Y);
+ fe25519_mul(v, u, d);
+ fe25519_sub(u, u, h->Z); /* u = y^2-1 */
+ fe25519_add(v, v, h->Z); /* v = dy^2+1 */
+
+ fe25519_sq(v3, v);
+ fe25519_mul(v3, v3, v); /* v3 = v^3 */
+ fe25519_sq(h->X, v3);
+ fe25519_mul(h->X, h->X, v);
+ fe25519_mul(h->X, h->X, u); /* x = uv^7 */
+
+ fe25519_pow22523(h->X, h->X); /* x = (uv^7)^((q-5)/8) */
+ fe25519_mul(h->X, h->X, v3);
+ fe25519_mul(h->X, h->X, u); /* x = uv^3(uv^7)^((q-5)/8) */
+
+ fe25519_sq(vxx, h->X);
+ fe25519_mul(vxx, vxx, v);
+ fe25519_sub(m_root_check, vxx, u); /* vx^2-u */
+ if (fe25519_iszero(m_root_check) == 0) {
+ fe25519_add(p_root_check, vxx, u); /* vx^2+u */
+ if (fe25519_iszero(p_root_check) == 0) {
+ return -1;
+ }
+ fe25519_mul(h->X, h->X, sqrtm1);
+ }
+
+ if (fe25519_isnegative(h->X) == (s[31] >> 7)) {
+ fe25519_neg(h->X, h->X);
+ }
+ fe25519_mul(h->T, h->X, h->Y);
+
+ return 0;
+}
+
+/*
+ r = p + q
+ */
+
+static void
+ge25519_madd(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_precomp *q)
+{
+ fe25519 t0;
+
+ fe25519_add(r->X, p->Y, p->X);
+ fe25519_sub(r->Y, p->Y, p->X);
+ fe25519_mul(r->Z, r->X, q->yplusx);
+ fe25519_mul(r->Y, r->Y, q->yminusx);
+ fe25519_mul(r->T, q->xy2d, p->T);
+ fe25519_add(t0, p->Z, p->Z);
+ fe25519_sub(r->X, r->Z, r->Y);
+ fe25519_add(r->Y, r->Z, r->Y);
+ fe25519_add(r->Z, t0, r->T);
+ fe25519_sub(r->T, t0, r->T);
+}
+
+/*
+ r = p - q
+ */
+
+static void
+ge25519_msub(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_precomp *q)
+{
+ fe25519 t0;
+
+ fe25519_add(r->X, p->Y, p->X);
+ fe25519_sub(r->Y, p->Y, p->X);
+ fe25519_mul(r->Z, r->X, q->yminusx);
+ fe25519_mul(r->Y, r->Y, q->yplusx);
+ fe25519_mul(r->T, q->xy2d, p->T);
+ fe25519_add(t0, p->Z, p->Z);
+ fe25519_sub(r->X, r->Z, r->Y);
+ fe25519_add(r->Y, r->Z, r->Y);
+ fe25519_sub(r->Z, t0, r->T);
+ fe25519_add(r->T, t0, r->T);
+}
+
+/*
+ r = p
+ */
+
+void
+ge25519_p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p)
+{
+ fe25519_mul(r->X, p->X, p->T);
+ fe25519_mul(r->Y, p->Y, p->Z);
+ fe25519_mul(r->Z, p->Z, p->T);
+}
+
+/*
+ r = p
+ */
+
+void
+ge25519_p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p)
+{
+ fe25519_mul(r->X, p->X, p->T);
+ fe25519_mul(r->Y, p->Y, p->Z);
+ fe25519_mul(r->Z, p->Z, p->T);
+ fe25519_mul(r->T, p->X, p->Y);
+}
+
+static void
+ge25519_p2_0(ge25519_p2 *h)
+{
+ fe25519_0(h->X);
+ fe25519_1(h->Y);
+ fe25519_1(h->Z);
+}
+
+/*
+ r = 2 * p
+ */
+
+static void
+ge25519_p2_dbl(ge25519_p1p1 *r, const ge25519_p2 *p)
+{
+ fe25519 t0;
+
+ fe25519_sq(r->X, p->X);
+ fe25519_sq(r->Z, p->Y);
+ fe25519_sq2(r->T, p->Z);
+ fe25519_add(r->Y, p->X, p->Y);
+ fe25519_sq(t0, r->Y);
+ fe25519_add(r->Y, r->Z, r->X);
+ fe25519_sub(r->Z, r->Z, r->X);
+ fe25519_sub(r->X, t0, r->Y);
+ fe25519_sub(r->T, r->T, r->Z);
+}
+
+static void
+ge25519_p3_0(ge25519_p3 *h)
+{
+ fe25519_0(h->X);
+ fe25519_1(h->Y);
+ fe25519_1(h->Z);
+ fe25519_0(h->T);
+}
+
+static void
+ge25519_cached_0(ge25519_cached *h)
+{
+ fe25519_1(h->YplusX);
+ fe25519_1(h->YminusX);
+ fe25519_1(h->Z);
+ fe25519_0(h->T2d);
+}
+
+/*
+ r = p
+ */
+
+void
+ge25519_p3_to_cached(ge25519_cached *r, const ge25519_p3 *p)
+{
+ fe25519_add(r->YplusX, p->Y, p->X);
+ fe25519_sub(r->YminusX, p->Y, p->X);
+ fe25519_copy(r->Z, p->Z);
+ fe25519_mul(r->T2d, p->T, d2);
+}
+
+static void
+ge25519_p3_to_precomp(ge25519_precomp *pi, const ge25519_p3 *p)
+{
+ fe25519 recip;
+ fe25519 x;
+ fe25519 y;
+ fe25519 xy;
+
+ fe25519_invert(recip, p->Z);
+ fe25519_mul(x, p->X, recip);
+ fe25519_mul(y, p->Y, recip);
+ fe25519_add(pi->yplusx, y, x);
+ fe25519_sub(pi->yminusx, y, x);
+ fe25519_mul(xy, x, y);
+ fe25519_mul(pi->xy2d, xy, d2);
+}
+
+/*
+ r = p
+ */
+
+static void
+ge25519_p3_to_p2(ge25519_p2 *r, const ge25519_p3 *p)
+{
+ fe25519_copy(r->X, p->X);
+ fe25519_copy(r->Y, p->Y);
+ fe25519_copy(r->Z, p->Z);
+}
+
+void
+ge25519_p3_tobytes(unsigned char *s, const ge25519_p3 *h)
+{
+ fe25519 recip;
+ fe25519 x;
+ fe25519 y;
+
+ fe25519_invert(recip, h->Z);
+ fe25519_mul(x, h->X, recip);
+ fe25519_mul(y, h->Y, recip);
+ fe25519_tobytes(s, y);
+ s[31] ^= fe25519_isnegative(x) << 7;
+}
+
+/*
+ r = 2 * p
+ */
+
+static void
+ge25519_p3_dbl(ge25519_p1p1 *r, const ge25519_p3 *p)
+{
+ ge25519_p2 q;
+ ge25519_p3_to_p2(&q, p);
+ ge25519_p2_dbl(r, &q);
+}
+
+static void
+ge25519_precomp_0(ge25519_precomp *h)
+{
+ fe25519_1(h->yplusx);
+ fe25519_1(h->yminusx);
+ fe25519_0(h->xy2d);
+}
+
+static unsigned char
+equal(signed char b, signed char c)
+{
+ unsigned char ub = b;
+ unsigned char uc = c;
+ unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */
+ uint32_t y = (uint32_t) x; /* 0: yes; 1..255: no */
+
+ y -= 1; /* 4294967295: yes; 0..254: no */
+ y >>= 31; /* 1: yes; 0: no */
+
+ return y;
+}
+
+static unsigned char
+negative(signed char b)
+{
+ /* 18446744073709551361..18446744073709551615: yes; 0..255: no */
+ uint64_t x = b;
+
+ x >>= 63; /* 1: yes; 0: no */
+
+ return x;
+}
+
+static void
+ge25519_cmov(ge25519_precomp *t, const ge25519_precomp *u, unsigned char b)
+{
+ fe25519_cmov(t->yplusx, u->yplusx, b);
+ fe25519_cmov(t->yminusx, u->yminusx, b);
+ fe25519_cmov(t->xy2d, u->xy2d, b);
+}
+
+static void
+ge25519_cmov_cached(ge25519_cached *t, const ge25519_cached *u, unsigned char b)
+{
+ fe25519_cmov(t->YplusX, u->YplusX, b);
+ fe25519_cmov(t->YminusX, u->YminusX, b);
+ fe25519_cmov(t->Z, u->Z, b);
+ fe25519_cmov(t->T2d, u->T2d, b);
+}
+
+static void
+ge25519_cmov8(ge25519_precomp *t, const ge25519_precomp precomp[8], const signed char b)
+{
+ ge25519_precomp minust;
+ const unsigned char bnegative = negative(b);
+ const unsigned char babs = b - (((-bnegative) & b) * ((signed char) 1 << 1));
+
+ ge25519_precomp_0(t);
+ ge25519_cmov(t, &precomp[0], equal(babs, 1));
+ ge25519_cmov(t, &precomp[1], equal(babs, 2));
+ ge25519_cmov(t, &precomp[2], equal(babs, 3));
+ ge25519_cmov(t, &precomp[3], equal(babs, 4));
+ ge25519_cmov(t, &precomp[4], equal(babs, 5));
+ ge25519_cmov(t, &precomp[5], equal(babs, 6));
+ ge25519_cmov(t, &precomp[6], equal(babs, 7));
+ ge25519_cmov(t, &precomp[7], equal(babs, 8));
+ fe25519_copy(minust.yplusx, t->yminusx);
+ fe25519_copy(minust.yminusx, t->yplusx);
+ fe25519_neg(minust.xy2d, t->xy2d);
+ ge25519_cmov(t, &minust, bnegative);
+}
+
+static void
+ge25519_cmov8_base(ge25519_precomp *t, const int pos, const signed char b)
+{
+ static const ge25519_precomp base[32][8] = { /* base[i][j] = (j+1)*256^i*B */
+#ifdef HAVE_TI_MODE
+# include "fe_51/base.h"
+#else
+# include "fe_25_5/base.h"
+#endif
+ };
+ ge25519_cmov8(t, base[pos], b);
+}
+
+static void
+ge25519_cmov8_cached(ge25519_cached *t, const ge25519_cached cached[8], const signed char b)
+{
+ ge25519_cached minust;
+ const unsigned char bnegative = negative(b);
+ const unsigned char babs = b - (((-bnegative) & b) * ((signed char) 1 << 1));
+
+ ge25519_cached_0(t);
+ ge25519_cmov_cached(t, &cached[0], equal(babs, 1));
+ ge25519_cmov_cached(t, &cached[1], equal(babs, 2));
+ ge25519_cmov_cached(t, &cached[2], equal(babs, 3));
+ ge25519_cmov_cached(t, &cached[3], equal(babs, 4));
+ ge25519_cmov_cached(t, &cached[4], equal(babs, 5));
+ ge25519_cmov_cached(t, &cached[5], equal(babs, 6));
+ ge25519_cmov_cached(t, &cached[6], equal(babs, 7));
+ ge25519_cmov_cached(t, &cached[7], equal(babs, 8));
+ fe25519_copy(minust.YplusX, t->YminusX);
+ fe25519_copy(minust.YminusX, t->YplusX);
+ fe25519_copy(minust.Z, t->Z);
+ fe25519_neg(minust.T2d, t->T2d);
+ ge25519_cmov_cached(t, &minust, bnegative);
+}
+
+/*
+ r = p - q
+ */
+
+void
+ge25519_sub(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_cached *q)
+{
+ fe25519 t0;
+
+ fe25519_add(r->X, p->Y, p->X);
+ fe25519_sub(r->Y, p->Y, p->X);
+ fe25519_mul(r->Z, r->X, q->YminusX);
+ fe25519_mul(r->Y, r->Y, q->YplusX);
+ fe25519_mul(r->T, q->T2d, p->T);
+ fe25519_mul(r->X, p->Z, q->Z);
+ fe25519_add(t0, r->X, r->X);
+ fe25519_sub(r->X, r->Z, r->Y);
+ fe25519_add(r->Y, r->Z, r->Y);
+ fe25519_sub(r->Z, t0, r->T);
+ fe25519_add(r->T, t0, r->T);
+}
+
+void
+ge25519_tobytes(unsigned char *s, const ge25519_p2 *h)
+{
+ fe25519 recip;
+ fe25519 x;
+ fe25519 y;
+
+ fe25519_invert(recip, h->Z);
+ fe25519_mul(x, h->X, recip);
+ fe25519_mul(y, h->Y, recip);
+ fe25519_tobytes(s, y);
+ s[31] ^= fe25519_isnegative(x) << 7;
+}
+
+/*
+ r = a * A + b * B
+ where a = a[0]+256*a[1]+...+256^31 a[31].
+ and b = b[0]+256*b[1]+...+256^31 b[31].
+ B is the Ed25519 base point (x,4/5) with x positive.
+
+ Only used for signatures verification.
+ */
+
+void
+ge25519_double_scalarmult_vartime(ge25519_p2 *r, const unsigned char *a,
+ const ge25519_p3 *A, const unsigned char *b)
+{
+ static const ge25519_precomp Bi[8] = {
+#ifdef HAVE_TI_MODE
+# include "fe_51/base2.h"
+#else
+# include "fe_25_5/base2.h"
+#endif
+ };
+ signed char aslide[256];
+ signed char bslide[256];
+ ge25519_cached Ai[8]; /* A,3A,5A,7A,9A,11A,13A,15A */
+ ge25519_p1p1 t;
+ ge25519_p3 u;
+ ge25519_p3 A2;
+ int i;
+
+ slide_vartime(aslide, a);
+ slide_vartime(bslide, b);
+
+ ge25519_p3_to_cached(&Ai[0], A);
+
+ ge25519_p3_dbl(&t, A);
+ ge25519_p1p1_to_p3(&A2, &t);
+
+ ge25519_add(&t, &A2, &Ai[0]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[1], &u);
+
+ ge25519_add(&t, &A2, &Ai[1]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[2], &u);
+
+ ge25519_add(&t, &A2, &Ai[2]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[3], &u);
+
+ ge25519_add(&t, &A2, &Ai[3]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[4], &u);
+
+ ge25519_add(&t, &A2, &Ai[4]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[5], &u);
+
+ ge25519_add(&t, &A2, &Ai[5]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[6], &u);
+
+ ge25519_add(&t, &A2, &Ai[6]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[7], &u);
+
+ ge25519_p2_0(r);
+
+ for (i = 255; i >= 0; --i) {
+ if (aslide[i] || bslide[i]) {
+ break;
+ }
+ }
+
+ for (; i >= 0; --i) {
+ ge25519_p2_dbl(&t, r);
+
+ if (aslide[i] > 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_add(&t, &u, &Ai[aslide[i] / 2]);
+ } else if (aslide[i] < 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
+ }
+
+ if (bslide[i] > 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_madd(&t, &u, &Bi[bslide[i] / 2]);
+ } else if (bslide[i] < 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_msub(&t, &u, &Bi[(-bslide[i]) / 2]);
+ }
+
+ ge25519_p1p1_to_p2(r, &t);
+ }
+}
+
+/*
+ h = a * p
+ where a = a[0]+256*a[1]+...+256^31 a[31]
+
+ Preconditions:
+ a[31] <= 127
+
+ p is public
+ */
+
+void
+ge25519_scalarmult(ge25519_p3 *h, const unsigned char *a, const ge25519_p3 *p)
+{
+ signed char e[64];
+ signed char carry;
+ ge25519_p1p1 r;
+ ge25519_p2 s;
+ ge25519_p1p1 t2, t3, t4, t5, t6, t7, t8;
+ ge25519_p3 p2, p3, p4, p5, p6, p7, p8;
+ ge25519_cached pi[8];
+ ge25519_cached t;
+ int i;
+
+ ge25519_p3_to_cached(&pi[1 - 1], p); /* p */
+
+ ge25519_p3_dbl(&t2, p);
+ ge25519_p1p1_to_p3(&p2, &t2);
+ ge25519_p3_to_cached(&pi[2 - 1], &p2); /* 2p = 2*p */
+
+ ge25519_add(&t3, p, &pi[2 - 1]);
+ ge25519_p1p1_to_p3(&p3, &t3);
+ ge25519_p3_to_cached(&pi[3 - 1], &p3); /* 3p = 2p+p */
+
+ ge25519_p3_dbl(&t4, &p2);
+ ge25519_p1p1_to_p3(&p4, &t4);
+ ge25519_p3_to_cached(&pi[4 - 1], &p4); /* 4p = 2*2p */
+
+ ge25519_add(&t5, p, &pi[4 - 1]);
+ ge25519_p1p1_to_p3(&p5, &t5);
+ ge25519_p3_to_cached(&pi[5 - 1], &p5); /* 5p = 4p+p */
+
+ ge25519_p3_dbl(&t6, &p3);
+ ge25519_p1p1_to_p3(&p6, &t6);
+ ge25519_p3_to_cached(&pi[6 - 1], &p6); /* 6p = 2*3p */
+
+ ge25519_add(&t7, p, &pi[6 - 1]);
+ ge25519_p1p1_to_p3(&p7, &t7);
+ ge25519_p3_to_cached(&pi[7 - 1], &p7); /* 7p = 6p+p */
+
+ ge25519_p3_dbl(&t8, &p4);
+ ge25519_p1p1_to_p3(&p8, &t8);
+ ge25519_p3_to_cached(&pi[8 - 1], &p8); /* 8p = 2*4p */
+
+ for (i = 0; i < 32; ++i) {
+ e[2 * i + 0] = (a[i] >> 0) & 15;
+ e[2 * i + 1] = (a[i] >> 4) & 15;
+ }
+ /* each e[i] is between 0 and 15 */
+ /* e[63] is between 0 and 7 */
+
+ carry = 0;
+ for (i = 0; i < 63; ++i) {
+ e[i] += carry;
+ carry = e[i] + 8;
+ carry >>= 4;
+ e[i] -= carry * ((signed char) 1 << 4);
+ }
+ e[63] += carry;
+ /* each e[i] is between -8 and 8 */
+
+ ge25519_p3_0(h);
+
+ for (i = 63; i != 0; i--) {
+ ge25519_cmov8_cached(&t, pi, e[i]);
+ ge25519_add(&r, h, &t);
+
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+
+ ge25519_p1p1_to_p3(h, &r); /* *16 */
+ }
+ ge25519_cmov8_cached(&t, pi, e[i]);
+ ge25519_add(&r, h, &t);
+
+ ge25519_p1p1_to_p3(h, &r);
+}
+
+/*
+ h = a * B (with precomputation)
+ where a = a[0]+256*a[1]+...+256^31 a[31]
+ B is the Ed25519 base point (x,4/5) with x positive
+ (as bytes: 0x5866666666666666666666666666666666666666666666666666666666666666)
+
+ Preconditions:
+ a[31] <= 127
+ */
+
+void
+ge25519_scalarmult_base(ge25519_p3 *h, const unsigned char *a)
+{
+ signed char e[64];
+ signed char carry;
+ ge25519_p1p1 r;
+ ge25519_p2 s;
+ ge25519_precomp t;
+ int i;
+
+ for (i = 0; i < 32; ++i) {
+ e[2 * i + 0] = (a[i] >> 0) & 15;
+ e[2 * i + 1] = (a[i] >> 4) & 15;
+ }
+ /* each e[i] is between 0 and 15 */
+ /* e[63] is between 0 and 7 */
+
+ carry = 0;
+ for (i = 0; i < 63; ++i) {
+ e[i] += carry;
+ carry = e[i] + 8;
+ carry >>= 4;
+ e[i] -= carry * ((signed char) 1 << 4);
+ }
+ e[63] += carry;
+ /* each e[i] is between -8 and 8 */
+
+ ge25519_p3_0(h);
+
+ for (i = 1; i < 64; i += 2) {
+ ge25519_cmov8_base(&t, i / 2, e[i]);
+ ge25519_madd(&r, h, &t);
+ ge25519_p1p1_to_p3(h, &r);
+ }
+
+ ge25519_p3_dbl(&r, h);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p2(&s, &r);
+ ge25519_p2_dbl(&r, &s);
+ ge25519_p1p1_to_p3(h, &r);
+
+ for (i = 0; i < 64; i += 2) {
+ ge25519_cmov8_base(&t, i / 2, e[i]);
+ ge25519_madd(&r, h, &t);
+ ge25519_p1p1_to_p3(h, &r);
+ }
+}
+
+/* multiply by the order of the main subgroup l = 2^252+27742317777372353535851937790883648493 */
+static void
+ge25519_mul_l(ge25519_p3 *r, const ge25519_p3 *A)
+{
+ static const signed char aslide[253] = {
+ 13, 0, 0, 0, 0, -1, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, -13, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 3, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ };
+ ge25519_cached Ai[8];
+ ge25519_p1p1 t;
+ ge25519_p3 u;
+ ge25519_p3 A2;
+ int i;
+
+ ge25519_p3_to_cached(&Ai[0], A);
+ ge25519_p3_dbl(&t, A);
+ ge25519_p1p1_to_p3(&A2, &t);
+ ge25519_add(&t, &A2, &Ai[0]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[1], &u);
+ ge25519_add(&t, &A2, &Ai[1]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[2], &u);
+ ge25519_add(&t, &A2, &Ai[2]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[3], &u);
+ ge25519_add(&t, &A2, &Ai[3]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[4], &u);
+ ge25519_add(&t, &A2, &Ai[4]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[5], &u);
+ ge25519_add(&t, &A2, &Ai[5]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[6], &u);
+ ge25519_add(&t, &A2, &Ai[6]);
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_p3_to_cached(&Ai[7], &u);
+
+ ge25519_p3_0(r);
+
+ for (i = 252; i >= 0; --i) {
+ ge25519_p3_dbl(&t, r);
+
+ if (aslide[i] > 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_add(&t, &u, &Ai[aslide[i] / 2]);
+ } else if (aslide[i] < 0) {
+ ge25519_p1p1_to_p3(&u, &t);
+ ge25519_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
+ }
+
+ ge25519_p1p1_to_p3(r, &t);
+ }
+}
+
+int
+ge25519_is_on_curve(const ge25519_p3 *p)
+{
+ fe25519 x2;
+ fe25519 y2;
+ fe25519 z2;
+ fe25519 z4;
+ fe25519 t0;
+ fe25519 t1;
+
+ fe25519_sq(x2, p->X);
+ fe25519_sq(y2, p->Y);
+ fe25519_sq(z2, p->Z);
+ fe25519_sub(t0, y2, x2);
+ fe25519_mul(t0, t0, z2);
+
+ fe25519_mul(t1, x2, y2);
+ fe25519_mul(t1, t1, d);
+ fe25519_sq(z4, z2);
+ fe25519_add(t1, t1, z4);
+ fe25519_sub(t0, t0, t1);
+
+ return fe25519_iszero(t0);
+}
+
+int
+ge25519_is_on_main_subgroup(const ge25519_p3 *p)
+{
+ ge25519_p3 pl;
+
+ ge25519_mul_l(&pl, p);
+
+ return fe25519_iszero(pl.X);
+}
+
+int
+ge25519_is_canonical(const unsigned char *s)
+{
+ unsigned char c;
+ unsigned char d;
+ unsigned int i;
+
+ c = (s[31] & 0x7f) ^ 0x7f;
+ for (i = 30; i > 0; i--) {
+ c |= s[i] ^ 0xff;
+ }
+ c = (((unsigned int) c) - 1U) >> 8;
+ d = (0xed - 1U - (unsigned int) s[0]) >> 8;
+
+ return 1 - (c & d & 1);
+}
+
+int
+ge25519_has_small_order(const unsigned char s[32])
+{
+ CRYPTO_ALIGN(16)
+ static const unsigned char blacklist[][32] = {
+ /* 0 (order 4) */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ /* 1 (order 1) */
+ { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ /* 2707385501144840649318225287225658788936804267575313519463743609750303402022
+ (order 8) */
+ { 0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4,
+ 0x89, 0xf2, 0xef, 0x98, 0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6,
+ 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05 },
+ /* 55188659117513257062467267217118295137698188065244968500265048394206261417927
+ (order 8) */
+ { 0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b,
+ 0x76, 0x0d, 0x10, 0x67, 0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39,
+ 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a },
+ /* p-1 (order 2) */
+ { 0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f },
+ /* p (=0, order 4) */
+ { 0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f },
+ /* p+1 (=1, order 1) */
+ { 0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f }
+ };
+ unsigned char c[7] = { 0 };
+ unsigned int k;
+ size_t i, j;
+
+ COMPILER_ASSERT(7 == sizeof blacklist / sizeof blacklist[0]);
+ for (j = 0; j < 31; j++) {
+ for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) {
+ c[i] |= s[j] ^ blacklist[i][j];
+ }
+ }
+ for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) {
+ c[i] |= (s[j] & 0x7f) ^ blacklist[i][j];
+ }
+ k = 0;
+ for (i = 0; i < sizeof blacklist / sizeof blacklist[0]; i++) {
+ k |= (c[i] - 1);
+ }
+ return (int) ((k >> 8) & 1);
+}
+
+/*
+ Input:
+ a[0]+256*a[1]+...+256^31*a[31] = a
+ b[0]+256*b[1]+...+256^31*b[31] = b
+ *
+ Output:
+ s[0]+256*s[1]+...+256^31*s[31] = (ab) mod l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ */
+
+void
+sc25519_mul(unsigned char s[32], const unsigned char a[32], const unsigned char b[32])
+{
+ int64_t a0 = 2097151 & load_3(a);
+ int64_t a1 = 2097151 & (load_4(a + 2) >> 5);
+ int64_t a2 = 2097151 & (load_3(a + 5) >> 2);
+ int64_t a3 = 2097151 & (load_4(a + 7) >> 7);
+ int64_t a4 = 2097151 & (load_4(a + 10) >> 4);
+ int64_t a5 = 2097151 & (load_3(a + 13) >> 1);
+ int64_t a6 = 2097151 & (load_4(a + 15) >> 6);
+ int64_t a7 = 2097151 & (load_3(a + 18) >> 3);
+ int64_t a8 = 2097151 & load_3(a + 21);
+ int64_t a9 = 2097151 & (load_4(a + 23) >> 5);
+ int64_t a10 = 2097151 & (load_3(a + 26) >> 2);
+ int64_t a11 = (load_4(a + 28) >> 7);
+
+ int64_t b0 = 2097151 & load_3(b);
+ int64_t b1 = 2097151 & (load_4(b + 2) >> 5);
+ int64_t b2 = 2097151 & (load_3(b + 5) >> 2);
+ int64_t b3 = 2097151 & (load_4(b + 7) >> 7);
+ int64_t b4 = 2097151 & (load_4(b + 10) >> 4);
+ int64_t b5 = 2097151 & (load_3(b + 13) >> 1);
+ int64_t b6 = 2097151 & (load_4(b + 15) >> 6);
+ int64_t b7 = 2097151 & (load_3(b + 18) >> 3);
+ int64_t b8 = 2097151 & load_3(b + 21);
+ int64_t b9 = 2097151 & (load_4(b + 23) >> 5);
+ int64_t b10 = 2097151 & (load_3(b + 26) >> 2);
+ int64_t b11 = (load_4(b + 28) >> 7);
+
+ int64_t s0;
+ int64_t s1;
+ int64_t s2;
+ int64_t s3;
+ int64_t s4;
+ int64_t s5;
+ int64_t s6;
+ int64_t s7;
+ int64_t s8;
+ int64_t s9;
+ int64_t s10;
+ int64_t s11;
+ int64_t s12;
+ int64_t s13;
+ int64_t s14;
+ int64_t s15;
+ int64_t s16;
+ int64_t s17;
+ int64_t s18;
+ int64_t s19;
+ int64_t s20;
+ int64_t s21;
+ int64_t s22;
+ int64_t s23;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+ int64_t carry10;
+ int64_t carry11;
+ int64_t carry12;
+ int64_t carry13;
+ int64_t carry14;
+ int64_t carry15;
+ int64_t carry16;
+ int64_t carry17;
+ int64_t carry18;
+ int64_t carry19;
+ int64_t carry20;
+ int64_t carry21;
+ int64_t carry22;
+
+ s0 = a0 * b0;
+ s1 = a0 * b1 + a1 * b0;
+ s2 = a0 * b2 + a1 * b1 + a2 * b0;
+ s3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
+ s4 = a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
+ s5 = a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
+ s6 = a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0;
+ s7 = a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 +
+ a6 * b1 + a7 * b0;
+ s8 = a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 +
+ a6 * b2 + a7 * b1 + a8 * b0;
+ s9 = a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 +
+ a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
+ s10 = a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 +
+ a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
+ s11 = a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 +
+ a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
+ s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 +
+ a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
+ s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 +
+ a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
+ s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 +
+ a9 * b5 + a10 * b4 + a11 * b3;
+ s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 +
+ a10 * b5 + a11 * b4;
+ s16 =
+ a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
+ s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
+ s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
+ s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
+ s20 = a9 * b11 + a10 * b10 + a11 * b9;
+ s21 = a10 * b11 + a11 * b10;
+ s22 = a11 * b11;
+ s23 = 0;
+
+ carry0 = (s0 + (int64_t) (1L << 20)) >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry2 = (s2 + (int64_t) (1L << 20)) >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry4 = (s4 + (int64_t) (1L << 20)) >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry12 = (s12 + (int64_t) (1L << 20)) >> 21;
+ s13 += carry12;
+ s12 -= carry12 * ((uint64_t) 1L << 21);
+ carry14 = (s14 + (int64_t) (1L << 20)) >> 21;
+ s15 += carry14;
+ s14 -= carry14 * ((uint64_t) 1L << 21);
+ carry16 = (s16 + (int64_t) (1L << 20)) >> 21;
+ s17 += carry16;
+ s16 -= carry16 * ((uint64_t) 1L << 21);
+ carry18 = (s18 + (int64_t) (1L << 20)) >> 21;
+ s19 += carry18;
+ s18 -= carry18 * ((uint64_t) 1L << 21);
+ carry20 = (s20 + (int64_t) (1L << 20)) >> 21;
+ s21 += carry20;
+ s20 -= carry20 * ((uint64_t) 1L << 21);
+ carry22 = (s22 + (int64_t) (1L << 20)) >> 21;
+ s23 += carry22;
+ s22 -= carry22 * ((uint64_t) 1L << 21);
+
+ carry1 = (s1 + (int64_t) (1L << 20)) >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry3 = (s3 + (int64_t) (1L << 20)) >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry5 = (s5 + (int64_t) (1L << 20)) >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+ carry13 = (s13 + (int64_t) (1L << 20)) >> 21;
+ s14 += carry13;
+ s13 -= carry13 * ((uint64_t) 1L << 21);
+ carry15 = (s15 + (int64_t) (1L << 20)) >> 21;
+ s16 += carry15;
+ s15 -= carry15 * ((uint64_t) 1L << 21);
+ carry17 = (s17 + (int64_t) (1L << 20)) >> 21;
+ s18 += carry17;
+ s17 -= carry17 * ((uint64_t) 1L << 21);
+ carry19 = (s19 + (int64_t) (1L << 20)) >> 21;
+ s20 += carry19;
+ s19 -= carry19 * ((uint64_t) 1L << 21);
+ carry21 = (s21 + (int64_t) (1L << 20)) >> 21;
+ s22 += carry21;
+ s21 -= carry21 * ((uint64_t) 1L << 21);
+
+ s11 += s23 * 666643;
+ s12 += s23 * 470296;
+ s13 += s23 * 654183;
+ s14 -= s23 * 997805;
+ s15 += s23 * 136657;
+ s16 -= s23 * 683901;
+
+ s10 += s22 * 666643;
+ s11 += s22 * 470296;
+ s12 += s22 * 654183;
+ s13 -= s22 * 997805;
+ s14 += s22 * 136657;
+ s15 -= s22 * 683901;
+
+ s9 += s21 * 666643;
+ s10 += s21 * 470296;
+ s11 += s21 * 654183;
+ s12 -= s21 * 997805;
+ s13 += s21 * 136657;
+ s14 -= s21 * 683901;
+
+ s8 += s20 * 666643;
+ s9 += s20 * 470296;
+ s10 += s20 * 654183;
+ s11 -= s20 * 997805;
+ s12 += s20 * 136657;
+ s13 -= s20 * 683901;
+
+ s7 += s19 * 666643;
+ s8 += s19 * 470296;
+ s9 += s19 * 654183;
+ s10 -= s19 * 997805;
+ s11 += s19 * 136657;
+ s12 -= s19 * 683901;
+
+ s6 += s18 * 666643;
+ s7 += s18 * 470296;
+ s8 += s18 * 654183;
+ s9 -= s18 * 997805;
+ s10 += s18 * 136657;
+ s11 -= s18 * 683901;
+
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry12 = (s12 + (int64_t) (1L << 20)) >> 21;
+ s13 += carry12;
+ s12 -= carry12 * ((uint64_t) 1L << 21);
+ carry14 = (s14 + (int64_t) (1L << 20)) >> 21;
+ s15 += carry14;
+ s14 -= carry14 * ((uint64_t) 1L << 21);
+ carry16 = (s16 + (int64_t) (1L << 20)) >> 21;
+ s17 += carry16;
+ s16 -= carry16 * ((uint64_t) 1L << 21);
+
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+ carry13 = (s13 + (int64_t) (1L << 20)) >> 21;
+ s14 += carry13;
+ s13 -= carry13 * ((uint64_t) 1L << 21);
+ carry15 = (s15 + (int64_t) (1L << 20)) >> 21;
+ s16 += carry15;
+ s15 -= carry15 * ((uint64_t) 1L << 21);
+
+ s5 += s17 * 666643;
+ s6 += s17 * 470296;
+ s7 += s17 * 654183;
+ s8 -= s17 * 997805;
+ s9 += s17 * 136657;
+ s10 -= s17 * 683901;
+
+ s4 += s16 * 666643;
+ s5 += s16 * 470296;
+ s6 += s16 * 654183;
+ s7 -= s16 * 997805;
+ s8 += s16 * 136657;
+ s9 -= s16 * 683901;
+
+ s3 += s15 * 666643;
+ s4 += s15 * 470296;
+ s5 += s15 * 654183;
+ s6 -= s15 * 997805;
+ s7 += s15 * 136657;
+ s8 -= s15 * 683901;
+
+ s2 += s14 * 666643;
+ s3 += s14 * 470296;
+ s4 += s14 * 654183;
+ s5 -= s14 * 997805;
+ s6 += s14 * 136657;
+ s7 -= s14 * 683901;
+
+ s1 += s13 * 666643;
+ s2 += s13 * 470296;
+ s3 += s13 * 654183;
+ s4 -= s13 * 997805;
+ s5 += s13 * 136657;
+ s6 -= s13 * 683901;
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = (s0 + (int64_t) (1L << 20)) >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry2 = (s2 + (int64_t) (1L << 20)) >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry4 = (s4 + (int64_t) (1L << 20)) >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ carry1 = (s1 + (int64_t) (1L << 20)) >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry3 = (s3 + (int64_t) (1L << 20)) >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry5 = (s5 + (int64_t) (1L << 20)) >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry11 = s11 >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ s[0] = s0 >> 0;
+ s[1] = s0 >> 8;
+ s[2] = (s0 >> 16) | (s1 * ((uint64_t) 1 << 5));
+ s[3] = s1 >> 3;
+ s[4] = s1 >> 11;
+ s[5] = (s1 >> 19) | (s2 * ((uint64_t) 1 << 2));
+ s[6] = s2 >> 6;
+ s[7] = (s2 >> 14) | (s3 * ((uint64_t) 1 << 7));
+ s[8] = s3 >> 1;
+ s[9] = s3 >> 9;
+ s[10] = (s3 >> 17) | (s4 * ((uint64_t) 1 << 4));
+ s[11] = s4 >> 4;
+ s[12] = s4 >> 12;
+ s[13] = (s4 >> 20) | (s5 * ((uint64_t) 1 << 1));
+ s[14] = s5 >> 7;
+ s[15] = (s5 >> 15) | (s6 * ((uint64_t) 1 << 6));
+ s[16] = s6 >> 2;
+ s[17] = s6 >> 10;
+ s[18] = (s6 >> 18) | (s7 * ((uint64_t) 1 << 3));
+ s[19] = s7 >> 5;
+ s[20] = s7 >> 13;
+ s[21] = s8 >> 0;
+ s[22] = s8 >> 8;
+ s[23] = (s8 >> 16) | (s9 * ((uint64_t) 1 << 5));
+ s[24] = s9 >> 3;
+ s[25] = s9 >> 11;
+ s[26] = (s9 >> 19) | (s10 * ((uint64_t) 1 << 2));
+ s[27] = s10 >> 6;
+ s[28] = (s10 >> 14) | (s11 * ((uint64_t) 1 << 7));
+ s[29] = s11 >> 1;
+ s[30] = s11 >> 9;
+ s[31] = s11 >> 17;
+}
+
+/*
+ Input:
+ a[0]+256*a[1]+...+256^31*a[31] = a
+ b[0]+256*b[1]+...+256^31*b[31] = b
+ c[0]+256*c[1]+...+256^31*c[31] = c
+ *
+ Output:
+ s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ */
+
+void
+sc25519_muladd(unsigned char s[32], const unsigned char a[32],
+ const unsigned char b[32], const unsigned char c[32])
+{
+ int64_t a0 = 2097151 & load_3(a);
+ int64_t a1 = 2097151 & (load_4(a + 2) >> 5);
+ int64_t a2 = 2097151 & (load_3(a + 5) >> 2);
+ int64_t a3 = 2097151 & (load_4(a + 7) >> 7);
+ int64_t a4 = 2097151 & (load_4(a + 10) >> 4);
+ int64_t a5 = 2097151 & (load_3(a + 13) >> 1);
+ int64_t a6 = 2097151 & (load_4(a + 15) >> 6);
+ int64_t a7 = 2097151 & (load_3(a + 18) >> 3);
+ int64_t a8 = 2097151 & load_3(a + 21);
+ int64_t a9 = 2097151 & (load_4(a + 23) >> 5);
+ int64_t a10 = 2097151 & (load_3(a + 26) >> 2);
+ int64_t a11 = (load_4(a + 28) >> 7);
+
+ int64_t b0 = 2097151 & load_3(b);
+ int64_t b1 = 2097151 & (load_4(b + 2) >> 5);
+ int64_t b2 = 2097151 & (load_3(b + 5) >> 2);
+ int64_t b3 = 2097151 & (load_4(b + 7) >> 7);
+ int64_t b4 = 2097151 & (load_4(b + 10) >> 4);
+ int64_t b5 = 2097151 & (load_3(b + 13) >> 1);
+ int64_t b6 = 2097151 & (load_4(b + 15) >> 6);
+ int64_t b7 = 2097151 & (load_3(b + 18) >> 3);
+ int64_t b8 = 2097151 & load_3(b + 21);
+ int64_t b9 = 2097151 & (load_4(b + 23) >> 5);
+ int64_t b10 = 2097151 & (load_3(b + 26) >> 2);
+ int64_t b11 = (load_4(b + 28) >> 7);
+
+ int64_t c0 = 2097151 & load_3(c);
+ int64_t c1 = 2097151 & (load_4(c + 2) >> 5);
+ int64_t c2 = 2097151 & (load_3(c + 5) >> 2);
+ int64_t c3 = 2097151 & (load_4(c + 7) >> 7);
+ int64_t c4 = 2097151 & (load_4(c + 10) >> 4);
+ int64_t c5 = 2097151 & (load_3(c + 13) >> 1);
+ int64_t c6 = 2097151 & (load_4(c + 15) >> 6);
+ int64_t c7 = 2097151 & (load_3(c + 18) >> 3);
+ int64_t c8 = 2097151 & load_3(c + 21);
+ int64_t c9 = 2097151 & (load_4(c + 23) >> 5);
+ int64_t c10 = 2097151 & (load_3(c + 26) >> 2);
+ int64_t c11 = (load_4(c + 28) >> 7);
+
+ int64_t s0;
+ int64_t s1;
+ int64_t s2;
+ int64_t s3;
+ int64_t s4;
+ int64_t s5;
+ int64_t s6;
+ int64_t s7;
+ int64_t s8;
+ int64_t s9;
+ int64_t s10;
+ int64_t s11;
+ int64_t s12;
+ int64_t s13;
+ int64_t s14;
+ int64_t s15;
+ int64_t s16;
+ int64_t s17;
+ int64_t s18;
+ int64_t s19;
+ int64_t s20;
+ int64_t s21;
+ int64_t s22;
+ int64_t s23;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+ int64_t carry10;
+ int64_t carry11;
+ int64_t carry12;
+ int64_t carry13;
+ int64_t carry14;
+ int64_t carry15;
+ int64_t carry16;
+ int64_t carry17;
+ int64_t carry18;
+ int64_t carry19;
+ int64_t carry20;
+ int64_t carry21;
+ int64_t carry22;
+
+ s0 = c0 + a0 * b0;
+ s1 = c1 + a0 * b1 + a1 * b0;
+ s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0;
+ s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0;
+ s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0;
+ s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0;
+ s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 +
+ a6 * b0;
+ s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 +
+ a6 * b1 + a7 * b0;
+ s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 +
+ a6 * b2 + a7 * b1 + a8 * b0;
+ s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 +
+ a6 * b3 + a7 * b2 + a8 * b1 + a9 * b0;
+ s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 +
+ a6 * b4 + a7 * b3 + a8 * b2 + a9 * b1 + a10 * b0;
+ s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 +
+ a6 * b5 + a7 * b4 + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0;
+ s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 +
+ a7 * b5 + a8 * b4 + a9 * b3 + a10 * b2 + a11 * b1;
+ s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 +
+ a8 * b5 + a9 * b4 + a10 * b3 + a11 * b2;
+ s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 +
+ a9 * b5 + a10 * b4 + a11 * b3;
+ s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 +
+ a10 * b5 + a11 * b4;
+ s16 =
+ a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5;
+ s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6;
+ s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7;
+ s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8;
+ s20 = a9 * b11 + a10 * b10 + a11 * b9;
+ s21 = a10 * b11 + a11 * b10;
+ s22 = a11 * b11;
+ s23 = 0;
+
+ carry0 = (s0 + (int64_t) (1L << 20)) >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry2 = (s2 + (int64_t) (1L << 20)) >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry4 = (s4 + (int64_t) (1L << 20)) >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry12 = (s12 + (int64_t) (1L << 20)) >> 21;
+ s13 += carry12;
+ s12 -= carry12 * ((uint64_t) 1L << 21);
+ carry14 = (s14 + (int64_t) (1L << 20)) >> 21;
+ s15 += carry14;
+ s14 -= carry14 * ((uint64_t) 1L << 21);
+ carry16 = (s16 + (int64_t) (1L << 20)) >> 21;
+ s17 += carry16;
+ s16 -= carry16 * ((uint64_t) 1L << 21);
+ carry18 = (s18 + (int64_t) (1L << 20)) >> 21;
+ s19 += carry18;
+ s18 -= carry18 * ((uint64_t) 1L << 21);
+ carry20 = (s20 + (int64_t) (1L << 20)) >> 21;
+ s21 += carry20;
+ s20 -= carry20 * ((uint64_t) 1L << 21);
+ carry22 = (s22 + (int64_t) (1L << 20)) >> 21;
+ s23 += carry22;
+ s22 -= carry22 * ((uint64_t) 1L << 21);
+
+ carry1 = (s1 + (int64_t) (1L << 20)) >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry3 = (s3 + (int64_t) (1L << 20)) >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry5 = (s5 + (int64_t) (1L << 20)) >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+ carry13 = (s13 + (int64_t) (1L << 20)) >> 21;
+ s14 += carry13;
+ s13 -= carry13 * ((uint64_t) 1L << 21);
+ carry15 = (s15 + (int64_t) (1L << 20)) >> 21;
+ s16 += carry15;
+ s15 -= carry15 * ((uint64_t) 1L << 21);
+ carry17 = (s17 + (int64_t) (1L << 20)) >> 21;
+ s18 += carry17;
+ s17 -= carry17 * ((uint64_t) 1L << 21);
+ carry19 = (s19 + (int64_t) (1L << 20)) >> 21;
+ s20 += carry19;
+ s19 -= carry19 * ((uint64_t) 1L << 21);
+ carry21 = (s21 + (int64_t) (1L << 20)) >> 21;
+ s22 += carry21;
+ s21 -= carry21 * ((uint64_t) 1L << 21);
+
+ s11 += s23 * 666643;
+ s12 += s23 * 470296;
+ s13 += s23 * 654183;
+ s14 -= s23 * 997805;
+ s15 += s23 * 136657;
+ s16 -= s23 * 683901;
+
+ s10 += s22 * 666643;
+ s11 += s22 * 470296;
+ s12 += s22 * 654183;
+ s13 -= s22 * 997805;
+ s14 += s22 * 136657;
+ s15 -= s22 * 683901;
+
+ s9 += s21 * 666643;
+ s10 += s21 * 470296;
+ s11 += s21 * 654183;
+ s12 -= s21 * 997805;
+ s13 += s21 * 136657;
+ s14 -= s21 * 683901;
+
+ s8 += s20 * 666643;
+ s9 += s20 * 470296;
+ s10 += s20 * 654183;
+ s11 -= s20 * 997805;
+ s12 += s20 * 136657;
+ s13 -= s20 * 683901;
+
+ s7 += s19 * 666643;
+ s8 += s19 * 470296;
+ s9 += s19 * 654183;
+ s10 -= s19 * 997805;
+ s11 += s19 * 136657;
+ s12 -= s19 * 683901;
+
+ s6 += s18 * 666643;
+ s7 += s18 * 470296;
+ s8 += s18 * 654183;
+ s9 -= s18 * 997805;
+ s10 += s18 * 136657;
+ s11 -= s18 * 683901;
+
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry12 = (s12 + (int64_t) (1L << 20)) >> 21;
+ s13 += carry12;
+ s12 -= carry12 * ((uint64_t) 1L << 21);
+ carry14 = (s14 + (int64_t) (1L << 20)) >> 21;
+ s15 += carry14;
+ s14 -= carry14 * ((uint64_t) 1L << 21);
+ carry16 = (s16 + (int64_t) (1L << 20)) >> 21;
+ s17 += carry16;
+ s16 -= carry16 * ((uint64_t) 1L << 21);
+
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+ carry13 = (s13 + (int64_t) (1L << 20)) >> 21;
+ s14 += carry13;
+ s13 -= carry13 * ((uint64_t) 1L << 21);
+ carry15 = (s15 + (int64_t) (1L << 20)) >> 21;
+ s16 += carry15;
+ s15 -= carry15 * ((uint64_t) 1L << 21);
+
+ s5 += s17 * 666643;
+ s6 += s17 * 470296;
+ s7 += s17 * 654183;
+ s8 -= s17 * 997805;
+ s9 += s17 * 136657;
+ s10 -= s17 * 683901;
+
+ s4 += s16 * 666643;
+ s5 += s16 * 470296;
+ s6 += s16 * 654183;
+ s7 -= s16 * 997805;
+ s8 += s16 * 136657;
+ s9 -= s16 * 683901;
+
+ s3 += s15 * 666643;
+ s4 += s15 * 470296;
+ s5 += s15 * 654183;
+ s6 -= s15 * 997805;
+ s7 += s15 * 136657;
+ s8 -= s15 * 683901;
+
+ s2 += s14 * 666643;
+ s3 += s14 * 470296;
+ s4 += s14 * 654183;
+ s5 -= s14 * 997805;
+ s6 += s14 * 136657;
+ s7 -= s14 * 683901;
+
+ s1 += s13 * 666643;
+ s2 += s13 * 470296;
+ s3 += s13 * 654183;
+ s4 -= s13 * 997805;
+ s5 += s13 * 136657;
+ s6 -= s13 * 683901;
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = (s0 + (int64_t) (1L << 20)) >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry2 = (s2 + (int64_t) (1L << 20)) >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry4 = (s4 + (int64_t) (1L << 20)) >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ carry1 = (s1 + (int64_t) (1L << 20)) >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry3 = (s3 + (int64_t) (1L << 20)) >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry5 = (s5 + (int64_t) (1L << 20)) >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry11 = s11 >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ s[0] = s0 >> 0;
+ s[1] = s0 >> 8;
+ s[2] = (s0 >> 16) | (s1 * ((uint64_t) 1 << 5));
+ s[3] = s1 >> 3;
+ s[4] = s1 >> 11;
+ s[5] = (s1 >> 19) | (s2 * ((uint64_t) 1 << 2));
+ s[6] = s2 >> 6;
+ s[7] = (s2 >> 14) | (s3 * ((uint64_t) 1 << 7));
+ s[8] = s3 >> 1;
+ s[9] = s3 >> 9;
+ s[10] = (s3 >> 17) | (s4 * ((uint64_t) 1 << 4));
+ s[11] = s4 >> 4;
+ s[12] = s4 >> 12;
+ s[13] = (s4 >> 20) | (s5 * ((uint64_t) 1 << 1));
+ s[14] = s5 >> 7;
+ s[15] = (s5 >> 15) | (s6 * ((uint64_t) 1 << 6));
+ s[16] = s6 >> 2;
+ s[17] = s6 >> 10;
+ s[18] = (s6 >> 18) | (s7 * ((uint64_t) 1 << 3));
+ s[19] = s7 >> 5;
+ s[20] = s7 >> 13;
+ s[21] = s8 >> 0;
+ s[22] = s8 >> 8;
+ s[23] = (s8 >> 16) | (s9 * ((uint64_t) 1 << 5));
+ s[24] = s9 >> 3;
+ s[25] = s9 >> 11;
+ s[26] = (s9 >> 19) | (s10 * ((uint64_t) 1 << 2));
+ s[27] = s10 >> 6;
+ s[28] = (s10 >> 14) | (s11 * ((uint64_t) 1 << 7));
+ s[29] = s11 >> 1;
+ s[30] = s11 >> 9;
+ s[31] = s11 >> 17;
+}
+
+/*
+ Input:
+ a[0]+256*a[1]+...+256^31*a[31] = a
+ *
+ Output:
+ s[0]+256*s[1]+...+256^31*s[31] = a^2 mod l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ */
+
+static inline void
+sc25519_sq(unsigned char *s, const unsigned char *a)
+{
+ sc25519_mul(s, a, a);
+}
+
+/*
+ Input:
+ s[0]+256*a[1]+...+256^31*a[31] = a
+ n
+ *
+ Output:
+ s[0]+256*s[1]+...+256^31*s[31] = x * s^(s^n) mod l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ Overwrites s in place.
+ */
+
+static inline void
+sc25519_sqmul(unsigned char s[32], const int n, const unsigned char a[32])
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ sc25519_sq(s, s);
+ }
+ sc25519_mul(s, s, a);
+}
+
+void
+sc25519_invert(unsigned char recip[32], const unsigned char s[32])
+{
+ unsigned char _10[32], _100[32], _1000[32], _10000[32], _100000[32],
+ _1000000[32], _10010011[32], _10010111[32], _100110[32], _1010[32],
+ _1010000[32], _1010011[32], _1011[32], _10110[32], _10111101[32],
+ _11[32], _1100011[32], _1100111[32], _11010011[32], _1101011[32],
+ _11100111[32], _11101011[32], _11110101[32];
+
+ sc25519_sq(_10, s);
+ sc25519_mul(_11, s, _10);
+ sc25519_mul(_100, s, _11);
+ sc25519_sq(_1000, _100);
+ sc25519_mul(_1010, _10, _1000);
+ sc25519_mul(_1011, s, _1010);
+ sc25519_sq(_10000, _1000);
+ sc25519_sq(_10110, _1011);
+ sc25519_mul(_100000, _1010, _10110);
+ sc25519_mul(_100110, _10000, _10110);
+ sc25519_sq(_1000000, _100000);
+ sc25519_mul(_1010000, _10000, _1000000);
+ sc25519_mul(_1010011, _11, _1010000);
+ sc25519_mul(_1100011, _10000, _1010011);
+ sc25519_mul(_1100111, _100, _1100011);
+ sc25519_mul(_1101011, _100, _1100111);
+ sc25519_mul(_10010011, _1000000, _1010011);
+ sc25519_mul(_10010111, _100, _10010011);
+ sc25519_mul(_10111101, _100110, _10010111);
+ sc25519_mul(_11010011, _10110, _10111101);
+ sc25519_mul(_11100111, _1010000, _10010111);
+ sc25519_mul(_11101011, _100, _11100111);
+ sc25519_mul(_11110101, _1010, _11101011);
+
+ sc25519_mul(recip, _1011, _11110101);
+ sc25519_sqmul(recip, 126, _1010011);
+ sc25519_sqmul(recip, 9, _10);
+ sc25519_mul(recip, recip, _11110101);
+ sc25519_sqmul(recip, 7, _1100111);
+ sc25519_sqmul(recip, 9, _11110101);
+ sc25519_sqmul(recip, 11, _10111101);
+ sc25519_sqmul(recip, 8, _11100111);
+ sc25519_sqmul(recip, 9, _1101011);
+ sc25519_sqmul(recip, 6, _1011);
+ sc25519_sqmul(recip, 14, _10010011);
+ sc25519_sqmul(recip, 10, _1100011);
+ sc25519_sqmul(recip, 9, _10010111);
+ sc25519_sqmul(recip, 10, _11110101);
+ sc25519_sqmul(recip, 8, _11010011);
+ sc25519_sqmul(recip, 8, _11101011);
+}
+
+/*
+ Input:
+ s[0]+256*s[1]+...+256^63*s[63] = s
+ *
+ Output:
+ s[0]+256*s[1]+...+256^31*s[31] = s mod l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ Overwrites s in place.
+ */
+
+void
+sc25519_reduce(unsigned char s[64])
+{
+ int64_t s0 = 2097151 & load_3(s);
+ int64_t s1 = 2097151 & (load_4(s + 2) >> 5);
+ int64_t s2 = 2097151 & (load_3(s + 5) >> 2);
+ int64_t s3 = 2097151 & (load_4(s + 7) >> 7);
+ int64_t s4 = 2097151 & (load_4(s + 10) >> 4);
+ int64_t s5 = 2097151 & (load_3(s + 13) >> 1);
+ int64_t s6 = 2097151 & (load_4(s + 15) >> 6);
+ int64_t s7 = 2097151 & (load_3(s + 18) >> 3);
+ int64_t s8 = 2097151 & load_3(s + 21);
+ int64_t s9 = 2097151 & (load_4(s + 23) >> 5);
+ int64_t s10 = 2097151 & (load_3(s + 26) >> 2);
+ int64_t s11 = 2097151 & (load_4(s + 28) >> 7);
+ int64_t s12 = 2097151 & (load_4(s + 31) >> 4);
+ int64_t s13 = 2097151 & (load_3(s + 34) >> 1);
+ int64_t s14 = 2097151 & (load_4(s + 36) >> 6);
+ int64_t s15 = 2097151 & (load_3(s + 39) >> 3);
+ int64_t s16 = 2097151 & load_3(s + 42);
+ int64_t s17 = 2097151 & (load_4(s + 44) >> 5);
+ int64_t s18 = 2097151 & (load_3(s + 47) >> 2);
+ int64_t s19 = 2097151 & (load_4(s + 49) >> 7);
+ int64_t s20 = 2097151 & (load_4(s + 52) >> 4);
+ int64_t s21 = 2097151 & (load_3(s + 55) >> 1);
+ int64_t s22 = 2097151 & (load_4(s + 57) >> 6);
+ int64_t s23 = (load_4(s + 60) >> 3);
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+ int64_t carry10;
+ int64_t carry11;
+ int64_t carry12;
+ int64_t carry13;
+ int64_t carry14;
+ int64_t carry15;
+ int64_t carry16;
+
+ s11 += s23 * 666643;
+ s12 += s23 * 470296;
+ s13 += s23 * 654183;
+ s14 -= s23 * 997805;
+ s15 += s23 * 136657;
+ s16 -= s23 * 683901;
+
+ s10 += s22 * 666643;
+ s11 += s22 * 470296;
+ s12 += s22 * 654183;
+ s13 -= s22 * 997805;
+ s14 += s22 * 136657;
+ s15 -= s22 * 683901;
+
+ s9 += s21 * 666643;
+ s10 += s21 * 470296;
+ s11 += s21 * 654183;
+ s12 -= s21 * 997805;
+ s13 += s21 * 136657;
+ s14 -= s21 * 683901;
+
+ s8 += s20 * 666643;
+ s9 += s20 * 470296;
+ s10 += s20 * 654183;
+ s11 -= s20 * 997805;
+ s12 += s20 * 136657;
+ s13 -= s20 * 683901;
+
+ s7 += s19 * 666643;
+ s8 += s19 * 470296;
+ s9 += s19 * 654183;
+ s10 -= s19 * 997805;
+ s11 += s19 * 136657;
+ s12 -= s19 * 683901;
+
+ s6 += s18 * 666643;
+ s7 += s18 * 470296;
+ s8 += s18 * 654183;
+ s9 -= s18 * 997805;
+ s10 += s18 * 136657;
+ s11 -= s18 * 683901;
+
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry12 = (s12 + (int64_t) (1L << 20)) >> 21;
+ s13 += carry12;
+ s12 -= carry12 * ((uint64_t) 1L << 21);
+ carry14 = (s14 + (int64_t) (1L << 20)) >> 21;
+ s15 += carry14;
+ s14 -= carry14 * ((uint64_t) 1L << 21);
+ carry16 = (s16 + (int64_t) (1L << 20)) >> 21;
+ s17 += carry16;
+ s16 -= carry16 * ((uint64_t) 1L << 21);
+
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+ carry13 = (s13 + (int64_t) (1L << 20)) >> 21;
+ s14 += carry13;
+ s13 -= carry13 * ((uint64_t) 1L << 21);
+ carry15 = (s15 + (int64_t) (1L << 20)) >> 21;
+ s16 += carry15;
+ s15 -= carry15 * ((uint64_t) 1L << 21);
+
+ s5 += s17 * 666643;
+ s6 += s17 * 470296;
+ s7 += s17 * 654183;
+ s8 -= s17 * 997805;
+ s9 += s17 * 136657;
+ s10 -= s17 * 683901;
+
+ s4 += s16 * 666643;
+ s5 += s16 * 470296;
+ s6 += s16 * 654183;
+ s7 -= s16 * 997805;
+ s8 += s16 * 136657;
+ s9 -= s16 * 683901;
+
+ s3 += s15 * 666643;
+ s4 += s15 * 470296;
+ s5 += s15 * 654183;
+ s6 -= s15 * 997805;
+ s7 += s15 * 136657;
+ s8 -= s15 * 683901;
+
+ s2 += s14 * 666643;
+ s3 += s14 * 470296;
+ s4 += s14 * 654183;
+ s5 -= s14 * 997805;
+ s6 += s14 * 136657;
+ s7 -= s14 * 683901;
+
+ s1 += s13 * 666643;
+ s2 += s13 * 470296;
+ s3 += s13 * 654183;
+ s4 -= s13 * 997805;
+ s5 += s13 * 136657;
+ s6 -= s13 * 683901;
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = (s0 + (int64_t) (1L << 20)) >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry2 = (s2 + (int64_t) (1L << 20)) >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry4 = (s4 + (int64_t) (1L << 20)) >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry6 = (s6 + (int64_t) (1L << 20)) >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry8 = (s8 + (int64_t) (1L << 20)) >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry10 = (s10 + (int64_t) (1L << 20)) >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ carry1 = (s1 + (int64_t) (1L << 20)) >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry3 = (s3 + (int64_t) (1L << 20)) >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry5 = (s5 + (int64_t) (1L << 20)) >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry7 = (s7 + (int64_t) (1L << 20)) >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry9 = (s9 + (int64_t) (1L << 20)) >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry11 = (s11 + (int64_t) (1L << 20)) >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+ s12 = 0;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+ carry11 = s11 >> 21;
+ s12 += carry11;
+ s11 -= carry11 * ((uint64_t) 1L << 21);
+
+ s0 += s12 * 666643;
+ s1 += s12 * 470296;
+ s2 += s12 * 654183;
+ s3 -= s12 * 997805;
+ s4 += s12 * 136657;
+ s5 -= s12 * 683901;
+
+ carry0 = s0 >> 21;
+ s1 += carry0;
+ s0 -= carry0 * ((uint64_t) 1L << 21);
+ carry1 = s1 >> 21;
+ s2 += carry1;
+ s1 -= carry1 * ((uint64_t) 1L << 21);
+ carry2 = s2 >> 21;
+ s3 += carry2;
+ s2 -= carry2 * ((uint64_t) 1L << 21);
+ carry3 = s3 >> 21;
+ s4 += carry3;
+ s3 -= carry3 * ((uint64_t) 1L << 21);
+ carry4 = s4 >> 21;
+ s5 += carry4;
+ s4 -= carry4 * ((uint64_t) 1L << 21);
+ carry5 = s5 >> 21;
+ s6 += carry5;
+ s5 -= carry5 * ((uint64_t) 1L << 21);
+ carry6 = s6 >> 21;
+ s7 += carry6;
+ s6 -= carry6 * ((uint64_t) 1L << 21);
+ carry7 = s7 >> 21;
+ s8 += carry7;
+ s7 -= carry7 * ((uint64_t) 1L << 21);
+ carry8 = s8 >> 21;
+ s9 += carry8;
+ s8 -= carry8 * ((uint64_t) 1L << 21);
+ carry9 = s9 >> 21;
+ s10 += carry9;
+ s9 -= carry9 * ((uint64_t) 1L << 21);
+ carry10 = s10 >> 21;
+ s11 += carry10;
+ s10 -= carry10 * ((uint64_t) 1L << 21);
+
+ s[0] = s0 >> 0;
+ s[1] = s0 >> 8;
+ s[2] = (s0 >> 16) | (s1 * ((uint64_t) 1 << 5));
+ s[3] = s1 >> 3;
+ s[4] = s1 >> 11;
+ s[5] = (s1 >> 19) | (s2 * ((uint64_t) 1 << 2));
+ s[6] = s2 >> 6;
+ s[7] = (s2 >> 14) | (s3 * ((uint64_t) 1 << 7));
+ s[8] = s3 >> 1;
+ s[9] = s3 >> 9;
+ s[10] = (s3 >> 17) | (s4 * ((uint64_t) 1 << 4));
+ s[11] = s4 >> 4;
+ s[12] = s4 >> 12;
+ s[13] = (s4 >> 20) | (s5 * ((uint64_t) 1 << 1));
+ s[14] = s5 >> 7;
+ s[15] = (s5 >> 15) | (s6 * ((uint64_t) 1 << 6));
+ s[16] = s6 >> 2;
+ s[17] = s6 >> 10;
+ s[18] = (s6 >> 18) | (s7 * ((uint64_t) 1 << 3));
+ s[19] = s7 >> 5;
+ s[20] = s7 >> 13;
+ s[21] = s8 >> 0;
+ s[22] = s8 >> 8;
+ s[23] = (s8 >> 16) | (s9 * ((uint64_t) 1 << 5));
+ s[24] = s9 >> 3;
+ s[25] = s9 >> 11;
+ s[26] = (s9 >> 19) | (s10 * ((uint64_t) 1 << 2));
+ s[27] = s10 >> 6;
+ s[28] = (s10 >> 14) | (s11 * ((uint64_t) 1 << 7));
+ s[29] = s11 >> 1;
+ s[30] = s11 >> 9;
+ s[31] = s11 >> 17;
+}
+
+int
+sc25519_is_canonical(const unsigned char s[32])
+{
+ /* 2^252+27742317777372353535851937790883648493 */
+ static const unsigned char L[32] = {
+ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7,
+ 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
+ };
+ unsigned char c = 0;
+ unsigned char n = 1;
+ unsigned int i = 32;
+
+ do {
+ i--;
+ c |= ((s[i] - L[i]) >> 8) & n;
+ n &= ((s[i] ^ L[i]) - 1) >> 8;
+ } while (i != 0);
+
+ return (c != 0);
+}
+
+/* multiply by the cofactor */
+static void
+ge25519_clear_cofactor(ge25519_p3 *p3)
+{
+ ge25519_p1p1 p1;
+ ge25519_p2 p2;
+
+ ge25519_p3_dbl(&p1, p3);
+ ge25519_p1p1_to_p2(&p2, &p1);
+ ge25519_p2_dbl(&p1, &p2);
+ ge25519_p1p1_to_p2(&p2, &p1);
+ ge25519_p2_dbl(&p1, &p2);
+ ge25519_p1p1_to_p3(p3, &p1);
+}
+
+static void
+ge25519_elligator2(unsigned char s[32], const fe25519 r, const unsigned char x_sign)
+{
+ fe25519 gx;
+ fe25519 negx;
+ fe25519 rr2;
+ fe25519 x, x2, x3;
+ ge25519_p3 p3;
+ unsigned int notsquare;
+
+ fe25519_sq2(rr2, r);
+ rr2[0]++;
+ fe25519_invert(rr2, rr2);
+ fe25519_mul32(x, rr2, curve25519_A[0]);
+ fe25519_neg(x, x);
+
+ fe25519_sq(x2, x);
+ fe25519_mul(x3, x, x2);
+ fe25519_add(gx, x3, x);
+ fe25519_mul32(x2, x2, curve25519_A[0]);
+ fe25519_add(gx, x2, gx);
+
+ notsquare = fe25519_notsquare(gx);
+ fe25519_neg(negx, x);
+ fe25519_cmov(x, negx, notsquare);
+ fe25519_0(x2);
+ fe25519_cmov(x2, curve25519_A, notsquare);
+ fe25519_sub(x, x, x2);
+
+ /* yed = (x-1)/(x+1) */
+ {
+ fe25519 one;
+ fe25519 x_plus_one;
+ fe25519 x_plus_one_inv;
+ fe25519 x_minus_one;
+ fe25519 yed;
+
+ fe25519_1(one);
+ fe25519_add(x_plus_one, x, one);
+ fe25519_sub(x_minus_one, x, one);
+ fe25519_invert(x_plus_one_inv, x_plus_one);
+ fe25519_mul(yed, x_minus_one, x_plus_one_inv);
+ fe25519_tobytes(s, yed);
+ }
+
+ /* recover x */
+ s[31] |= x_sign;
+ if (ge25519_frombytes(&p3, s) != 0) {
+ abort(); /* LCOV_EXCL_LINE */
+ }
+
+ ge25519_clear_cofactor(&p3);
+ ge25519_p3_tobytes(s, &p3);
+}
+
+void
+ge25519_from_uniform(unsigned char s[32], const unsigned char r[32])
+{
+ fe25519 r_fe;
+ unsigned char x_sign;
+
+ memcpy(s, r, 32);
+ x_sign = s[31] & 0x80;
+ s[31] &= 0x7f;
+ fe25519_frombytes(r_fe, s);
+ ge25519_elligator2(s, r_fe, x_sign);
+}
+
+void
+ge25519_from_hash(unsigned char s[32], const unsigned char h[64])
+{
+ unsigned char fl[32];
+ unsigned char gl[32];
+ fe25519 fe_f;
+ fe25519 fe_g;
+ size_t i;
+ unsigned char x_sign;
+
+ x_sign = h[0] & 0x80;
+ for (i = 0; i < 32; i++) {
+ fl[i] = h[63 - i];
+ gl[i] = h[31 - i];
+ }
+ fl[31] &= 0x7f;
+ gl[31] &= 0x7f;
+ fe25519_frombytes(fe_f, fl);
+ fe25519_frombytes(fe_g, gl);
+ fe_f[0] += (h[32] >> 7) * 19;
+ for (i = 0; i < sizeof (fe25519) / sizeof fe_f[0]; i++) {
+ fe_f[i] += 38 * fe_g[i];
+ }
+ fe25519_reduce(fe_f, fe_f);
+ ge25519_elligator2(s, fe_f, x_sign);
+}
+
+/* Ristretto group */
+
+static int
+ristretto255_sqrt_ratio_m1(fe25519 x, const fe25519 u, const fe25519 v)
+{
+ fe25519 v3;
+ fe25519 vxx;
+ fe25519 m_root_check, p_root_check, f_root_check;
+ fe25519 x_sqrtm1;
+ int has_m_root, has_p_root, has_f_root;
+
+ fe25519_sq(v3, v);
+ fe25519_mul(v3, v3, v); /* v3 = v^3 */
+ fe25519_sq(x, v3);
+ fe25519_mul(x, x, v);
+ fe25519_mul(x, x, u); /* x = uv^7 */
+
+ fe25519_pow22523(x, x); /* x = (uv^7)^((q-5)/8) */
+ fe25519_mul(x, x, v3);
+ fe25519_mul(x, x, u); /* x = uv^3(uv^7)^((q-5)/8) */
+
+ fe25519_sq(vxx, x);
+ fe25519_mul(vxx, vxx, v); /* vx^2 */
+ fe25519_sub(m_root_check, vxx, u); /* vx^2-u */
+ fe25519_add(p_root_check, vxx, u); /* vx^2+u */
+ fe25519_mul(f_root_check, u, sqrtm1); /* u*sqrt(-1) */
+ fe25519_add(f_root_check, vxx, f_root_check); /* vx^2+u*sqrt(-1) */
+ has_m_root = fe25519_iszero(m_root_check);
+ has_p_root = fe25519_iszero(p_root_check);
+ has_f_root = fe25519_iszero(f_root_check);
+ fe25519_mul(x_sqrtm1, x, sqrtm1); /* x*sqrt(-1) */
+
+ fe25519_cmov(x, x_sqrtm1, has_p_root | has_f_root);
+ fe25519_abs(x, x);
+
+ return has_m_root | has_p_root;
+}
+
+static int
+ristretto255_is_canonical(const unsigned char *s)
+{
+ unsigned char c;
+ unsigned char d;
+ unsigned char e;
+ unsigned int i;
+
+ c = (s[31] & 0x7f) ^ 0x7f;
+ for (i = 30; i > 0; i--) {
+ c |= s[i] ^ 0xff;
+ }
+ c = (((unsigned int) c) - 1U) >> 8;
+ d = (0xed - 1U - (unsigned int) s[0]) >> 8;
+ e = s[31] >> 7;
+
+ return 1 - (((c & d) | e | s[0]) & 1);
+}
+
+int
+ristretto255_frombytes(ge25519_p3 *h, const unsigned char *s)
+{
+ fe25519 inv_sqrt;
+ fe25519 one;
+ fe25519 s_;
+ fe25519 ss;
+ fe25519 u1, u2;
+ fe25519 u1u1, u2u2;
+ fe25519 v;
+ fe25519 v_u2u2;
+ int was_square;
+
+ if (ristretto255_is_canonical(s) == 0) {
+ return -1;
+ }
+ fe25519_frombytes(s_, s);
+ fe25519_sq(ss, s_); /* ss = s^2 */
+
+ fe25519_1(u1);
+ fe25519_sub(u1, u1, ss); /* u1 = 1-ss */
+ fe25519_sq(u1u1, u1); /* u1u1 = u1^2 */
+
+ fe25519_1(u2);
+ fe25519_add(u2, u2, ss); /* u2 = 1+ss */
+ fe25519_sq(u2u2, u2); /* u2u2 = u2^2 */
+
+ fe25519_mul(v, d, u1u1); /* v = d*u1^2 */
+ fe25519_neg(v, v); /* v = -d*u1^2 */
+ fe25519_sub(v, v, u2u2); /* v = -(d*u1^2)-u2^2 */
+
+ fe25519_mul(v_u2u2, v, u2u2); /* v_u2u2 = v*u2^2 */
+
+ fe25519_1(one);
+ was_square = ristretto255_sqrt_ratio_m1(inv_sqrt, one, v_u2u2);
+ fe25519_mul(h->X, inv_sqrt, u2);
+ fe25519_mul(h->Y, inv_sqrt, h->X);
+ fe25519_mul(h->Y, h->Y, v);
+
+ fe25519_mul(h->X, h->X, s_);
+ fe25519_add(h->X, h->X, h->X);
+ fe25519_abs(h->X, h->X);
+ fe25519_mul(h->Y, u1, h->Y);
+ fe25519_1(h->Z);
+ fe25519_mul(h->T, h->X, h->Y);
+
+ return - ((1 - was_square) |
+ fe25519_isnegative(h->T) | fe25519_iszero(h->Y));
+}
+
+void
+ristretto255_p3_tobytes(unsigned char *s, const ge25519_p3 *h)
+{
+ fe25519 den1, den2;
+ fe25519 den_inv;
+ fe25519 eden;
+ fe25519 inv_sqrt;
+ fe25519 ix, iy;
+ fe25519 one;
+ fe25519 s_;
+ fe25519 t_z_inv;
+ fe25519 u1, u2;
+ fe25519 u1_u2u2;
+ fe25519 x_, y_;
+ fe25519 x_z_inv;
+ fe25519 z_inv;
+ fe25519 zmy;
+ int rotate;
+
+ fe25519_add(u1, h->Z, h->Y); /* u1 = Z+Y */
+ fe25519_sub(zmy, h->Z, h->Y); /* zmy = Z-Y */
+ fe25519_mul(u1, u1, zmy); /* u1 = (Z+Y)*(Z-Y) */
+ fe25519_mul(u2, h->X, h->Y); /* u2 = X*Y */
+
+ fe25519_sq(u1_u2u2, u2); /* u1_u2u2 = u2^2 */
+ fe25519_mul(u1_u2u2, u1, u1_u2u2); /* u1_u2u2 = u1*u2^2 */
+
+ fe25519_1(one);
+ (void) ristretto255_sqrt_ratio_m1(inv_sqrt, one, u1_u2u2);
+ fe25519_mul(den1, inv_sqrt, u1); /* den1 = inv_sqrt*u1 */
+ fe25519_mul(den2, inv_sqrt, u2); /* den2 = inv_sqrt*u2 */
+ fe25519_mul(z_inv, den1, den2); /* z_inv = den1*den2 */
+ fe25519_mul(z_inv, z_inv, h->T); /* z_inv = den1*den2*T */
+
+ fe25519_mul(ix, h->X, sqrtm1); /* ix = X*sqrt(-1) */
+ fe25519_mul(iy, h->Y, sqrtm1); /* iy = Y*sqrt(-1) */
+ fe25519_mul(eden, den1, invsqrtamd); /* eden = den1/sqrt(a-d) */
+
+ fe25519_mul(t_z_inv, h->T, z_inv); /* t_z_inv = T*z_inv */
+ rotate = fe25519_isnegative(t_z_inv);
+
+ fe25519_copy(x_, h->X);
+ fe25519_copy(y_, h->Y);
+ fe25519_copy(den_inv, den2);
+
+ fe25519_cmov(x_, iy, rotate);
+ fe25519_cmov(y_, ix, rotate);
+ fe25519_cmov(den_inv, eden, rotate);
+
+ fe25519_mul(x_z_inv, x_, z_inv);
+ fe25519_cneg(y_, y_, fe25519_isnegative(x_z_inv));
+
+ fe25519_sub(s_, h->Z, y_);
+ fe25519_mul(s_, den_inv, s_);
+ fe25519_abs(s_, s_);
+ fe25519_tobytes(s, s_);
+}
+
+static void
+ristretto255_elligator(ge25519_p3 *p, const fe25519 t)
+{
+ fe25519 c;
+ fe25519 n;
+ fe25519 one;
+ fe25519 r;
+ fe25519 rpd;
+ fe25519 s, s_prime;
+ fe25519 ss;
+ fe25519 u, v;
+ fe25519 w0, w1, w2, w3;
+ int wasnt_square;
+
+ fe25519_1(one);
+ fe25519_sq(r, t); /* r = t^2 */
+ fe25519_mul(r, sqrtm1, r); /* r = sqrt(-1)*t^2 */
+ fe25519_add(u, r, one); /* u = r+1 */
+ fe25519_mul(u, u, onemsqd); /* u = (r+1)*(1-d^2) */
+ fe25519_1(c);
+ fe25519_neg(c, c); /* c = -1 */
+ fe25519_add(rpd, r, d); /* rpd = r+d */
+ fe25519_mul(v, r, d); /* v = r*d */
+ fe25519_sub(v, c, v); /* v = c-r*d */
+ fe25519_mul(v, v, rpd); /* v = (c-r*d)*(r+d) */
+
+ wasnt_square = 1 - ristretto255_sqrt_ratio_m1(s, u, v);
+ fe25519_mul(s_prime, s, t);
+ fe25519_abs(s_prime, s_prime);
+ fe25519_neg(s_prime, s_prime); /* s_prime = -|s*t| */
+ fe25519_cmov(s, s_prime, wasnt_square);
+ fe25519_cmov(c, r, wasnt_square);
+
+ fe25519_sub(n, r, one); /* n = r-1 */
+ fe25519_mul(n, n, c); /* n = c*(r-1) */
+ fe25519_mul(n, n, sqdmone); /* n = c*(r-1)*(d-1)^2 */
+ fe25519_sub(n, n, v); /* n = c*(r-1)*(d-1)^2-v */
+
+ fe25519_add(w0, s, s); /* w0 = 2s */
+ fe25519_mul(w0, w0, v); /* w0 = 2s*v */
+ fe25519_mul(w1, n, sqrtadm1); /* w1 = n*sqrt(ad-1) */
+ fe25519_sq(ss, s); /* ss = s^2 */
+ fe25519_sub(w2, one, ss); /* w2 = 1-s^2 */
+ fe25519_add(w3, one, ss); /* w3 = 1+s^2 */
+
+ fe25519_mul(p->X, w0, w3);
+ fe25519_mul(p->Y, w2, w1);
+ fe25519_mul(p->Z, w1, w3);
+ fe25519_mul(p->T, w0, w2);
+}
+
+void
+ristretto255_from_hash(unsigned char s[32], const unsigned char h[64])
+{
+ fe25519 r0, r1;
+ ge25519_cached p1_cached;
+ ge25519_p1p1 p_p1p1;
+ ge25519_p3 p0, p1;
+ ge25519_p3 p;
+
+ fe25519_frombytes(r0, h);
+ fe25519_frombytes(r1, h + 32);
+ ristretto255_elligator(&p0, r0);
+ ristretto255_elligator(&p1, r1);
+ ge25519_p3_to_cached(&p1_cached, &p1);
+ ge25519_add(&p_p1p1, &p0, &p1_cached);
+ ge25519_p1p1_to_p3(&p, &p_p1p1);
+ ristretto255_p3_tobytes(s, &p);
+}
diff --git a/packages/runtime/vendor/libsodium/ed25519_ref10.h b/packages/runtime/vendor/libsodium/ed25519_ref10.h
new file mode 100644
index 000000000..3f4c45caf
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/ed25519_ref10.h
@@ -0,0 +1,142 @@
+#ifndef ed25519_ref10_H
+#define ed25519_ref10_H
+
+#include
+#include
+
+/*
+ fe means field element.
+ Here the field is \Z/(2^255-19).
+ */
+
+#ifdef HAVE_TI_MODE
+typedef uint64_t fe25519[5];
+#else
+typedef int32_t fe25519[10];
+#endif
+
+void fe25519_invert(fe25519 out, const fe25519 z);
+void fe25519_frombytes(fe25519 h, const unsigned char *s);
+void fe25519_tobytes(unsigned char *s, const fe25519 h);
+
+#ifdef HAVE_TI_MODE
+# include "ed25519_ref10_fe_51.h"
+#else
+# include "ed25519_ref10_fe_25_5.h"
+#endif
+
+
+/*
+ ge means group element.
+
+ Here the group is the set of pairs (x,y) of field elements
+ satisfying -x^2 + y^2 = 1 + d x^2y^2
+ where d = -121665/121666.
+
+ Representations:
+ ge25519_p2 (projective): (X:Y:Z) satisfying x=X/Z, y=Y/Z
+ ge25519_p3 (extended): (X:Y:Z:T) satisfying x=X/Z, y=Y/Z, XY=ZT
+ ge25519_p1p1 (completed): ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T
+ ge25519_precomp (Duif): (y+x,y-x,2dxy)
+ */
+
+typedef struct {
+ fe25519 X;
+ fe25519 Y;
+ fe25519 Z;
+} ge25519_p2;
+
+typedef struct {
+ fe25519 X;
+ fe25519 Y;
+ fe25519 Z;
+ fe25519 T;
+} ge25519_p3;
+
+typedef struct {
+ fe25519 X;
+ fe25519 Y;
+ fe25519 Z;
+ fe25519 T;
+} ge25519_p1p1;
+
+typedef struct {
+ fe25519 yplusx;
+ fe25519 yminusx;
+ fe25519 xy2d;
+} ge25519_precomp;
+
+typedef struct {
+ fe25519 YplusX;
+ fe25519 YminusX;
+ fe25519 Z;
+ fe25519 T2d;
+} ge25519_cached;
+
+void ge25519_tobytes(unsigned char *s, const ge25519_p2 *h);
+
+void ge25519_p3_tobytes(unsigned char *s, const ge25519_p3 *h);
+
+int ge25519_frombytes(ge25519_p3 *h, const unsigned char *s);
+
+int ge25519_frombytes_negate_vartime(ge25519_p3 *h, const unsigned char *s);
+
+void ge25519_p3_to_cached(ge25519_cached *r, const ge25519_p3 *p);
+
+void ge25519_p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p);
+
+void ge25519_p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p);
+
+void ge25519_add(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_cached *q);
+
+void ge25519_sub(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_cached *q);
+
+void ge25519_scalarmult_base(ge25519_p3 *h, const unsigned char *a);
+
+void ge25519_double_scalarmult_vartime(ge25519_p2 *r, const unsigned char *a,
+ const ge25519_p3 *A,
+ const unsigned char *b);
+
+void ge25519_scalarmult(ge25519_p3 *h, const unsigned char *a,
+ const ge25519_p3 *p);
+
+int ge25519_is_canonical(const unsigned char *s);
+
+int ge25519_is_on_curve(const ge25519_p3 *p);
+
+int ge25519_is_on_main_subgroup(const ge25519_p3 *p);
+
+int ge25519_has_small_order(const unsigned char s[32]);
+
+void ge25519_from_uniform(unsigned char s[32], const unsigned char r[32]);
+
+void ge25519_from_hash(unsigned char s[32], const unsigned char h[64]);
+
+/*
+ Ristretto group
+ */
+
+int ristretto255_frombytes(ge25519_p3 *h, const unsigned char *s);
+
+void ristretto255_p3_tobytes(unsigned char *s, const ge25519_p3 *h);
+
+void ristretto255_from_hash(unsigned char s[32], const unsigned char h[64]);
+
+/*
+ The set of scalars is \Z/l
+ where l = 2^252 + 27742317777372353535851937790883648493.
+ */
+
+void sc25519_invert(unsigned char recip[32], const unsigned char s[32]);
+
+void sc25519_reduce(unsigned char s[64]);
+
+void sc25519_mul(unsigned char s[32], const unsigned char a[32],
+ const unsigned char b[32]);
+
+void sc25519_muladd(unsigned char s[32], const unsigned char a[32],
+ const unsigned char b[32], const unsigned char c[32]);
+
+int sc25519_is_canonical(const unsigned char s[32]);
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/ed25519_ref10_fe_25_5.h b/packages/runtime/vendor/libsodium/ed25519_ref10_fe_25_5.h
new file mode 100644
index 000000000..a0816b219
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/ed25519_ref10_fe_25_5.h
@@ -0,0 +1,1030 @@
+#include
+
+#include "common.h"
+#include "utils.h"
+
+/*
+ h = 0
+ */
+
+static inline void
+fe25519_0(fe25519 h)
+{
+ memset(&h[0], 0, 10 * sizeof h[0]);
+}
+
+/*
+ h = 1
+ */
+
+static inline void
+fe25519_1(fe25519 h)
+{
+ h[0] = 1;
+ h[1] = 0;
+ memset(&h[2], 0, 8 * sizeof h[0]);
+}
+
+/*
+ h = f + g
+ Can overlap h with f or g.
+ *
+ Preconditions:
+ |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+ */
+
+static inline void
+fe25519_add(fe25519 h, const fe25519 f, const fe25519 g)
+{
+ int32_t h0 = f[0] + g[0];
+ int32_t h1 = f[1] + g[1];
+ int32_t h2 = f[2] + g[2];
+ int32_t h3 = f[3] + g[3];
+ int32_t h4 = f[4] + g[4];
+ int32_t h5 = f[5] + g[5];
+ int32_t h6 = f[6] + g[6];
+ int32_t h7 = f[7] + g[7];
+ int32_t h8 = f[8] + g[8];
+ int32_t h9 = f[9] + g[9];
+
+ h[0] = h0;
+ h[1] = h1;
+ h[2] = h2;
+ h[3] = h3;
+ h[4] = h4;
+ h[5] = h5;
+ h[6] = h6;
+ h[7] = h7;
+ h[8] = h8;
+ h[9] = h9;
+}
+
+/*
+ h = f - g
+ Can overlap h with f or g.
+ *
+ Preconditions:
+ |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+ */
+
+static void
+fe25519_sub(fe25519 h, const fe25519 f, const fe25519 g)
+{
+ int32_t h0 = f[0] - g[0];
+ int32_t h1 = f[1] - g[1];
+ int32_t h2 = f[2] - g[2];
+ int32_t h3 = f[3] - g[3];
+ int32_t h4 = f[4] - g[4];
+ int32_t h5 = f[5] - g[5];
+ int32_t h6 = f[6] - g[6];
+ int32_t h7 = f[7] - g[7];
+ int32_t h8 = f[8] - g[8];
+ int32_t h9 = f[9] - g[9];
+
+ h[0] = h0;
+ h[1] = h1;
+ h[2] = h2;
+ h[3] = h3;
+ h[4] = h4;
+ h[5] = h5;
+ h[6] = h6;
+ h[7] = h7;
+ h[8] = h8;
+ h[9] = h9;
+}
+
+/*
+ h = -f
+ *
+ Preconditions:
+ |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
+ */
+
+static inline void
+fe25519_neg(fe25519 h, const fe25519 f)
+{
+ int32_t h0 = -f[0];
+ int32_t h1 = -f[1];
+ int32_t h2 = -f[2];
+ int32_t h3 = -f[3];
+ int32_t h4 = -f[4];
+ int32_t h5 = -f[5];
+ int32_t h6 = -f[6];
+ int32_t h7 = -f[7];
+ int32_t h8 = -f[8];
+ int32_t h9 = -f[9];
+
+ h[0] = h0;
+ h[1] = h1;
+ h[2] = h2;
+ h[3] = h3;
+ h[4] = h4;
+ h[5] = h5;
+ h[6] = h6;
+ h[7] = h7;
+ h[8] = h8;
+ h[9] = h9;
+}
+
+/*
+ Replace (f,g) with (g,g) if b == 1;
+ replace (f,g) with (f,g) if b == 0.
+ *
+ Preconditions: b in {0,1}.
+ */
+
+static void
+fe25519_cmov(fe25519 f, const fe25519 g, unsigned int b)
+{
+ const uint32_t mask = (uint32_t) (-(int32_t) b);
+
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+
+ int32_t x0 = f0 ^ g[0];
+ int32_t x1 = f1 ^ g[1];
+ int32_t x2 = f2 ^ g[2];
+ int32_t x3 = f3 ^ g[3];
+ int32_t x4 = f4 ^ g[4];
+ int32_t x5 = f5 ^ g[5];
+ int32_t x6 = f6 ^ g[6];
+ int32_t x7 = f7 ^ g[7];
+ int32_t x8 = f8 ^ g[8];
+ int32_t x9 = f9 ^ g[9];
+
+ x0 &= mask;
+ x1 &= mask;
+ x2 &= mask;
+ x3 &= mask;
+ x4 &= mask;
+ x5 &= mask;
+ x6 &= mask;
+ x7 &= mask;
+ x8 &= mask;
+ x9 &= mask;
+
+ f[0] = f0 ^ x0;
+ f[1] = f1 ^ x1;
+ f[2] = f2 ^ x2;
+ f[3] = f3 ^ x3;
+ f[4] = f4 ^ x4;
+ f[5] = f5 ^ x5;
+ f[6] = f6 ^ x6;
+ f[7] = f7 ^ x7;
+ f[8] = f8 ^ x8;
+ f[9] = f9 ^ x9;
+}
+
+static void
+fe25519_cswap(fe25519 f, fe25519 g, unsigned int b)
+{
+ const uint32_t mask = (uint32_t) (-(int64_t) b);
+
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+
+ int32_t g0 = g[0];
+ int32_t g1 = g[1];
+ int32_t g2 = g[2];
+ int32_t g3 = g[3];
+ int32_t g4 = g[4];
+ int32_t g5 = g[5];
+ int32_t g6 = g[6];
+ int32_t g7 = g[7];
+ int32_t g8 = g[8];
+ int32_t g9 = g[9];
+
+ int32_t x0 = f0 ^ g0;
+ int32_t x1 = f1 ^ g1;
+ int32_t x2 = f2 ^ g2;
+ int32_t x3 = f3 ^ g3;
+ int32_t x4 = f4 ^ g4;
+ int32_t x5 = f5 ^ g5;
+ int32_t x6 = f6 ^ g6;
+ int32_t x7 = f7 ^ g7;
+ int32_t x8 = f8 ^ g8;
+ int32_t x9 = f9 ^ g9;
+
+ x0 &= mask;
+ x1 &= mask;
+ x2 &= mask;
+ x3 &= mask;
+ x4 &= mask;
+ x5 &= mask;
+ x6 &= mask;
+ x7 &= mask;
+ x8 &= mask;
+ x9 &= mask;
+
+ f[0] = f0 ^ x0;
+ f[1] = f1 ^ x1;
+ f[2] = f2 ^ x2;
+ f[3] = f3 ^ x3;
+ f[4] = f4 ^ x4;
+ f[5] = f5 ^ x5;
+ f[6] = f6 ^ x6;
+ f[7] = f7 ^ x7;
+ f[8] = f8 ^ x8;
+ f[9] = f9 ^ x9;
+
+ g[0] = g0 ^ x0;
+ g[1] = g1 ^ x1;
+ g[2] = g2 ^ x2;
+ g[3] = g3 ^ x3;
+ g[4] = g4 ^ x4;
+ g[5] = g5 ^ x5;
+ g[6] = g6 ^ x6;
+ g[7] = g7 ^ x7;
+ g[8] = g8 ^ x8;
+ g[9] = g9 ^ x9;
+}
+
+/*
+ h = f
+ */
+
+static inline void
+fe25519_copy(fe25519 h, const fe25519 f)
+{
+ memcpy(h, f, 10 * sizeof h[0]);
+}
+
+/*
+ return 1 if f is in {1,3,5,...,q-2}
+ return 0 if f is in {0,2,4,...,q-1}
+
+ Preconditions:
+ |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+ */
+
+static inline int
+fe25519_isnegative(const fe25519 f)
+{
+ unsigned char s[32];
+
+ fe25519_tobytes(s, f);
+
+ return s[0] & 1;
+}
+
+/*
+ return 1 if f == 0
+ return 0 if f != 0
+
+ Preconditions:
+ |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+ */
+
+static inline int
+fe25519_iszero(const fe25519 f)
+{
+ unsigned char s[32];
+
+ fe25519_tobytes(s, f);
+
+ return sodium_is_zero(s, 32);
+}
+
+/*
+ h = f * g
+ Can overlap h with f or g.
+ *
+ Preconditions:
+ |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+ |g| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+ */
+
+/*
+ Notes on implementation strategy:
+ *
+ Using schoolbook multiplication.
+ Karatsuba would save a little in some cost models.
+ *
+ Most multiplications by 2 and 19 are 32-bit precomputations;
+ cheaper than 64-bit postcomputations.
+ *
+ There is one remaining multiplication by 19 in the carry chain;
+ one *19 precomputation can be merged into this,
+ but the resulting data flow is considerably less clean.
+ *
+ There are 12 carries below.
+ 10 of them are 2-way parallelizable and vectorizable.
+ Can get away with 11 carries, but then data flow is much deeper.
+ *
+ With tighter constraints on inputs can squeeze carries into int32.
+ */
+
+static void
+fe25519_mul(fe25519 h, const fe25519 f, const fe25519 g)
+{
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+
+ int32_t g0 = g[0];
+ int32_t g1 = g[1];
+ int32_t g2 = g[2];
+ int32_t g3 = g[3];
+ int32_t g4 = g[4];
+ int32_t g5 = g[5];
+ int32_t g6 = g[6];
+ int32_t g7 = g[7];
+ int32_t g8 = g[8];
+ int32_t g9 = g[9];
+
+ int32_t g1_19 = 19 * g1; /* 1.959375*2^29 */
+ int32_t g2_19 = 19 * g2; /* 1.959375*2^30; still ok */
+ int32_t g3_19 = 19 * g3;
+ int32_t g4_19 = 19 * g4;
+ int32_t g5_19 = 19 * g5;
+ int32_t g6_19 = 19 * g6;
+ int32_t g7_19 = 19 * g7;
+ int32_t g8_19 = 19 * g8;
+ int32_t g9_19 = 19 * g9;
+ int32_t f1_2 = 2 * f1;
+ int32_t f3_2 = 2 * f3;
+ int32_t f5_2 = 2 * f5;
+ int32_t f7_2 = 2 * f7;
+ int32_t f9_2 = 2 * f9;
+
+ int64_t f0g0 = f0 * (int64_t) g0;
+ int64_t f0g1 = f0 * (int64_t) g1;
+ int64_t f0g2 = f0 * (int64_t) g2;
+ int64_t f0g3 = f0 * (int64_t) g3;
+ int64_t f0g4 = f0 * (int64_t) g4;
+ int64_t f0g5 = f0 * (int64_t) g5;
+ int64_t f0g6 = f0 * (int64_t) g6;
+ int64_t f0g7 = f0 * (int64_t) g7;
+ int64_t f0g8 = f0 * (int64_t) g8;
+ int64_t f0g9 = f0 * (int64_t) g9;
+ int64_t f1g0 = f1 * (int64_t) g0;
+ int64_t f1g1_2 = f1_2 * (int64_t) g1;
+ int64_t f1g2 = f1 * (int64_t) g2;
+ int64_t f1g3_2 = f1_2 * (int64_t) g3;
+ int64_t f1g4 = f1 * (int64_t) g4;
+ int64_t f1g5_2 = f1_2 * (int64_t) g5;
+ int64_t f1g6 = f1 * (int64_t) g6;
+ int64_t f1g7_2 = f1_2 * (int64_t) g7;
+ int64_t f1g8 = f1 * (int64_t) g8;
+ int64_t f1g9_38 = f1_2 * (int64_t) g9_19;
+ int64_t f2g0 = f2 * (int64_t) g0;
+ int64_t f2g1 = f2 * (int64_t) g1;
+ int64_t f2g2 = f2 * (int64_t) g2;
+ int64_t f2g3 = f2 * (int64_t) g3;
+ int64_t f2g4 = f2 * (int64_t) g4;
+ int64_t f2g5 = f2 * (int64_t) g5;
+ int64_t f2g6 = f2 * (int64_t) g6;
+ int64_t f2g7 = f2 * (int64_t) g7;
+ int64_t f2g8_19 = f2 * (int64_t) g8_19;
+ int64_t f2g9_19 = f2 * (int64_t) g9_19;
+ int64_t f3g0 = f3 * (int64_t) g0;
+ int64_t f3g1_2 = f3_2 * (int64_t) g1;
+ int64_t f3g2 = f3 * (int64_t) g2;
+ int64_t f3g3_2 = f3_2 * (int64_t) g3;
+ int64_t f3g4 = f3 * (int64_t) g4;
+ int64_t f3g5_2 = f3_2 * (int64_t) g5;
+ int64_t f3g6 = f3 * (int64_t) g6;
+ int64_t f3g7_38 = f3_2 * (int64_t) g7_19;
+ int64_t f3g8_19 = f3 * (int64_t) g8_19;
+ int64_t f3g9_38 = f3_2 * (int64_t) g9_19;
+ int64_t f4g0 = f4 * (int64_t) g0;
+ int64_t f4g1 = f4 * (int64_t) g1;
+ int64_t f4g2 = f4 * (int64_t) g2;
+ int64_t f4g3 = f4 * (int64_t) g3;
+ int64_t f4g4 = f4 * (int64_t) g4;
+ int64_t f4g5 = f4 * (int64_t) g5;
+ int64_t f4g6_19 = f4 * (int64_t) g6_19;
+ int64_t f4g7_19 = f4 * (int64_t) g7_19;
+ int64_t f4g8_19 = f4 * (int64_t) g8_19;
+ int64_t f4g9_19 = f4 * (int64_t) g9_19;
+ int64_t f5g0 = f5 * (int64_t) g0;
+ int64_t f5g1_2 = f5_2 * (int64_t) g1;
+ int64_t f5g2 = f5 * (int64_t) g2;
+ int64_t f5g3_2 = f5_2 * (int64_t) g3;
+ int64_t f5g4 = f5 * (int64_t) g4;
+ int64_t f5g5_38 = f5_2 * (int64_t) g5_19;
+ int64_t f5g6_19 = f5 * (int64_t) g6_19;
+ int64_t f5g7_38 = f5_2 * (int64_t) g7_19;
+ int64_t f5g8_19 = f5 * (int64_t) g8_19;
+ int64_t f5g9_38 = f5_2 * (int64_t) g9_19;
+ int64_t f6g0 = f6 * (int64_t) g0;
+ int64_t f6g1 = f6 * (int64_t) g1;
+ int64_t f6g2 = f6 * (int64_t) g2;
+ int64_t f6g3 = f6 * (int64_t) g3;
+ int64_t f6g4_19 = f6 * (int64_t) g4_19;
+ int64_t f6g5_19 = f6 * (int64_t) g5_19;
+ int64_t f6g6_19 = f6 * (int64_t) g6_19;
+ int64_t f6g7_19 = f6 * (int64_t) g7_19;
+ int64_t f6g8_19 = f6 * (int64_t) g8_19;
+ int64_t f6g9_19 = f6 * (int64_t) g9_19;
+ int64_t f7g0 = f7 * (int64_t) g0;
+ int64_t f7g1_2 = f7_2 * (int64_t) g1;
+ int64_t f7g2 = f7 * (int64_t) g2;
+ int64_t f7g3_38 = f7_2 * (int64_t) g3_19;
+ int64_t f7g4_19 = f7 * (int64_t) g4_19;
+ int64_t f7g5_38 = f7_2 * (int64_t) g5_19;
+ int64_t f7g6_19 = f7 * (int64_t) g6_19;
+ int64_t f7g7_38 = f7_2 * (int64_t) g7_19;
+ int64_t f7g8_19 = f7 * (int64_t) g8_19;
+ int64_t f7g9_38 = f7_2 * (int64_t) g9_19;
+ int64_t f8g0 = f8 * (int64_t) g0;
+ int64_t f8g1 = f8 * (int64_t) g1;
+ int64_t f8g2_19 = f8 * (int64_t) g2_19;
+ int64_t f8g3_19 = f8 * (int64_t) g3_19;
+ int64_t f8g4_19 = f8 * (int64_t) g4_19;
+ int64_t f8g5_19 = f8 * (int64_t) g5_19;
+ int64_t f8g6_19 = f8 * (int64_t) g6_19;
+ int64_t f8g7_19 = f8 * (int64_t) g7_19;
+ int64_t f8g8_19 = f8 * (int64_t) g8_19;
+ int64_t f8g9_19 = f8 * (int64_t) g9_19;
+ int64_t f9g0 = f9 * (int64_t) g0;
+ int64_t f9g1_38 = f9_2 * (int64_t) g1_19;
+ int64_t f9g2_19 = f9 * (int64_t) g2_19;
+ int64_t f9g3_38 = f9_2 * (int64_t) g3_19;
+ int64_t f9g4_19 = f9 * (int64_t) g4_19;
+ int64_t f9g5_38 = f9_2 * (int64_t) g5_19;
+ int64_t f9g6_19 = f9 * (int64_t) g6_19;
+ int64_t f9g7_38 = f9_2 * (int64_t) g7_19;
+ int64_t f9g8_19 = f9 * (int64_t) g8_19;
+ int64_t f9g9_38 = f9_2 * (int64_t) g9_19;
+
+ int64_t h0 = f0g0 + f1g9_38 + f2g8_19 + f3g7_38 + f4g6_19 + f5g5_38 +
+ f6g4_19 + f7g3_38 + f8g2_19 + f9g1_38;
+ int64_t h1 = f0g1 + f1g0 + f2g9_19 + f3g8_19 + f4g7_19 + f5g6_19 + f6g5_19 +
+ f7g4_19 + f8g3_19 + f9g2_19;
+ int64_t h2 = f0g2 + f1g1_2 + f2g0 + f3g9_38 + f4g8_19 + f5g7_38 + f6g6_19 +
+ f7g5_38 + f8g4_19 + f9g3_38;
+ int64_t h3 = f0g3 + f1g2 + f2g1 + f3g0 + f4g9_19 + f5g8_19 + f6g7_19 +
+ f7g6_19 + f8g5_19 + f9g4_19;
+ int64_t h4 = f0g4 + f1g3_2 + f2g2 + f3g1_2 + f4g0 + f5g9_38 + f6g8_19 +
+ f7g7_38 + f8g6_19 + f9g5_38;
+ int64_t h5 = f0g5 + f1g4 + f2g3 + f3g2 + f4g1 + f5g0 + f6g9_19 + f7g8_19 +
+ f8g7_19 + f9g6_19;
+ int64_t h6 = f0g6 + f1g5_2 + f2g4 + f3g3_2 + f4g2 + f5g1_2 + f6g0 +
+ f7g9_38 + f8g8_19 + f9g7_38;
+ int64_t h7 = f0g7 + f1g6 + f2g5 + f3g4 + f4g3 + f5g2 + f6g1 + f7g0 +
+ f8g9_19 + f9g8_19;
+ int64_t h8 = f0g8 + f1g7_2 + f2g6 + f3g5_2 + f4g4 + f5g3_2 + f6g2 + f7g1_2 +
+ f8g0 + f9g9_38;
+ int64_t h9 =
+ f0g9 + f1g8 + f2g7 + f3g6 + f4g5 + f5g4 + f6g3 + f7g2 + f8g1 + f9g0;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+
+ /*
+ |h0| <= (1.65*1.65*2^52*(1+19+19+19+19)+1.65*1.65*2^50*(38+38+38+38+38))
+ i.e. |h0| <= 1.4*2^60; narrower ranges for h2, h4, h6, h8
+ |h1| <= (1.65*1.65*2^51*(1+1+19+19+19+19+19+19+19+19))
+ i.e. |h1| <= 1.7*2^59; narrower ranges for h3, h5, h7, h9
+ */
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+ /* |h0| <= 2^25 */
+ /* |h4| <= 2^25 */
+ /* |h1| <= 1.71*2^59 */
+ /* |h5| <= 1.71*2^59 */
+
+ carry1 = (h1 + (int64_t)(1L << 24)) >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((uint64_t) 1L << 25);
+ carry5 = (h5 + (int64_t)(1L << 24)) >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((uint64_t) 1L << 25);
+ /* |h1| <= 2^24; from now on fits into int32 */
+ /* |h5| <= 2^24; from now on fits into int32 */
+ /* |h2| <= 1.41*2^60 */
+ /* |h6| <= 1.41*2^60 */
+
+ carry2 = (h2 + (int64_t)(1L << 25)) >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((uint64_t) 1L << 26);
+ carry6 = (h6 + (int64_t)(1L << 25)) >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((uint64_t) 1L << 26);
+ /* |h2| <= 2^25; from now on fits into int32 unchanged */
+ /* |h6| <= 2^25; from now on fits into int32 unchanged */
+ /* |h3| <= 1.71*2^59 */
+ /* |h7| <= 1.71*2^59 */
+
+ carry3 = (h3 + (int64_t)(1L << 24)) >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((uint64_t) 1L << 25);
+ carry7 = (h7 + (int64_t)(1L << 24)) >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((uint64_t) 1L << 25);
+ /* |h3| <= 2^24; from now on fits into int32 unchanged */
+ /* |h7| <= 2^24; from now on fits into int32 unchanged */
+ /* |h4| <= 1.72*2^34 */
+ /* |h8| <= 1.41*2^60 */
+
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+ carry8 = (h8 + (int64_t)(1L << 25)) >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((uint64_t) 1L << 26);
+ /* |h4| <= 2^25; from now on fits into int32 unchanged */
+ /* |h8| <= 2^25; from now on fits into int32 unchanged */
+ /* |h5| <= 1.01*2^24 */
+ /* |h9| <= 1.71*2^59 */
+
+ carry9 = (h9 + (int64_t)(1L << 24)) >> 25;
+ h0 += carry9 * 19;
+ h9 -= carry9 * ((uint64_t) 1L << 25);
+ /* |h9| <= 2^24; from now on fits into int32 unchanged */
+ /* |h0| <= 1.1*2^39 */
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+ /* |h0| <= 2^25; from now on fits into int32 unchanged */
+ /* |h1| <= 1.01*2^24 */
+
+ h[0] = (int32_t) h0;
+ h[1] = (int32_t) h1;
+ h[2] = (int32_t) h2;
+ h[3] = (int32_t) h3;
+ h[4] = (int32_t) h4;
+ h[5] = (int32_t) h5;
+ h[6] = (int32_t) h6;
+ h[7] = (int32_t) h7;
+ h[8] = (int32_t) h8;
+ h[9] = (int32_t) h9;
+}
+
+/*
+ h = f * f
+ Can overlap h with f.
+ *
+ Preconditions:
+ |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+ */
+
+static void
+fe25519_sq(fe25519 h, const fe25519 f)
+{
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+
+ int32_t f0_2 = 2 * f0;
+ int32_t f1_2 = 2 * f1;
+ int32_t f2_2 = 2 * f2;
+ int32_t f3_2 = 2 * f3;
+ int32_t f4_2 = 2 * f4;
+ int32_t f5_2 = 2 * f5;
+ int32_t f6_2 = 2 * f6;
+ int32_t f7_2 = 2 * f7;
+ int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */
+ int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */
+ int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */
+ int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */
+ int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */
+
+ int64_t f0f0 = f0 * (int64_t) f0;
+ int64_t f0f1_2 = f0_2 * (int64_t) f1;
+ int64_t f0f2_2 = f0_2 * (int64_t) f2;
+ int64_t f0f3_2 = f0_2 * (int64_t) f3;
+ int64_t f0f4_2 = f0_2 * (int64_t) f4;
+ int64_t f0f5_2 = f0_2 * (int64_t) f5;
+ int64_t f0f6_2 = f0_2 * (int64_t) f6;
+ int64_t f0f7_2 = f0_2 * (int64_t) f7;
+ int64_t f0f8_2 = f0_2 * (int64_t) f8;
+ int64_t f0f9_2 = f0_2 * (int64_t) f9;
+ int64_t f1f1_2 = f1_2 * (int64_t) f1;
+ int64_t f1f2_2 = f1_2 * (int64_t) f2;
+ int64_t f1f3_4 = f1_2 * (int64_t) f3_2;
+ int64_t f1f4_2 = f1_2 * (int64_t) f4;
+ int64_t f1f5_4 = f1_2 * (int64_t) f5_2;
+ int64_t f1f6_2 = f1_2 * (int64_t) f6;
+ int64_t f1f7_4 = f1_2 * (int64_t) f7_2;
+ int64_t f1f8_2 = f1_2 * (int64_t) f8;
+ int64_t f1f9_76 = f1_2 * (int64_t) f9_38;
+ int64_t f2f2 = f2 * (int64_t) f2;
+ int64_t f2f3_2 = f2_2 * (int64_t) f3;
+ int64_t f2f4_2 = f2_2 * (int64_t) f4;
+ int64_t f2f5_2 = f2_2 * (int64_t) f5;
+ int64_t f2f6_2 = f2_2 * (int64_t) f6;
+ int64_t f2f7_2 = f2_2 * (int64_t) f7;
+ int64_t f2f8_38 = f2_2 * (int64_t) f8_19;
+ int64_t f2f9_38 = f2 * (int64_t) f9_38;
+ int64_t f3f3_2 = f3_2 * (int64_t) f3;
+ int64_t f3f4_2 = f3_2 * (int64_t) f4;
+ int64_t f3f5_4 = f3_2 * (int64_t) f5_2;
+ int64_t f3f6_2 = f3_2 * (int64_t) f6;
+ int64_t f3f7_76 = f3_2 * (int64_t) f7_38;
+ int64_t f3f8_38 = f3_2 * (int64_t) f8_19;
+ int64_t f3f9_76 = f3_2 * (int64_t) f9_38;
+ int64_t f4f4 = f4 * (int64_t) f4;
+ int64_t f4f5_2 = f4_2 * (int64_t) f5;
+ int64_t f4f6_38 = f4_2 * (int64_t) f6_19;
+ int64_t f4f7_38 = f4 * (int64_t) f7_38;
+ int64_t f4f8_38 = f4_2 * (int64_t) f8_19;
+ int64_t f4f9_38 = f4 * (int64_t) f9_38;
+ int64_t f5f5_38 = f5 * (int64_t) f5_38;
+ int64_t f5f6_38 = f5_2 * (int64_t) f6_19;
+ int64_t f5f7_76 = f5_2 * (int64_t) f7_38;
+ int64_t f5f8_38 = f5_2 * (int64_t) f8_19;
+ int64_t f5f9_76 = f5_2 * (int64_t) f9_38;
+ int64_t f6f6_19 = f6 * (int64_t) f6_19;
+ int64_t f6f7_38 = f6 * (int64_t) f7_38;
+ int64_t f6f8_38 = f6_2 * (int64_t) f8_19;
+ int64_t f6f9_38 = f6 * (int64_t) f9_38;
+ int64_t f7f7_38 = f7 * (int64_t) f7_38;
+ int64_t f7f8_38 = f7_2 * (int64_t) f8_19;
+ int64_t f7f9_76 = f7_2 * (int64_t) f9_38;
+ int64_t f8f8_19 = f8 * (int64_t) f8_19;
+ int64_t f8f9_38 = f8 * (int64_t) f9_38;
+ int64_t f9f9_38 = f9 * (int64_t) f9_38;
+
+ int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
+ int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
+ int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
+ int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
+ int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38;
+ int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
+ int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
+ int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
+ int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38;
+ int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+
+ carry1 = (h1 + (int64_t)(1L << 24)) >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((uint64_t) 1L << 25);
+ carry5 = (h5 + (int64_t)(1L << 24)) >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((uint64_t) 1L << 25);
+
+ carry2 = (h2 + (int64_t)(1L << 25)) >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((uint64_t) 1L << 26);
+ carry6 = (h6 + (int64_t)(1L << 25)) >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((uint64_t) 1L << 26);
+
+ carry3 = (h3 + (int64_t)(1L << 24)) >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((uint64_t) 1L << 25);
+ carry7 = (h7 + (int64_t)(1L << 24)) >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((uint64_t) 1L << 25);
+
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+ carry8 = (h8 + (int64_t)(1L << 25)) >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((uint64_t) 1L << 26);
+
+ carry9 = (h9 + (int64_t)(1L << 24)) >> 25;
+ h0 += carry9 * 19;
+ h9 -= carry9 * ((uint64_t) 1L << 25);
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+
+ h[0] = (int32_t) h0;
+ h[1] = (int32_t) h1;
+ h[2] = (int32_t) h2;
+ h[3] = (int32_t) h3;
+ h[4] = (int32_t) h4;
+ h[5] = (int32_t) h5;
+ h[6] = (int32_t) h6;
+ h[7] = (int32_t) h7;
+ h[8] = (int32_t) h8;
+ h[9] = (int32_t) h9;
+}
+
+/*
+ h = 2 * f * f
+ Can overlap h with f.
+ *
+ Preconditions:
+ |f| bounded by 1.65*2^26,1.65*2^25,1.65*2^26,1.65*2^25,etc.
+ *
+ Postconditions:
+ |h| bounded by 1.01*2^25,1.01*2^24,1.01*2^25,1.01*2^24,etc.
+ */
+
+static void
+fe25519_sq2(fe25519 h, const fe25519 f)
+{
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+
+ int32_t f0_2 = 2 * f0;
+ int32_t f1_2 = 2 * f1;
+ int32_t f2_2 = 2 * f2;
+ int32_t f3_2 = 2 * f3;
+ int32_t f4_2 = 2 * f4;
+ int32_t f5_2 = 2 * f5;
+ int32_t f6_2 = 2 * f6;
+ int32_t f7_2 = 2 * f7;
+ int32_t f5_38 = 38 * f5; /* 1.959375*2^30 */
+ int32_t f6_19 = 19 * f6; /* 1.959375*2^30 */
+ int32_t f7_38 = 38 * f7; /* 1.959375*2^30 */
+ int32_t f8_19 = 19 * f8; /* 1.959375*2^30 */
+ int32_t f9_38 = 38 * f9; /* 1.959375*2^30 */
+
+ int64_t f0f0 = f0 * (int64_t) f0;
+ int64_t f0f1_2 = f0_2 * (int64_t) f1;
+ int64_t f0f2_2 = f0_2 * (int64_t) f2;
+ int64_t f0f3_2 = f0_2 * (int64_t) f3;
+ int64_t f0f4_2 = f0_2 * (int64_t) f4;
+ int64_t f0f5_2 = f0_2 * (int64_t) f5;
+ int64_t f0f6_2 = f0_2 * (int64_t) f6;
+ int64_t f0f7_2 = f0_2 * (int64_t) f7;
+ int64_t f0f8_2 = f0_2 * (int64_t) f8;
+ int64_t f0f9_2 = f0_2 * (int64_t) f9;
+ int64_t f1f1_2 = f1_2 * (int64_t) f1;
+ int64_t f1f2_2 = f1_2 * (int64_t) f2;
+ int64_t f1f3_4 = f1_2 * (int64_t) f3_2;
+ int64_t f1f4_2 = f1_2 * (int64_t) f4;
+ int64_t f1f5_4 = f1_2 * (int64_t) f5_2;
+ int64_t f1f6_2 = f1_2 * (int64_t) f6;
+ int64_t f1f7_4 = f1_2 * (int64_t) f7_2;
+ int64_t f1f8_2 = f1_2 * (int64_t) f8;
+ int64_t f1f9_76 = f1_2 * (int64_t) f9_38;
+ int64_t f2f2 = f2 * (int64_t) f2;
+ int64_t f2f3_2 = f2_2 * (int64_t) f3;
+ int64_t f2f4_2 = f2_2 * (int64_t) f4;
+ int64_t f2f5_2 = f2_2 * (int64_t) f5;
+ int64_t f2f6_2 = f2_2 * (int64_t) f6;
+ int64_t f2f7_2 = f2_2 * (int64_t) f7;
+ int64_t f2f8_38 = f2_2 * (int64_t) f8_19;
+ int64_t f2f9_38 = f2 * (int64_t) f9_38;
+ int64_t f3f3_2 = f3_2 * (int64_t) f3;
+ int64_t f3f4_2 = f3_2 * (int64_t) f4;
+ int64_t f3f5_4 = f3_2 * (int64_t) f5_2;
+ int64_t f3f6_2 = f3_2 * (int64_t) f6;
+ int64_t f3f7_76 = f3_2 * (int64_t) f7_38;
+ int64_t f3f8_38 = f3_2 * (int64_t) f8_19;
+ int64_t f3f9_76 = f3_2 * (int64_t) f9_38;
+ int64_t f4f4 = f4 * (int64_t) f4;
+ int64_t f4f5_2 = f4_2 * (int64_t) f5;
+ int64_t f4f6_38 = f4_2 * (int64_t) f6_19;
+ int64_t f4f7_38 = f4 * (int64_t) f7_38;
+ int64_t f4f8_38 = f4_2 * (int64_t) f8_19;
+ int64_t f4f9_38 = f4 * (int64_t) f9_38;
+ int64_t f5f5_38 = f5 * (int64_t) f5_38;
+ int64_t f5f6_38 = f5_2 * (int64_t) f6_19;
+ int64_t f5f7_76 = f5_2 * (int64_t) f7_38;
+ int64_t f5f8_38 = f5_2 * (int64_t) f8_19;
+ int64_t f5f9_76 = f5_2 * (int64_t) f9_38;
+ int64_t f6f6_19 = f6 * (int64_t) f6_19;
+ int64_t f6f7_38 = f6 * (int64_t) f7_38;
+ int64_t f6f8_38 = f6_2 * (int64_t) f8_19;
+ int64_t f6f9_38 = f6 * (int64_t) f9_38;
+ int64_t f7f7_38 = f7 * (int64_t) f7_38;
+ int64_t f7f8_38 = f7_2 * (int64_t) f8_19;
+ int64_t f7f9_76 = f7_2 * (int64_t) f9_38;
+ int64_t f8f8_19 = f8 * (int64_t) f8_19;
+ int64_t f8f9_38 = f8 * (int64_t) f9_38;
+ int64_t f9f9_38 = f9 * (int64_t) f9_38;
+
+ int64_t h0 = f0f0 + f1f9_76 + f2f8_38 + f3f7_76 + f4f6_38 + f5f5_38;
+ int64_t h1 = f0f1_2 + f2f9_38 + f3f8_38 + f4f7_38 + f5f6_38;
+ int64_t h2 = f0f2_2 + f1f1_2 + f3f9_76 + f4f8_38 + f5f7_76 + f6f6_19;
+ int64_t h3 = f0f3_2 + f1f2_2 + f4f9_38 + f5f8_38 + f6f7_38;
+ int64_t h4 = f0f4_2 + f1f3_4 + f2f2 + f5f9_76 + f6f8_38 + f7f7_38;
+ int64_t h5 = f0f5_2 + f1f4_2 + f2f3_2 + f6f9_38 + f7f8_38;
+ int64_t h6 = f0f6_2 + f1f5_4 + f2f4_2 + f3f3_2 + f7f9_76 + f8f8_19;
+ int64_t h7 = f0f7_2 + f1f6_2 + f2f5_2 + f3f4_2 + f8f9_38;
+ int64_t h8 = f0f8_2 + f1f7_4 + f2f6_2 + f3f5_4 + f4f4 + f9f9_38;
+ int64_t h9 = f0f9_2 + f1f8_2 + f2f7_2 + f3f6_2 + f4f5_2;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+
+ h0 += h0;
+ h1 += h1;
+ h2 += h2;
+ h3 += h3;
+ h4 += h4;
+ h5 += h5;
+ h6 += h6;
+ h7 += h7;
+ h8 += h8;
+ h9 += h9;
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+
+ carry1 = (h1 + (int64_t)(1L << 24)) >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((uint64_t) 1L << 25);
+ carry5 = (h5 + (int64_t)(1L << 24)) >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((uint64_t) 1L << 25);
+
+ carry2 = (h2 + (int64_t)(1L << 25)) >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((uint64_t) 1L << 26);
+ carry6 = (h6 + (int64_t)(1L << 25)) >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((uint64_t) 1L << 26);
+
+ carry3 = (h3 + (int64_t)(1L << 24)) >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((uint64_t) 1L << 25);
+ carry7 = (h7 + (int64_t)(1L << 24)) >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((uint64_t) 1L << 25);
+
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+ carry8 = (h8 + (int64_t)(1L << 25)) >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((uint64_t) 1L << 26);
+
+ carry9 = (h9 + (int64_t)(1L << 24)) >> 25;
+ h0 += carry9 * 19;
+ h9 -= carry9 * ((uint64_t) 1L << 25);
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+
+ h[0] = (int32_t) h0;
+ h[1] = (int32_t) h1;
+ h[2] = (int32_t) h2;
+ h[3] = (int32_t) h3;
+ h[4] = (int32_t) h4;
+ h[5] = (int32_t) h5;
+ h[6] = (int32_t) h6;
+ h[7] = (int32_t) h7;
+ h[8] = (int32_t) h8;
+ h[9] = (int32_t) h9;
+}
+
+static inline void
+fe25519_mul32(fe25519 h, const fe25519 f, uint32_t n)
+{
+ int64_t sn = (int64_t) n;
+ int32_t f0 = f[0];
+ int32_t f1 = f[1];
+ int32_t f2 = f[2];
+ int32_t f3 = f[3];
+ int32_t f4 = f[4];
+ int32_t f5 = f[5];
+ int32_t f6 = f[6];
+ int32_t f7 = f[7];
+ int32_t f8 = f[8];
+ int32_t f9 = f[9];
+ int64_t h0 = f0 * sn;
+ int64_t h1 = f1 * sn;
+ int64_t h2 = f2 * sn;
+ int64_t h3 = f3 * sn;
+ int64_t h4 = f4 * sn;
+ int64_t h5 = f5 * sn;
+ int64_t h6 = f6 * sn;
+ int64_t h7 = f7 * sn;
+ int64_t h8 = f8 * sn;
+ int64_t h9 = f9 * sn;
+ int64_t carry0, carry1, carry2, carry3, carry4, carry5, carry6, carry7,
+ carry8, carry9;
+
+ carry9 = (h9 + ((int64_t) 1 << 24)) >> 25;
+ h0 += carry9 * 19;
+ h9 -= carry9 * ((int64_t) 1 << 25);
+ carry1 = (h1 + ((int64_t) 1 << 24)) >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((int64_t) 1 << 25);
+ carry3 = (h3 + ((int64_t) 1 << 24)) >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((int64_t) 1 << 25);
+ carry5 = (h5 + ((int64_t) 1 << 24)) >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((int64_t) 1 << 25);
+ carry7 = (h7 + ((int64_t) 1 << 24)) >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((int64_t) 1 << 25);
+
+ carry0 = (h0 + ((int64_t) 1 << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((int64_t) 1 << 26);
+ carry2 = (h2 + ((int64_t) 1 << 25)) >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((int64_t) 1 << 26);
+ carry4 = (h4 + ((int64_t) 1 << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((int64_t) 1 << 26);
+ carry6 = (h6 + ((int64_t) 1 << 25)) >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((int64_t) 1 << 26);
+ carry8 = (h8 + ((int64_t) 1 << 25)) >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((int64_t) 1 << 26);
+
+ h[0] = (int32_t) h0;
+ h[1] = (int32_t) h1;
+ h[2] = (int32_t) h2;
+ h[3] = (int32_t) h3;
+ h[4] = (int32_t) h4;
+ h[5] = (int32_t) h5;
+ h[6] = (int32_t) h6;
+ h[7] = (int32_t) h7;
+ h[8] = (int32_t) h8;
+ h[9] = (int32_t) h9;
+}
diff --git a/packages/runtime/vendor/libsodium/export.h b/packages/runtime/vendor/libsodium/export.h
new file mode 100644
index 000000000..a0074fc9c
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/export.h
@@ -0,0 +1,57 @@
+
+#ifndef sodium_export_H
+#define sodium_export_H
+
+#include
+#include
+#include
+
+#if !defined(__clang__) && !defined(__GNUC__)
+# ifdef __attribute__
+# undef __attribute__
+# endif
+# define __attribute__(a)
+#endif
+
+#ifdef SODIUM_STATIC
+# define SODIUM_EXPORT
+# define SODIUM_EXPORT_WEAK
+#else
+# if defined(_MSC_VER)
+# ifdef SODIUM_DLL_EXPORT
+# define SODIUM_EXPORT __declspec(dllexport)
+# else
+# define SODIUM_EXPORT __declspec(dllimport)
+# endif
+# else
+# if defined(__SUNPRO_C)
+# ifndef __GNU_C__
+# define SODIUM_EXPORT __attribute__ (visibility(__global))
+# else
+# define SODIUM_EXPORT __attribute__ __global
+# endif
+# elif defined(_MSG_VER)
+# define SODIUM_EXPORT extern __declspec(dllexport)
+# else
+# define SODIUM_EXPORT __attribute__ ((visibility ("default")))
+# endif
+# endif
+# if defined(__ELF__) && !defined(SODIUM_DISABLE_WEAK_FUNCTIONS)
+# define SODIUM_EXPORT_WEAK SODIUM_EXPORT __attribute__((weak))
+# else
+# define SODIUM_EXPORT_WEAK SODIUM_EXPORT
+# endif
+#endif
+
+#ifndef CRYPTO_ALIGN
+# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
+# define CRYPTO_ALIGN(x) __declspec(align(x))
+# else
+# define CRYPTO_ALIGN(x) __attribute__ ((aligned(x)))
+# endif
+#endif
+
+#define SODIUM_MIN(A, B) ((A) < (B) ? (A) : (B))
+#define SODIUM_SIZE_MAX SODIUM_MIN(UINT64_MAX, SIZE_MAX)
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/fe_25_5/base.h b/packages/runtime/vendor/libsodium/fe_25_5/base.h
new file mode 100644
index 000000000..e18530bbb
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/fe_25_5/base.h
@@ -0,0 +1,1344 @@
+{ /* 0/31 */
+ {
+ { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 },
+ { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 },
+ { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }
+ },
+ {
+ { -12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303 },
+ { -21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081 },
+ { 26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697 }
+ },
+ {
+ { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 },
+ { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 },
+ { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }
+ },
+ {
+ { -17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540 },
+ { 23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397 },
+ { 7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325 }
+ },
+ {
+ { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 },
+ { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 },
+ { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }
+ },
+ {
+ { -15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777 },
+ { -8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737 },
+ { -18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652 }
+ },
+ {
+ { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 },
+ { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 },
+ { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }
+ },
+ {
+ { 14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726 },
+ { -7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955 },
+ { 27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425 }
+ }
+},
+{ /* 1/31 */
+ {
+ { -13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171 },
+ { 27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510 },
+ { 17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660 }
+ },
+ {
+ { -10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639 },
+ { 29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963 },
+ { 5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950 }
+ },
+ {
+ { -27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568 },
+ { 12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335 },
+ { 25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628 }
+ },
+ {
+ { -26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007 },
+ { -2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772 },
+ { -22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653 }
+ },
+ {
+ { 2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567 },
+ { 13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686 },
+ { 21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372 }
+ },
+ {
+ { -13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887 },
+ { -23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954 },
+ { -29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953 }
+ },
+ {
+ { 24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833 },
+ { -16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532 },
+ { -22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876 }
+ },
+ {
+ { 2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268 },
+ { 33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214 },
+ { 1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038 }
+ }
+},
+{ /* 2/31 */
+ {
+ { 6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800 },
+ { 4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645 },
+ { -4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664 }
+ },
+ {
+ { 1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933 },
+ { -25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182 },
+ { -17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222 }
+ },
+ {
+ { -18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991 },
+ { 20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880 },
+ { 9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092 }
+ },
+ {
+ { -16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295 },
+ { 19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788 },
+ { 8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553 }
+ },
+ {
+ { -15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026 },
+ { 11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347 },
+ { -18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033 }
+ },
+ {
+ { -23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395 },
+ { -27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278 },
+ { 1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890 }
+ },
+ {
+ { 32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995 },
+ { -30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596 },
+ { -11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891 }
+ },
+ {
+ { 31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060 },
+ { 11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608 },
+ { -20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606 }
+ }
+},
+{ /* 3/31 */
+ {
+ { 7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389 },
+ { -19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016 },
+ { -11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341 }
+ },
+ {
+ { -22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505 },
+ { 14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553 },
+ { -28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655 }
+ },
+ {
+ { 15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220 },
+ { 12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631 },
+ { -4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099 }
+ },
+ {
+ { 26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556 },
+ { 14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749 },
+ { 236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930 }
+ },
+ {
+ { 1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391 },
+ { 5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253 },
+ { 20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066 }
+ },
+ {
+ { 24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958 },
+ { -11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082 },
+ { -28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383 }
+ },
+ {
+ { -30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521 },
+ { -11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807 },
+ { 23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948 }
+ },
+ {
+ { 9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134 },
+ { -32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455 },
+ { 27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629 }
+ }
+},
+{ /* 4/31 */
+ {
+ { -8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069 },
+ { -32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746 },
+ { 24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919 }
+ },
+ {
+ { 11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837 },
+ { 8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906 },
+ { -28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771 }
+ },
+ {
+ { -25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817 },
+ { 10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098 },
+ { 10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409 }
+ },
+ {
+ { -12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504 },
+ { -26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727 },
+ { 28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420 }
+ },
+ {
+ { -32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003 },
+ { -1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605 },
+ { -30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384 }
+ },
+ {
+ { -26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701 },
+ { -23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683 },
+ { 29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708 }
+ },
+ {
+ { -3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563 },
+ { -19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260 },
+ { -5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387 }
+ },
+ {
+ { -19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672 },
+ { 23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686 },
+ { -24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665 }
+ }
+},
+{ /* 5/31 */
+ {
+ { 11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182 },
+ { -31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277 },
+ { 14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628 }
+ },
+ {
+ { -4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474 },
+ { -26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539 },
+ { -25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822 }
+ },
+ {
+ { -10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970 },
+ { 19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756 },
+ { -24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508 }
+ },
+ {
+ { -26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683 },
+ { -10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655 },
+ { -20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158 }
+ },
+ {
+ { -4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125 },
+ { -15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839 },
+ { -20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664 }
+ },
+ {
+ { 27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294 },
+ { -18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899 },
+ { -11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070 }
+ },
+ {
+ { 3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294 },
+ { -15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949 },
+ { -21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083 }
+ },
+ {
+ { 31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420 },
+ { -5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940 },
+ { 29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396 }
+ }
+},
+{ /* 6/31 */
+ {
+ { -12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567 },
+ { 20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127 },
+ { -16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294 }
+ },
+ {
+ { -12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887 },
+ { 22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964 },
+ { 16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195 }
+ },
+ {
+ { 9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244 },
+ { 24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999 },
+ { -1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762 }
+ },
+ {
+ { -18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274 },
+ { -33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236 },
+ { -16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605 }
+ },
+ {
+ { -13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761 },
+ { -22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884 },
+ { -6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482 }
+ },
+ {
+ { -24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638 },
+ { -11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490 },
+ { -32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170 }
+ },
+ {
+ { 5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736 },
+ { 10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124 },
+ { -17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392 }
+ },
+ {
+ { 8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029 },
+ { 6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048 },
+ { 28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958 }
+ }
+},
+{ /* 7/31 */
+ {
+ { 24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593 },
+ { 26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071 },
+ { -11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692 }
+ },
+ {
+ { 11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687 },
+ { -160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441 },
+ { -20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001 }
+ },
+ {
+ { -938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460 },
+ { -19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007 },
+ { -21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762 }
+ },
+ {
+ { 15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005 },
+ { -9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674 },
+ { 4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035 }
+ },
+ {
+ { 7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590 },
+ { -2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957 },
+ { -30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812 }
+ },
+ {
+ { 33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740 },
+ { -18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122 },
+ { -27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158 }
+ },
+ {
+ { 8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885 },
+ { 26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140 },
+ { 19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857 }
+ },
+ {
+ { 801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155 },
+ { 19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260 },
+ { 19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483 }
+ }
+},
+{ /* 8/31 */
+ {
+ { -3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677 },
+ { 32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815 },
+ { 22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751 }
+ },
+ {
+ { -16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203 },
+ { -11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208 },
+ { 1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230 }
+ },
+ {
+ { 16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850 },
+ { -21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389 },
+ { -9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968 }
+ },
+ {
+ { -11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689 },
+ { 14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880 },
+ { 5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304 }
+ },
+ {
+ { 30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632 },
+ { -3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412 },
+ { 20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566 }
+ },
+ {
+ { -20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038 },
+ { -26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232 },
+ { -1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943 }
+ },
+ {
+ { 17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856 },
+ { 23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738 },
+ { 15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971 }
+ },
+ {
+ { -27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718 },
+ { -13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697 },
+ { -11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883 }
+ }
+},
+{ /* 9/31 */
+ {
+ { 5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912 },
+ { -26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358 },
+ { 3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849 }
+ },
+ {
+ { 29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307 },
+ { -14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977 },
+ { -6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335 }
+ },
+ {
+ { -29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644 },
+ { -22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616 },
+ { -27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735 }
+ },
+ {
+ { -21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099 },
+ { 29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341 },
+ { -936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336 }
+ },
+ {
+ { -23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646 },
+ { 31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425 },
+ { -17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388 }
+ },
+ {
+ { -31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743 },
+ { -16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822 },
+ { -8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462 }
+ },
+ {
+ { 18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985 },
+ { 9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702 },
+ { -22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797 }
+ },
+ {
+ { 21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293 },
+ { 27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100 },
+ { 19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688 }
+ }
+},
+{ /* 10/31 */
+ {
+ { 12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186 },
+ { 2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610 },
+ { -2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707 }
+ },
+ {
+ { 7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220 },
+ { 915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025 },
+ { 32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044 }
+ },
+ {
+ { 32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992 },
+ { -4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027 },
+ { 21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197 }
+ },
+ {
+ { 8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901 },
+ { 31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952 },
+ { 19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878 }
+ },
+ {
+ { -28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390 },
+ { 32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730 },
+ { 2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730 }
+ },
+ {
+ { -19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180 },
+ { -30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272 },
+ { -15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715 }
+ },
+ {
+ { -22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970 },
+ { -31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772 },
+ { -17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865 }
+ },
+ {
+ { 15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750 },
+ { 20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373 },
+ { 32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348 }
+ }
+},
+{ /* 11/31 */
+ {
+ { 9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144 },
+ { -22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195 },
+ { 5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086 }
+ },
+ {
+ { -13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684 },
+ { -8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518 },
+ { -2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233 }
+ },
+ {
+ { -5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793 },
+ { -2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794 },
+ { 580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435 }
+ },
+ {
+ { 23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921 },
+ { 13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518 },
+ { 2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563 }
+ },
+ {
+ { 14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278 },
+ { -27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024 },
+ { 4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030 }
+ },
+ {
+ { 10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783 },
+ { 27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717 },
+ { 6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844 }
+ },
+ {
+ { 14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333 },
+ { 16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048 },
+ { 22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760 }
+ },
+ {
+ { -4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760 },
+ { -15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757 },
+ { -2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112 }
+ }
+},
+{ /* 12/31 */
+ {
+ { -19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468 },
+ { 3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184 },
+ { 10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289 }
+ },
+ {
+ { 15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066 },
+ { 24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882 },
+ { 13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226 }
+ },
+ {
+ { 16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101 },
+ { 29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279 },
+ { -6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811 }
+ },
+ {
+ { 27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709 },
+ { 20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714 },
+ { -2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121 }
+ },
+ {
+ { 9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464 },
+ { 12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847 },
+ { 13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400 }
+ },
+ {
+ { 4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414 },
+ { -15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158 },
+ { 17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045 }
+ },
+ {
+ { -461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415 },
+ { -5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459 },
+ { -31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079 }
+ },
+ {
+ { 21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412 },
+ { -20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743 },
+ { -14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836 }
+ }
+},
+{ /* 13/31 */
+ {
+ { 12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022 },
+ { 18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429 },
+ { -6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065 }
+ },
+ {
+ { 30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861 },
+ { 10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000 },
+ { -33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101 }
+ },
+ {
+ { 32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815 },
+ { 29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642 },
+ { 10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966 }
+ },
+ {
+ { 25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574 },
+ { -21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742 },
+ { -18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689 }
+ },
+ {
+ { 12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020 },
+ { -10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772 },
+ { 3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982 }
+ },
+ {
+ { -14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953 },
+ { -16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218 },
+ { -17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265 }
+ },
+ {
+ { 29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073 },
+ { -3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325 },
+ { -11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798 }
+ },
+ {
+ { -4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870 },
+ { -7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863 },
+ { -13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927 }
+ }
+},
+{ /* 14/31 */
+ {
+ { -2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267 },
+ { -9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663 },
+ { 22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862 }
+ },
+ {
+ { -25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673 },
+ { 15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943 },
+ { 15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020 }
+ },
+ {
+ { -4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238 },
+ { 11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064 },
+ { 14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795 }
+ },
+ {
+ { 15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052 },
+ { -10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904 },
+ { 29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531 }
+ },
+ {
+ { -13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979 },
+ { -5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841 },
+ { 10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431 }
+ },
+ {
+ { 10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324 },
+ { -31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940 },
+ { 10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320 }
+ },
+ {
+ { -15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184 },
+ { 14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114 },
+ { 30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878 }
+ },
+ {
+ { 12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784 },
+ { -2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091 },
+ { -16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585 }
+ }
+},
+{ /* 15/31 */
+ {
+ { -8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208 },
+ { 10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864 },
+ { 17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661 }
+ },
+ {
+ { 7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233 },
+ { 26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212 },
+ { -12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525 }
+ },
+ {
+ { -24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068 },
+ { 9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397 },
+ { -8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988 }
+ },
+ {
+ { 5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889 },
+ { 32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038 },
+ { 14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697 }
+ },
+ {
+ { 20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875 },
+ { -25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905 },
+ { -25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656 }
+ },
+ {
+ { 11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818 },
+ { 27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714 },
+ { 10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203 }
+ },
+ {
+ { 20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931 },
+ { -30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024 },
+ { -23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084 }
+ },
+ {
+ { -1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204 },
+ { 20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817 },
+ { 27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667 }
+ }
+},
+{ /* 16/31 */
+ {
+ { 11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504 },
+ { -12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768 },
+ { -19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255 }
+ },
+ {
+ { 6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790 },
+ { 1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438 },
+ { -22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333 }
+ },
+ {
+ { 17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971 },
+ { 31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905 },
+ { 29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409 }
+ },
+ {
+ { 12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409 },
+ { 6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499 },
+ { -8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363 }
+ },
+ {
+ { 28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664 },
+ { -11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324 },
+ { -21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940 }
+ },
+ {
+ { 13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990 },
+ { -17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914 },
+ { -25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290 }
+ },
+ {
+ { 24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257 },
+ { -6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433 },
+ { -16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236 }
+ },
+ {
+ { -12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045 },
+ { 11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093 },
+ { -1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347 }
+ }
+},
+{ /* 17/31 */
+ {
+ { -28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191 },
+ { -15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507 },
+ { -12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906 }
+ },
+ {
+ { 3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018 },
+ { -16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109 },
+ { -23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926 }
+ },
+ {
+ { -24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528 },
+ { 8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625 },
+ { -32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286 }
+ },
+ {
+ { 2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033 },
+ { 27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866 },
+ { 21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896 }
+ },
+ {
+ { 30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075 },
+ { 26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347 },
+ { -22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437 }
+ },
+ {
+ { -5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165 },
+ { -18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588 },
+ { -32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193 }
+ },
+ {
+ { -19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017 },
+ { -28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883 },
+ { 21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961 }
+ },
+ {
+ { 8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043 },
+ { 29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663 },
+ { -20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362 }
+ }
+},
+{ /* 18/31 */
+ {
+ { -33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860 },
+ { 2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466 },
+ { -24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063 }
+ },
+ {
+ { -26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997 },
+ { -1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295 },
+ { -13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369 }
+ },
+ {
+ { 9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385 },
+ { 18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109 },
+ { 2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906 }
+ },
+ {
+ { 4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424 },
+ { -19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185 },
+ { 7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962 }
+ },
+ {
+ { -7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325 },
+ { 10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593 },
+ { 696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404 }
+ },
+ {
+ { -11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644 },
+ { 17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801 },
+ { 26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804 }
+ },
+ {
+ { -31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884 },
+ { -586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577 },
+ { -9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849 }
+ },
+ {
+ { 32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473 },
+ { -8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644 },
+ { -2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319 }
+ }
+},
+{ /* 19/31 */
+ {
+ { -11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599 },
+ { -9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768 },
+ { -27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084 }
+ },
+ {
+ { -27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328 },
+ { -15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369 },
+ { 20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920 }
+ },
+ {
+ { 12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815 },
+ { -32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025 },
+ { -21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397 }
+ },
+ {
+ { -20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448 },
+ { 6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981 },
+ { 30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165 }
+ },
+ {
+ { 32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501 },
+ { 17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073 },
+ { -1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861 }
+ },
+ {
+ { 14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845 },
+ { -1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211 },
+ { 18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870 }
+ },
+ {
+ { 10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096 },
+ { 33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803 },
+ { -32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168 }
+ },
+ {
+ { 30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965 },
+ { -14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505 },
+ { 18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598 }
+ }
+},
+{ /* 20/31 */
+ {
+ { 5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782 },
+ { 5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900 },
+ { -31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479 }
+ },
+ {
+ { -12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208 },
+ { 8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232 },
+ { 17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719 }
+ },
+ {
+ { 16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271 },
+ { -4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326 },
+ { -8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132 }
+ },
+ {
+ { 14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300 },
+ { 8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570 },
+ { 15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670 }
+ },
+ {
+ { -2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994 },
+ { -12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913 },
+ { 31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317 }
+ },
+ {
+ { -25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730 },
+ { 842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096 },
+ { -4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078 }
+ },
+ {
+ { -15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411 },
+ { -19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905 },
+ { -9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654 }
+ },
+ {
+ { -28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870 },
+ { -23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498 },
+ { 12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579 }
+ }
+},
+{ /* 21/31 */
+ {
+ { 14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677 },
+ { 10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647 },
+ { -2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743 }
+ },
+ {
+ { -25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468 },
+ { 21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375 },
+ { -25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155 }
+ },
+ {
+ { 6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725 },
+ { -12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612 },
+ { -10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943 }
+ },
+ {
+ { -30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944 },
+ { 30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928 },
+ { 9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406 }
+ },
+ {
+ { 22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139 },
+ { -8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963 },
+ { -31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693 }
+ },
+ {
+ { 1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734 },
+ { -448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680 },
+ { -24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410 }
+ },
+ {
+ { -9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931 },
+ { -16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654 },
+ { 22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710 }
+ },
+ {
+ { 29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180 },
+ { -26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684 },
+ { -10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895 }
+ }
+},
+{ /* 22/31 */
+ {
+ { 22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501 },
+ { -11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413 },
+ { 6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880 }
+ },
+ {
+ { -8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874 },
+ { 22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962 },
+ { -7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899 }
+ },
+ {
+ { 21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152 },
+ { 9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063 },
+ { 7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080 }
+ },
+ {
+ { -9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146 },
+ { -17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183 },
+ { -19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133 }
+ },
+ {
+ { -32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421 },
+ { -3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622 },
+ { -4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197 }
+ },
+ {
+ { 2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663 },
+ { 31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753 },
+ { 4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755 }
+ },
+ {
+ { -9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862 },
+ { -26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118 },
+ { 26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171 }
+ },
+ {
+ { 15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380 },
+ { 16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824 },
+ { 28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270 }
+ }
+},
+{ /* 23/31 */
+ {
+ { -817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438 },
+ { -31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584 },
+ { -594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562 }
+ },
+ {
+ { 30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471 },
+ { 18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610 },
+ { 19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269 }
+ },
+ {
+ { -30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650 },
+ { 14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369 },
+ { 19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461 }
+ },
+ {
+ { 30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462 },
+ { -5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793 },
+ { -2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218 }
+ },
+ {
+ { -24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226 },
+ { 18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019 },
+ { -15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037 }
+ },
+ {
+ { 31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171 },
+ { -17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132 },
+ { -28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841 }
+ },
+ {
+ { 21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181 },
+ { -33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210 },
+ { -1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040 }
+ },
+ {
+ { 3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935 },
+ { 24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105 },
+ { -28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814 }
+ }
+},
+{ /* 24/31 */
+ {
+ { 793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852 },
+ { 5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581 },
+ { -4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646 }
+ },
+ {
+ { 10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844 },
+ { 10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025 },
+ { 27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453 }
+ },
+ {
+ { -23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068 },
+ { 4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192 },
+ { -17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921 }
+ },
+ {
+ { -9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259 },
+ { -12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426 },
+ { -5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072 }
+ },
+ {
+ { -17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305 },
+ { 13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832 },
+ { 28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943 }
+ },
+ {
+ { -16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011 },
+ { 24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447 },
+ { 17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494 }
+ },
+ {
+ { -28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245 },
+ { -20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859 },
+ { 28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915 }
+ },
+ {
+ { 16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707 },
+ { 10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848 },
+ { -11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224 }
+ }
+},
+{ /* 25/31 */
+ {
+ { -25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391 },
+ { 15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215 },
+ { -23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101 }
+ },
+ {
+ { 23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713 },
+ { 21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849 },
+ { -7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930 }
+ },
+ {
+ { -29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940 },
+ { -21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031 },
+ { -17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404 }
+ },
+ {
+ { -25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243 },
+ { -23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116 },
+ { -24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525 }
+ },
+ {
+ { -23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509 },
+ { -10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883 },
+ { 15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865 }
+ },
+ {
+ { -3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660 },
+ { 4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273 },
+ { -28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138 }
+ },
+ {
+ { -25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560 },
+ { -10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135 },
+ { 2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941 }
+ },
+ {
+ { -4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739 },
+ { 18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756 },
+ { -30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819 }
+ }
+},
+{ /* 26/31 */
+ {
+ { -6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347 },
+ { -27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028 },
+ { 21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075 }
+ },
+ {
+ { 16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799 },
+ { -2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609 },
+ { -25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817 }
+ },
+ {
+ { -23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989 },
+ { -30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523 },
+ { 4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278 }
+ },
+ {
+ { 31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045 },
+ { 19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377 },
+ { 24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480 }
+ },
+ {
+ { 17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016 },
+ { 510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426 },
+ { 18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525 }
+ },
+ {
+ { 13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396 },
+ { 9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080 },
+ { 12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892 }
+ },
+ {
+ { 15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275 },
+ { 11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074 },
+ { 20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140 }
+ },
+ {
+ { -16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717 },
+ { -1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101 },
+ { 24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127 }
+ }
+},
+{ /* 27/31 */
+ {
+ { -12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632 },
+ { -26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415 },
+ { -31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160 }
+ },
+ {
+ { 31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876 },
+ { 22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625 },
+ { -15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478 }
+ },
+ {
+ { 27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164 },
+ { 26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595 },
+ { -7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248 }
+ },
+ {
+ { -16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858 },
+ { 15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193 },
+ { 8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184 }
+ },
+ {
+ { -18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942 },
+ { -1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635 },
+ { 21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948 }
+ },
+ {
+ { 11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935 },
+ { -25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415 },
+ { -15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416 }
+ },
+ {
+ { -7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018 },
+ { 4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778 },
+ { 366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659 }
+ },
+ {
+ { -24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385 },
+ { 18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503 },
+ { 476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329 }
+ }
+},
+{ /* 28/31 */
+ {
+ { 20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056 },
+ { -13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838 },
+ { 24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948 }
+ },
+ {
+ { -3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691 },
+ { -15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118 },
+ { -23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517 }
+ },
+ {
+ { -20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269 },
+ { -6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904 },
+ { -23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589 }
+ },
+ {
+ { -28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193 },
+ { -7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910 },
+ { -30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930 }
+ },
+ {
+ { -7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667 },
+ { 25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481 },
+ { -9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876 }
+ },
+ {
+ { 22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640 },
+ { -8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278 },
+ { -21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112 }
+ },
+ {
+ { 26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272 },
+ { 17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012 },
+ { -10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221 }
+ },
+ {
+ { 30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046 },
+ { 13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345 },
+ { -19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310 }
+ }
+},
+{ /* 29/31 */
+ {
+ { 19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937 },
+ { 31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636 },
+ { -9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008 }
+ },
+ {
+ { -2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429 },
+ { -15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576 },
+ { 31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066 }
+ },
+ {
+ { -9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490 },
+ { -12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104 },
+ { 33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053 }
+ },
+ {
+ { 31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275 },
+ { -20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511 },
+ { 22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095 }
+ },
+ {
+ { -28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439 },
+ { 23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939 },
+ { -23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424 }
+ },
+ {
+ { 2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310 },
+ { 3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608 },
+ { -32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079 }
+ },
+ {
+ { -23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101 },
+ { 21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418 },
+ { 18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576 }
+ },
+ {
+ { 30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356 },
+ { 9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996 },
+ { -26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099 }
+ }
+},
+{ /* 30/31 */
+ {
+ { -26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728 },
+ { -13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658 },
+ { -10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242 }
+ },
+ {
+ { -21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001 },
+ { -4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766 },
+ { 18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373 }
+ },
+ {
+ { 26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458 },
+ { -17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628 },
+ { -13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657 }
+ },
+ {
+ { -23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062 },
+ { 25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616 },
+ { 31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014 }
+ },
+ {
+ { 24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383 },
+ { -25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814 },
+ { -20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718 }
+ },
+ {
+ { 30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417 },
+ { 2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222 },
+ { 33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444 }
+ },
+ {
+ { -20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597 },
+ { 23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970 },
+ { 1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799 }
+ },
+ {
+ { -5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647 },
+ { 13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511 },
+ { -29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032 }
+ }
+},
+{ /* 31/31 */
+ {
+ { 9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834 },
+ { -23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461 },
+ { 29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062 }
+ },
+ {
+ { -25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516 },
+ { -20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547 },
+ { -24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240 }
+ },
+ {
+ { -17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038 },
+ { -33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741 },
+ { 16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103 }
+ },
+ {
+ { -19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747 },
+ { -1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323 },
+ { 31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016 }
+ },
+ {
+ { -14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373 },
+ { 15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228 },
+ { -2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141 }
+ },
+ {
+ { 16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399 },
+ { 11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831 },
+ { -185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376 }
+ },
+ {
+ { -32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313 },
+ { -18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958 },
+ { -6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577 }
+ },
+ {
+ { -22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743 },
+ { 29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684 },
+ { -20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476 }
+ }
+}
diff --git a/packages/runtime/vendor/libsodium/fe_25_5/base2.h b/packages/runtime/vendor/libsodium/fe_25_5/base2.h
new file mode 100644
index 000000000..90a1457ea
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/fe_25_5/base2.h
@@ -0,0 +1,40 @@
+{
+ { 25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605 },
+ { -12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378 },
+ { -8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546 }
+},
+{
+ { 15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024 },
+ { 16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574 },
+ { 30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357 }
+},
+{
+ { 10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380 },
+ { 4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306 },
+ { 19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942 }
+},
+{
+ { 5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766 },
+ { -30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701 },
+ { 28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300 }
+},
+{
+ { -22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877 },
+ { -6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951 },
+ { 4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784 }
+},
+{
+ { -25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436 },
+ { 25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918 },
+ { 23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877 }
+},
+{
+ { -33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800 },
+ { -25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305 },
+ { -13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300 }
+},
+{
+ { -3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876 },
+ { -24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619 },
+ { -3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683 }
+}
diff --git a/packages/runtime/vendor/libsodium/fe_25_5/constants.h b/packages/runtime/vendor/libsodium/fe_25_5/constants.h
new file mode 100644
index 000000000..dd6da1b87
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/fe_25_5/constants.h
@@ -0,0 +1,40 @@
+/* 37095705934669439343138083508754565189542113879843219016388785533085940283555 */
+static const fe25519 d = {
+ -10913610, 13857413, -15372611, 6949391, 114729, -8787816, -6275908, -3247719, -18696448, -12055116
+};
+
+/* 2 * d =
+ * 16295367250680780974490674513165176452449235426866156013048779062215315747161
+ */
+static const fe25519 d2 = {
+ -21827239, -5839606, -30745221, 13898782, 229458, 15978800, -12551817, -6495438, 29715968, 9444199 };
+
+/* sqrt(-1) */
+static const fe25519 sqrtm1 = {
+ -32595792, -7943725, 9377950, 3500415, 12389472, -272473, -25146209, -2005654, 326686, 11406482
+};
+
+/* A = 486662 */
+static const fe25519 curve25519_A = {
+ 486662, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/* sqrt(ad - 1) with a = -1 (mod p) */
+static const fe25519 sqrtadm1 = {
+ 24849947, -153582, -23613485, 6347715, -21072328, -667138, -25271143, -15367704, -870347, 14525639
+};
+
+/* 1 / sqrt(a - d) */
+static const fe25519 invsqrtamd = {
+ 6111485, 4156064, -27798727, 12243468, -25904040, 120897, 20826367, -7060776, 6093568, -1986012
+};
+
+/* 1 - d ^ 2 */
+static const fe25519 onemsqd = {
+ 6275446, -16617371, -22938544, -3773710, 11667077, 7397348, -27922721, 1766195, -24433858, 672203
+};
+
+/* (d - 1) ^ 2 */
+static const fe25519 sqdmone = {
+ 15551795, -11097455, -13425098, -10125071, -11896535, 10178284, -26634327, 4729244, -5282110, -10116402
+};
diff --git a/packages/runtime/vendor/libsodium/fe_25_5/fe.h b/packages/runtime/vendor/libsodium/fe_25_5/fe.h
new file mode 100644
index 000000000..9e11afd11
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/fe_25_5/fe.h
@@ -0,0 +1,220 @@
+/*
+ Ignores top bit of s.
+ */
+
+void
+fe25519_frombytes(fe25519 h, const unsigned char *s)
+{
+ int64_t h0 = load_4(s);
+ int64_t h1 = load_3(s + 4) << 6;
+ int64_t h2 = load_3(s + 7) << 5;
+ int64_t h3 = load_3(s + 10) << 3;
+ int64_t h4 = load_3(s + 13) << 2;
+ int64_t h5 = load_4(s + 16);
+ int64_t h6 = load_3(s + 20) << 7;
+ int64_t h7 = load_3(s + 23) << 5;
+ int64_t h8 = load_3(s + 26) << 4;
+ int64_t h9 = (load_3(s + 29) & 8388607) << 2;
+
+ int64_t carry0;
+ int64_t carry1;
+ int64_t carry2;
+ int64_t carry3;
+ int64_t carry4;
+ int64_t carry5;
+ int64_t carry6;
+ int64_t carry7;
+ int64_t carry8;
+ int64_t carry9;
+
+ carry9 = (h9 + (int64_t)(1L << 24)) >> 25;
+ h0 += carry9 * 19;
+ h9 -= carry9 * ((uint64_t) 1L << 25);
+ carry1 = (h1 + (int64_t)(1L << 24)) >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((uint64_t) 1L << 25);
+ carry3 = (h3 + (int64_t)(1L << 24)) >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((uint64_t) 1L << 25);
+ carry5 = (h5 + (int64_t)(1L << 24)) >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((uint64_t) 1L << 25);
+ carry7 = (h7 + (int64_t)(1L << 24)) >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((uint64_t) 1L << 25);
+
+ carry0 = (h0 + (int64_t)(1L << 25)) >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint64_t) 1L << 26);
+ carry2 = (h2 + (int64_t)(1L << 25)) >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((uint64_t) 1L << 26);
+ carry4 = (h4 + (int64_t)(1L << 25)) >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint64_t) 1L << 26);
+ carry6 = (h6 + (int64_t)(1L << 25)) >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((uint64_t) 1L << 26);
+ carry8 = (h8 + (int64_t)(1L << 25)) >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((uint64_t) 1L << 26);
+
+ h[0] = (int32_t) h0;
+ h[1] = (int32_t) h1;
+ h[2] = (int32_t) h2;
+ h[3] = (int32_t) h3;
+ h[4] = (int32_t) h4;
+ h[5] = (int32_t) h5;
+ h[6] = (int32_t) h6;
+ h[7] = (int32_t) h7;
+ h[8] = (int32_t) h8;
+ h[9] = (int32_t) h9;
+}
+
+/*
+ Preconditions:
+ |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
+
+ Write p=2^255-19; q=floor(h/p).
+ Basic claim: q = floor(2^(-255)(h + 19 2^(-25)h9 + 2^(-1))).
+
+ Proof:
+ Have |h|<=p so |q|<=1 so |19^2 2^(-255) q|<1/4.
+ Also have |h-2^230 h9|<2^231 so |19 2^(-255)(h-2^230 h9)|<1/4.
+
+ Write y=2^(-1)-19^2 2^(-255)q-19 2^(-255)(h-2^230 h9).
+ Then 0> 25;
+ q = (h0 + q) >> 26;
+ q = (h1 + q) >> 25;
+ q = (h2 + q) >> 26;
+ q = (h3 + q) >> 25;
+ q = (h4 + q) >> 26;
+ q = (h5 + q) >> 25;
+ q = (h6 + q) >> 26;
+ q = (h7 + q) >> 25;
+ q = (h8 + q) >> 26;
+ q = (h9 + q) >> 25;
+
+ /* Goal: Output h-(2^255-19)q, which is between 0 and 2^255-20. */
+ h0 += 19 * q;
+ /* Goal: Output h-2^255 q, which is between 0 and 2^255-20. */
+
+ carry0 = h0 >> 26;
+ h1 += carry0;
+ h0 -= carry0 * ((uint32_t) 1L << 26);
+ carry1 = h1 >> 25;
+ h2 += carry1;
+ h1 -= carry1 * ((uint32_t) 1L << 25);
+ carry2 = h2 >> 26;
+ h3 += carry2;
+ h2 -= carry2 * ((uint32_t) 1L << 26);
+ carry3 = h3 >> 25;
+ h4 += carry3;
+ h3 -= carry3 * ((uint32_t) 1L << 25);
+ carry4 = h4 >> 26;
+ h5 += carry4;
+ h4 -= carry4 * ((uint32_t) 1L << 26);
+ carry5 = h5 >> 25;
+ h6 += carry5;
+ h5 -= carry5 * ((uint32_t) 1L << 25);
+ carry6 = h6 >> 26;
+ h7 += carry6;
+ h6 -= carry6 * ((uint32_t) 1L << 26);
+ carry7 = h7 >> 25;
+ h8 += carry7;
+ h7 -= carry7 * ((uint32_t) 1L << 25);
+ carry8 = h8 >> 26;
+ h9 += carry8;
+ h8 -= carry8 * ((uint32_t) 1L << 26);
+ carry9 = h9 >> 25;
+ h9 -= carry9 * ((uint32_t) 1L << 25);
+
+ h[0] = h0;
+ h[1] = h1;
+ h[2] = h2;
+ h[3] = h3;
+ h[4] = h4;
+ h[5] = h5;
+ h[6] = h6;
+ h[7] = h7;
+ h[8] = h8;
+ h[9] = h9;
+}
+
+/*
+ Goal: Output h0+...+2^255 h10-2^255 q, which is between 0 and 2^255-20.
+ Have h0+...+2^230 h9 between 0 and 2^255-1;
+ evidently 2^255 h10-2^255 q = 0.
+
+ Goal: Output h0+...+2^230 h9.
+ */
+
+void
+fe25519_tobytes(unsigned char *s, const fe25519 h)
+{
+ fe25519 t;
+
+ fe25519_reduce(t, h);
+ s[0] = t[0] >> 0;
+ s[1] = t[0] >> 8;
+ s[2] = t[0] >> 16;
+ s[3] = (t[0] >> 24) | (t[1] * ((uint32_t) 1 << 2));
+ s[4] = t[1] >> 6;
+ s[5] = t[1] >> 14;
+ s[6] = (t[1] >> 22) | (t[2] * ((uint32_t) 1 << 3));
+ s[7] = t[2] >> 5;
+ s[8] = t[2] >> 13;
+ s[9] = (t[2] >> 21) | (t[3] * ((uint32_t) 1 << 5));
+ s[10] = t[3] >> 3;
+ s[11] = t[3] >> 11;
+ s[12] = (t[3] >> 19) | (t[4] * ((uint32_t) 1 << 6));
+ s[13] = t[4] >> 2;
+ s[14] = t[4] >> 10;
+ s[15] = t[4] >> 18;
+ s[16] = t[5] >> 0;
+ s[17] = t[5] >> 8;
+ s[18] = t[5] >> 16;
+ s[19] = (t[5] >> 24) | (t[6] * ((uint32_t) 1 << 1));
+ s[20] = t[6] >> 7;
+ s[21] = t[6] >> 15;
+ s[22] = (t[6] >> 23) | (t[7] * ((uint32_t) 1 << 3));
+ s[23] = t[7] >> 5;
+ s[24] = t[7] >> 13;
+ s[25] = (t[7] >> 21) | (t[8] * ((uint32_t) 1 << 4));
+ s[26] = t[8] >> 4;
+ s[27] = t[8] >> 12;
+ s[28] = (t[8] >> 20) | (t[9] * ((uint32_t) 1 << 6));
+ s[29] = t[9] >> 2;
+ s[30] = t[9] >> 10;
+ s[31] = t[9] >> 18;
+}
diff --git a/packages/runtime/vendor/libsodium/hash_sha512_cp.c b/packages/runtime/vendor/libsodium/hash_sha512_cp.c
new file mode 100644
index 000000000..59a8b9368
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/hash_sha512_cp.c
@@ -0,0 +1,284 @@
+
+/*-
+ * Copyright 2005,2007,2009 Colin Percival
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "crypto_hash_sha512.h"
+#include "common.h"
+#include "utils.h"
+
+static void
+be64enc_vect(unsigned char *dst, const uint64_t *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 8; i++) {
+ STORE64_BE(dst + i * 8, src[i]);
+ }
+}
+
+static void
+be64dec_vect(uint64_t *dst, const unsigned char *src, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len / 8; i++) {
+ dst[i] = LOAD64_BE(src + i * 8);
+ }
+}
+
+static const uint64_t Krnd[80] = {
+ 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL,
+ 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL,
+ 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL,
+ 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL,
+ 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL,
+ 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL,
+ 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 0x2de92c6f592b0275ULL,
+ 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL,
+ 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL,
+ 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL,
+ 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL,
+ 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL,
+ 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL,
+ 0x92722c851482353bULL, 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL,
+ 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL,
+ 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL,
+ 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL,
+ 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL,
+ 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL,
+ 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL,
+ 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL,
+ 0xc67178f2e372532bULL, 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL,
+ 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL,
+ 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL,
+ 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL,
+ 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL,
+ 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL
+};
+
+#define Ch(x, y, z) ((x & (y ^ z)) ^ z)
+#define Maj(x, y, z) ((x & (y | z)) | (y & z))
+#define SHR(x, n) (x >> n)
+#define ROTR(x, n) ROTR64(x, n)
+#define S0(x) (ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39))
+#define S1(x) (ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41))
+#define s0(x) (ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7))
+#define s1(x) (ROTR(x, 19) ^ ROTR(x, 61) ^ SHR(x, 6))
+
+#define RND(a, b, c, d, e, f, g, h, k) \
+ h += S1(e) + Ch(e, f, g) + k; \
+ d += h; \
+ h += S0(a) + Maj(a, b, c);
+
+#define RNDr(S, W, i, ii) \
+ RND(S[(80 - i) % 8], S[(81 - i) % 8], S[(82 - i) % 8], S[(83 - i) % 8], \
+ S[(84 - i) % 8], S[(85 - i) % 8], S[(86 - i) % 8], S[(87 - i) % 8], \
+ W[i + ii] + Krnd[i + ii])
+
+#define MSCH(W, ii, i) \
+ W[i + ii + 16] = \
+ s1(W[i + ii + 14]) + W[i + ii + 9] + s0(W[i + ii + 1]) + W[i + ii]
+
+static void
+SHA512_Transform(uint64_t *state, const uint8_t block[128], uint64_t W[80],
+ uint64_t S[8])
+{
+ int i;
+
+ be64dec_vect(W, block, 128);
+ memcpy(S, state, 64);
+ for (i = 0; i < 80; i += 16) {
+ RNDr(S, W, 0, i);
+ RNDr(S, W, 1, i);
+ RNDr(S, W, 2, i);
+ RNDr(S, W, 3, i);
+ RNDr(S, W, 4, i);
+ RNDr(S, W, 5, i);
+ RNDr(S, W, 6, i);
+ RNDr(S, W, 7, i);
+ RNDr(S, W, 8, i);
+ RNDr(S, W, 9, i);
+ RNDr(S, W, 10, i);
+ RNDr(S, W, 11, i);
+ RNDr(S, W, 12, i);
+ RNDr(S, W, 13, i);
+ RNDr(S, W, 14, i);
+ RNDr(S, W, 15, i);
+ if (i == 64) {
+ break;
+ }
+ MSCH(W, 0, i);
+ MSCH(W, 1, i);
+ MSCH(W, 2, i);
+ MSCH(W, 3, i);
+ MSCH(W, 4, i);
+ MSCH(W, 5, i);
+ MSCH(W, 6, i);
+ MSCH(W, 7, i);
+ MSCH(W, 8, i);
+ MSCH(W, 9, i);
+ MSCH(W, 10, i);
+ MSCH(W, 11, i);
+ MSCH(W, 12, i);
+ MSCH(W, 13, i);
+ MSCH(W, 14, i);
+ MSCH(W, 15, i);
+ }
+ for (i = 0; i < 8; i++) {
+ state[i] += S[i];
+ }
+}
+
+static const uint8_t PAD[128] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static void
+SHA512_Pad(crypto_hash_sha512_state *state, uint64_t tmp64[80 + 8])
+{
+ unsigned int r;
+ unsigned int i;
+
+ ACQUIRE_FENCE;
+ r = (unsigned int) ((state->count[1] >> 3) & 0x7f);
+ if (r < 112) {
+ for (i = 0; i < 112 - r; i++) {
+ state->buf[r + i] = PAD[i];
+ }
+ } else {
+ for (i = 0; i < 128 - r; i++) {
+ state->buf[r + i] = PAD[i];
+ }
+ SHA512_Transform(state->state, state->buf, &tmp64[0], &tmp64[80]);
+ memset(&state->buf[0], 0, 112);
+ }
+ be64enc_vect(&state->buf[112], state->count, 16);
+ SHA512_Transform(state->state, state->buf, &tmp64[0], &tmp64[80]);
+}
+
+int
+crypto_hash_sha512_init(crypto_hash_sha512_state *state)
+{
+ static const uint64_t sha512_initial_state[8] = {
+ 0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL,
+ 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,
+ 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL
+ };
+
+ state->count[0] = state->count[1] = (uint64_t) 0U;
+ memcpy(state->state, sha512_initial_state, sizeof sha512_initial_state);
+
+ return 0;
+}
+
+int
+crypto_hash_sha512_update(crypto_hash_sha512_state *state,
+ const unsigned char *in, unsigned long long inlen)
+{
+ uint64_t tmp64[80 + 8];
+ uint64_t bitlen[2];
+ unsigned long long i;
+ unsigned long long r;
+
+ if (inlen <= 0U) {
+ return 0;
+ }
+ ACQUIRE_FENCE;
+ r = (unsigned long long) ((state->count[1] >> 3) & 0x7f);
+
+ bitlen[1] = ((uint64_t) inlen) << 3;
+ bitlen[0] = ((uint64_t) inlen) >> 61;
+ /* LCOV_EXCL_START */
+ if ((state->count[1] += bitlen[1]) < bitlen[1]) {
+ state->count[0]++;
+ }
+ /* LCOV_EXCL_STOP */
+ state->count[0] += bitlen[0];
+ if (inlen < 128 - r) {
+ for (i = 0; i < inlen; i++) {
+ state->buf[r + i] = in[i];
+ }
+ return 0;
+ }
+ for (i = 0; i < 128 - r; i++) {
+ state->buf[r + i] = in[i];
+ }
+ SHA512_Transform(state->state, state->buf, &tmp64[0], &tmp64[80]);
+ in += 128 - r;
+ inlen -= 128 - r;
+
+ while (inlen >= 128) {
+ SHA512_Transform(state->state, in, &tmp64[0], &tmp64[80]);
+ in += 128;
+ inlen -= 128;
+ }
+ inlen &= 127;
+ for (i = 0; i < inlen; i++) {
+ state->buf[i] = in[i];
+ }
+ sodium_memzero((void *) tmp64, sizeof tmp64);
+
+ return 0;
+}
+
+int
+crypto_hash_sha512_final(crypto_hash_sha512_state *state, unsigned char *out)
+{
+ uint64_t tmp64[80 + 8];
+
+ SHA512_Pad(state, tmp64);
+ be64enc_vect(out, state->state, 64);
+ sodium_memzero((void *) tmp64, sizeof tmp64);
+ sodium_memzero((void *) state, sizeof *state);
+
+ return 0;
+}
+
+int
+crypto_hash_sha512(unsigned char *out, const unsigned char *in,
+ unsigned long long inlen)
+{
+ crypto_hash_sha512_state state;
+
+ crypto_hash_sha512_init(&state);
+ crypto_hash_sha512_update(&state, in, inlen);
+ crypto_hash_sha512_final(&state, out);
+
+ return 0;
+}
diff --git a/packages/runtime/vendor/libsodium/keypair.c b/packages/runtime/vendor/libsodium/keypair.c
new file mode 100644
index 000000000..18059adfe
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/keypair.c
@@ -0,0 +1,84 @@
+
+#include
+
+#include "crypto_hash_sha512.h"
+#include "crypto_scalarmult_curve25519.h"
+#include "crypto_sign_ed25519.h"
+#include "sign_ed25519_ref10.h"
+#include "ed25519_ref10.h"
+#include "randombytes.h"
+#include "utils.h"
+
+int
+crypto_sign_ed25519_seed_keypair(unsigned char *pk, unsigned char *sk,
+ const unsigned char *seed)
+{
+ ge25519_p3 A;
+
+ crypto_hash_sha512(sk, seed, 32);
+ sk[0] &= 248;
+ sk[31] &= 127;
+ sk[31] |= 64;
+
+ ge25519_scalarmult_base(&A, sk);
+ ge25519_p3_tobytes(pk, &A);
+
+ memmove(sk, seed, 32);
+ memmove(sk + 32, pk, 32);
+
+ return 0;
+}
+
+int
+crypto_sign_ed25519_keypair(unsigned char *pk, unsigned char *sk)
+{
+ unsigned char seed[32];
+ int ret;
+
+ randombytes_buf(seed, sizeof seed);
+ ret = crypto_sign_ed25519_seed_keypair(pk, sk, seed);
+ sodium_memzero(seed, sizeof seed);
+
+ return ret;
+}
+
+int
+crypto_sign_ed25519_pk_to_curve25519(unsigned char *curve25519_pk,
+ const unsigned char *ed25519_pk)
+{
+ ge25519_p3 A;
+ fe25519 x;
+ fe25519 one_minus_y;
+
+ if (ge25519_has_small_order(ed25519_pk) != 0 ||
+ ge25519_frombytes_negate_vartime(&A, ed25519_pk) != 0 ||
+ ge25519_is_on_main_subgroup(&A) == 0) {
+ return -1;
+ }
+ fe25519_1(one_minus_y);
+ /* assumes A.Z=1 */
+ fe25519_sub(one_minus_y, one_minus_y, A.Y);
+ fe25519_1(x);
+ fe25519_add(x, x, A.Y);
+ fe25519_invert(one_minus_y, one_minus_y);
+ fe25519_mul(x, x, one_minus_y);
+ fe25519_tobytes(curve25519_pk, x);
+
+ return 0;
+}
+
+int
+crypto_sign_ed25519_sk_to_curve25519(unsigned char *curve25519_sk,
+ const unsigned char *ed25519_sk)
+{
+ unsigned char h[crypto_hash_sha512_BYTES];
+
+ crypto_hash_sha512(h, ed25519_sk, 32);
+ h[0] &= 248;
+ h[31] &= 127;
+ h[31] |= 64;
+ memcpy(curve25519_sk, h, crypto_scalarmult_curve25519_BYTES);
+ sodium_memzero(h, sizeof h);
+
+ return 0;
+}
diff --git a/packages/runtime/vendor/libsodium/open.c b/packages/runtime/vendor/libsodium/open.c
new file mode 100644
index 000000000..0670866d6
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/open.c
@@ -0,0 +1,98 @@
+
+#include
+#include
+#include
+
+#include "crypto_hash_sha512.h"
+#include "crypto_sign_ed25519.h"
+#include "crypto_verify_32.h"
+#include "sign_ed25519_ref10.h"
+#include "common.h"
+#include "ed25519_ref10.h"
+#include "utils.h"
+
+int
+_crypto_sign_ed25519_verify_detached(const unsigned char *sig,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *pk,
+ int prehashed)
+{
+ crypto_hash_sha512_state hs;
+ unsigned char h[64];
+ unsigned char rcheck[32];
+ ge25519_p3 A;
+ ge25519_p2 R;
+
+ ACQUIRE_FENCE;
+#ifdef ED25519_COMPAT
+ if (sig[63] & 224) {
+ return -1;
+ }
+#else
+ if (sc25519_is_canonical(sig + 32) == 0 ||
+ ge25519_has_small_order(sig) != 0) {
+ return -1;
+ }
+ if (ge25519_is_canonical(pk) == 0 ||
+ ge25519_has_small_order(pk) != 0) {
+ return -1;
+ }
+#endif
+ if (ge25519_frombytes_negate_vartime(&A, pk) != 0) {
+ return -1;
+ }
+ _crypto_sign_ed25519_ref10_hinit(&hs, prehashed);
+ crypto_hash_sha512_update(&hs, sig, 32);
+ crypto_hash_sha512_update(&hs, pk, 32);
+ crypto_hash_sha512_update(&hs, m, mlen);
+ crypto_hash_sha512_final(&hs, h);
+ sc25519_reduce(h);
+
+ ge25519_double_scalarmult_vartime(&R, h, &A, sig + 32);
+ ge25519_tobytes(rcheck, &R);
+
+ return crypto_verify_32(rcheck, sig) | (-(rcheck == sig)) |
+ sodium_memcmp(sig, rcheck, 32);
+}
+
+int
+crypto_sign_ed25519_verify_detached(const unsigned char *sig,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *pk)
+{
+ return _crypto_sign_ed25519_verify_detached(sig, m, mlen, pk, 0);
+}
+
+int
+crypto_sign_ed25519_open(unsigned char *m, unsigned long long *mlen_p,
+ const unsigned char *sm, unsigned long long smlen,
+ const unsigned char *pk)
+{
+ unsigned long long mlen;
+
+ if (smlen < 64 || smlen - 64 > crypto_sign_ed25519_MESSAGEBYTES_MAX) {
+ goto badsig;
+ }
+ mlen = smlen - 64;
+ if (crypto_sign_ed25519_verify_detached(sm, sm + 64, mlen, pk) != 0) {
+ if (m != NULL) {
+ memset(m, 0, mlen);
+ }
+ goto badsig;
+ }
+ if (mlen_p != NULL) {
+ *mlen_p = mlen;
+ }
+ if (m != NULL) {
+ memmove(m, sm + 64, mlen);
+ }
+ return 0;
+
+badsig:
+ if (mlen_p != NULL) {
+ *mlen_p = 0;
+ }
+ return -1;
+}
diff --git a/packages/runtime/vendor/libsodium/randombytes.h b/packages/runtime/vendor/libsodium/randombytes.h
new file mode 100644
index 000000000..c83a4df50
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/randombytes.h
@@ -0,0 +1,72 @@
+
+#ifndef randombytes_H
+#define randombytes_H
+
+#include
+#include
+
+#include
+
+#include "export.h"
+
+#ifdef __cplusplus
+# ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wlong-long"
+# endif
+extern "C" {
+#endif
+
+typedef struct randombytes_implementation {
+ const char *(*implementation_name)(void); /* required */
+ uint32_t (*random)(void); /* required */
+ void (*stir)(void); /* optional */
+ uint32_t (*uniform)(const uint32_t upper_bound); /* optional, a default implementation will be used if NULL */
+ void (*buf)(void * const buf, const size_t size); /* required */
+ int (*close)(void); /* optional */
+} randombytes_implementation;
+
+#define randombytes_BYTES_MAX SODIUM_MIN(SODIUM_SIZE_MAX, 0xffffffffUL)
+
+#define randombytes_SEEDBYTES 32U
+SODIUM_EXPORT
+size_t randombytes_seedbytes(void);
+
+SODIUM_EXPORT
+void randombytes_buf(void * const buf, const size_t size)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+void randombytes_buf_deterministic(void * const buf, const size_t size,
+ const unsigned char seed[randombytes_SEEDBYTES])
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+uint32_t randombytes_random(void);
+
+SODIUM_EXPORT
+uint32_t randombytes_uniform(const uint32_t upper_bound);
+
+SODIUM_EXPORT
+void randombytes_stir(void);
+
+SODIUM_EXPORT
+int randombytes_close(void);
+
+SODIUM_EXPORT
+int randombytes_set_implementation(const randombytes_implementation *impl)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+const char *randombytes_implementation_name(void);
+
+/* -- NaCl compatibility interface -- */
+
+SODIUM_EXPORT
+void randombytes(unsigned char * const buf, const unsigned long long buf_len)
+ __attribute__ ((nonnull));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/sign.c b/packages/runtime/vendor/libsodium/sign.c
new file mode 100644
index 000000000..1b4a2923c
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/sign.c
@@ -0,0 +1,128 @@
+
+#include
+
+#include "crypto_hash_sha512.h"
+#include "crypto_sign_ed25519.h"
+#include "sign_ed25519_ref10.h"
+#include "ed25519_ref10.h"
+#include "randombytes.h"
+#include "utils.h"
+
+void
+_crypto_sign_ed25519_ref10_hinit(crypto_hash_sha512_state *hs, int prehashed)
+{
+ static const unsigned char DOM2PREFIX[32 + 2] = {
+ 'S', 'i', 'g', 'E', 'd', '2', '5', '5', '1', '9', ' ',
+ 'n', 'o', ' ',
+ 'E', 'd', '2', '5', '5', '1', '9', ' ',
+ 'c', 'o', 'l', 'l', 'i', 's', 'i', 'o', 'n', 's', 1, 0
+ };
+
+ crypto_hash_sha512_init(hs);
+ if (prehashed) {
+ crypto_hash_sha512_update(hs, DOM2PREFIX, sizeof DOM2PREFIX);
+ }
+}
+
+static inline void
+_crypto_sign_ed25519_clamp(unsigned char k[32])
+{
+ k[0] &= 248;
+ k[31] &= 127;
+ k[31] |= 64;
+}
+
+#ifdef ED25519_NONDETERMINISTIC
+/* r = hash(k || K || noise || pad || M) (mod q) */
+static void
+_crypto_sign_ed25519_synthetic_r_hv(crypto_hash_sha512_state *hs,
+ unsigned char tmp[64],
+ const unsigned char az[64])
+{
+ crypto_hash_sha512_update(hs, az, 64);
+ randombytes_buf(tmp, 32);
+ memset(tmp + 32, 0, 32);
+ crypto_hash_sha512_update(hs, tmp, 64);
+}
+#endif
+
+int
+_crypto_sign_ed25519_detached(unsigned char *sig, unsigned long long *siglen_p,
+ const unsigned char *m, unsigned long long mlen,
+ const unsigned char *sk, int prehashed)
+{
+ crypto_hash_sha512_state hs;
+ unsigned char az[64];
+ unsigned char nonce[64];
+ unsigned char hram[64];
+ ge25519_p3 R;
+
+ _crypto_sign_ed25519_ref10_hinit(&hs, prehashed);
+
+ crypto_hash_sha512(az, sk, 32);
+#ifdef ED25519_NONDETERMINISTIC
+ _crypto_sign_ed25519_synthetic_r_hv(&hs, nonce /* tmp */, az);
+#else
+ crypto_hash_sha512_update(&hs, az + 32, 32);
+#endif
+
+ crypto_hash_sha512_update(&hs, m, mlen);
+ crypto_hash_sha512_final(&hs, nonce);
+
+ memmove(sig + 32, sk + 32, 32);
+
+ sc25519_reduce(nonce);
+ ge25519_scalarmult_base(&R, nonce);
+ ge25519_p3_tobytes(sig, &R);
+
+ _crypto_sign_ed25519_ref10_hinit(&hs, prehashed);
+ crypto_hash_sha512_update(&hs, sig, 64);
+ crypto_hash_sha512_update(&hs, m, mlen);
+ crypto_hash_sha512_final(&hs, hram);
+
+ sc25519_reduce(hram);
+ _crypto_sign_ed25519_clamp(az);
+ sc25519_muladd(sig + 32, hram, az, nonce);
+
+ sodium_memzero(az, sizeof az);
+ sodium_memzero(nonce, sizeof nonce);
+
+ if (siglen_p != NULL) {
+ *siglen_p = 64U;
+ }
+ return 0;
+}
+
+int
+crypto_sign_ed25519_detached(unsigned char *sig, unsigned long long *siglen_p,
+ const unsigned char *m, unsigned long long mlen,
+ const unsigned char *sk)
+{
+ return _crypto_sign_ed25519_detached(sig, siglen_p, m, mlen, sk, 0);
+}
+
+int
+crypto_sign_ed25519(unsigned char *sm, unsigned long long *smlen_p,
+ const unsigned char *m, unsigned long long mlen,
+ const unsigned char *sk)
+{
+ unsigned long long siglen;
+
+ memmove(sm + crypto_sign_ed25519_BYTES, m, mlen);
+ /* LCOV_EXCL_START */
+ if (crypto_sign_ed25519_detached(
+ sm, &siglen, sm + crypto_sign_ed25519_BYTES, mlen, sk) != 0 ||
+ siglen != crypto_sign_ed25519_BYTES) {
+ if (smlen_p != NULL) {
+ *smlen_p = 0;
+ }
+ memset(sm, 0, mlen + crypto_sign_ed25519_BYTES);
+ return -1;
+ }
+ /* LCOV_EXCL_STOP */
+
+ if (smlen_p != NULL) {
+ *smlen_p = mlen + siglen;
+ }
+ return 0;
+}
diff --git a/packages/runtime/vendor/libsodium/sign_ed25519_ref10.h b/packages/runtime/vendor/libsodium/sign_ed25519_ref10.h
new file mode 100644
index 000000000..29f45a854
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/sign_ed25519_ref10.h
@@ -0,0 +1,18 @@
+#ifndef sign_ed25519_ref10_H
+#define sign_ed25519_ref10_H
+
+void _crypto_sign_ed25519_ref10_hinit(crypto_hash_sha512_state *hs,
+ int prehashed);
+
+int _crypto_sign_ed25519_detached(unsigned char *sig,
+ unsigned long long *siglen_p,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *sk, int prehashed);
+
+int _crypto_sign_ed25519_verify_detached(const unsigned char *sig,
+ const unsigned char *m,
+ unsigned long long mlen,
+ const unsigned char *pk,
+ int prehashed);
+#endif
diff --git a/packages/runtime/vendor/libsodium/utils.h b/packages/runtime/vendor/libsodium/utils.h
new file mode 100644
index 000000000..f9b369796
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/utils.h
@@ -0,0 +1,179 @@
+
+#ifndef sodium_utils_H
+#define sodium_utils_H
+
+#include
+
+#include "export.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef SODIUM_C99
+# if defined(__cplusplus) || !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L
+# define SODIUM_C99(X)
+# else
+# define SODIUM_C99(X) X
+# endif
+#endif
+
+SODIUM_EXPORT
+void sodium_memzero(void * const pnt, const size_t len);
+
+SODIUM_EXPORT
+void sodium_stackzero(const size_t len);
+
+/*
+ * WARNING: sodium_memcmp() must be used to verify if two secret keys
+ * are equal, in constant time.
+ * It returns 0 if the keys are equal, and -1 if they differ.
+ * This function is not designed for lexicographical comparisons.
+ */
+SODIUM_EXPORT
+int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len)
+ __attribute__ ((warn_unused_result));
+
+/*
+ * sodium_compare() returns -1 if b1_ < b2_, 1 if b1_ > b2_ and 0 if b1_ == b2_
+ * It is suitable for lexicographical comparisons, or to compare nonces
+ * and counters stored in little-endian format.
+ * However, it is slower than sodium_memcmp().
+ */
+SODIUM_EXPORT
+int sodium_compare(const unsigned char *b1_, const unsigned char *b2_,
+ size_t len) __attribute__ ((warn_unused_result));
+
+SODIUM_EXPORT
+int sodium_is_zero(const unsigned char *n, const size_t nlen);
+
+SODIUM_EXPORT
+void sodium_increment(unsigned char *n, const size_t nlen);
+
+SODIUM_EXPORT
+void sodium_add(unsigned char *a, const unsigned char *b, const size_t len);
+
+SODIUM_EXPORT
+void sodium_sub(unsigned char *a, const unsigned char *b, const size_t len);
+
+SODIUM_EXPORT
+char *sodium_bin2hex(char * const hex, const size_t hex_maxlen,
+ const unsigned char * const bin, const size_t bin_len)
+ __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int sodium_hex2bin(unsigned char * const bin, const size_t bin_maxlen,
+ const char * const hex, const size_t hex_len,
+ const char * const ignore, size_t * const bin_len,
+ const char ** const hex_end)
+ __attribute__ ((nonnull(1)));
+
+#define sodium_base64_VARIANT_ORIGINAL 1
+#define sodium_base64_VARIANT_ORIGINAL_NO_PADDING 3
+#define sodium_base64_VARIANT_URLSAFE 5
+#define sodium_base64_VARIANT_URLSAFE_NO_PADDING 7
+
+/*
+ * Computes the required length to encode BIN_LEN bytes as a base64 string
+ * using the given variant. The computed length includes a trailing \0.
+ */
+#define sodium_base64_ENCODED_LEN(BIN_LEN, VARIANT) \
+ (((BIN_LEN) / 3U) * 4U + \
+ ((((BIN_LEN) - ((BIN_LEN) / 3U) * 3U) | (((BIN_LEN) - ((BIN_LEN) / 3U) * 3U) >> 1)) & 1U) * \
+ (4U - (~((((VARIANT) & 2U) >> 1) - 1U) & (3U - ((BIN_LEN) - ((BIN_LEN) / 3U) * 3U)))) + 1U)
+
+SODIUM_EXPORT
+size_t sodium_base64_encoded_len(const size_t bin_len, const int variant);
+
+SODIUM_EXPORT
+char *sodium_bin2base64(char * const b64, const size_t b64_maxlen,
+ const unsigned char * const bin, const size_t bin_len,
+ const int variant) __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int sodium_base642bin(unsigned char * const bin, const size_t bin_maxlen,
+ const char * const b64, const size_t b64_len,
+ const char * const ignore, size_t * const bin_len,
+ const char ** const b64_end, const int variant)
+ __attribute__ ((nonnull(1)));
+
+SODIUM_EXPORT
+int sodium_mlock(void * const addr, const size_t len)
+ __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int sodium_munlock(void * const addr, const size_t len)
+ __attribute__ ((nonnull));
+
+/* WARNING: sodium_malloc() and sodium_allocarray() are not general-purpose
+ * allocation functions.
+ *
+ * They return a pointer to a region filled with 0xd0 bytes, immediately
+ * followed by a guard page.
+ * As a result, accessing a single byte after the requested allocation size
+ * will intentionally trigger a segmentation fault.
+ *
+ * A canary and an additional guard page placed before the beginning of the
+ * region may also kill the process if a buffer underflow is detected.
+ *
+ * The memory layout is:
+ * [unprotected region size (read only)][guard page (no access)][unprotected pages (read/write)][guard page (no access)]
+ * With the layout of the unprotected pages being:
+ * [optional padding][16-bytes canary][user region]
+ *
+ * However:
+ * - These functions are significantly slower than standard functions
+ * - Each allocation requires 3 or 4 additional pages
+ * - The returned address will not be aligned if the allocation size is not
+ * a multiple of the required alignment. For this reason, these functions
+ * are designed to store data, such as secret keys and messages.
+ *
+ * sodium_malloc() can be used to allocate any libsodium data structure.
+ *
+ * The crypto_generichash_state structure is packed and its length is
+ * either 357 or 361 bytes. For this reason, when using sodium_malloc() to
+ * allocate a crypto_generichash_state structure, padding must be added in
+ * order to ensure proper alignment. crypto_generichash_statebytes()
+ * returns the rounded up structure size, and should be preferred to sizeof():
+ * state = sodium_malloc(crypto_generichash_statebytes());
+ */
+
+SODIUM_EXPORT
+void *sodium_malloc(const size_t size)
+ __attribute__ ((malloc));
+
+SODIUM_EXPORT
+void *sodium_allocarray(size_t count, size_t size)
+ __attribute__ ((malloc));
+
+SODIUM_EXPORT
+void sodium_free(void *ptr);
+
+SODIUM_EXPORT
+int sodium_mprotect_noaccess(void *ptr) __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int sodium_mprotect_readonly(void *ptr) __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int sodium_mprotect_readwrite(void *ptr) __attribute__ ((nonnull));
+
+SODIUM_EXPORT
+int sodium_pad(size_t *padded_buflen_p, unsigned char *buf,
+ size_t unpadded_buflen, size_t blocksize, size_t max_buflen)
+ __attribute__ ((nonnull(2)));
+
+SODIUM_EXPORT
+int sodium_unpad(size_t *unpadded_buflen_p, const unsigned char *buf,
+ size_t padded_buflen, size_t blocksize)
+ __attribute__ ((nonnull(2)));
+
+/* -------- */
+
+int _sodium_alloc_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/packages/runtime/vendor/libsodium/verify.c b/packages/runtime/vendor/libsodium/verify.c
new file mode 100644
index 000000000..0c3323a50
--- /dev/null
+++ b/packages/runtime/vendor/libsodium/verify.c
@@ -0,0 +1,95 @@
+
+#include
+#include
+
+#include "crypto_verify_16.h"
+#include "crypto_verify_32.h"
+#include "crypto_verify_64.h"
+
+size_t
+crypto_verify_16_bytes(void)
+{
+ return crypto_verify_16_BYTES;
+}
+
+size_t
+crypto_verify_32_bytes(void)
+{
+ return crypto_verify_32_BYTES;
+}
+
+size_t
+crypto_verify_64_bytes(void)
+{
+ return crypto_verify_64_BYTES;
+}
+
+#if defined(HAVE_EMMINTRIN_H) && defined(__SSE2__)
+
+# include
+
+static inline int
+crypto_verify_n(const unsigned char *x_, const unsigned char *y_,
+ const int n)
+{
+ const __m128i zero = _mm_setzero_si128();
+ volatile __m128i v1, v2, z;
+ volatile int m;
+ int i;
+
+ const volatile __m128i *volatile x =
+ (const volatile __m128i *volatile) (const void *) x_;
+ const volatile __m128i *volatile y =
+ (const volatile __m128i *volatile) (const void *) y_;
+ v1 = _mm_loadu_si128((const __m128i *) &x[0]);
+ v2 = _mm_loadu_si128((const __m128i *) &y[0]);
+ z = _mm_xor_si128(v1, v2);
+ for (i = 1; i < n / 16; i++) {
+ v1 = _mm_loadu_si128((const __m128i *) &x[i]);
+ v2 = _mm_loadu_si128((const __m128i *) &y[i]);
+ z = _mm_or_si128(z, _mm_xor_si128(v1, v2));
+ }
+ m = _mm_movemask_epi8(_mm_cmpeq_epi32(z, zero));
+ v1 = zero; v2 = zero; z = zero;
+
+ return (int) (((uint32_t) m + 1U) >> 16) - 1;
+}
+
+#else
+
+static inline int
+crypto_verify_n(const unsigned char *x_, const unsigned char *y_,
+ const int n)
+{
+ const volatile unsigned char *volatile x =
+ (const volatile unsigned char *volatile) x_;
+ const volatile unsigned char *volatile y =
+ (const volatile unsigned char *volatile) y_;
+ volatile uint_fast16_t d = 0U;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ d |= x[i] ^ y[i];
+ }
+ return (1 & ((d - 1) >> 8)) - 1;
+}
+
+#endif
+
+int
+crypto_verify_16(const unsigned char *x, const unsigned char *y)
+{
+ return crypto_verify_n(x, y, crypto_verify_16_BYTES);
+}
+
+int
+crypto_verify_32(const unsigned char *x, const unsigned char *y)
+{
+ return crypto_verify_n(x, y, crypto_verify_32_BYTES);
+}
+
+int
+crypto_verify_64(const unsigned char *x, const unsigned char *y)
+{
+ return crypto_verify_n(x, y, crypto_verify_64_BYTES);
+}
diff --git a/scripts/generate-ts7-ast-schema.mjs b/scripts/generate-ts7-ast-schema.mjs
index a1bc80117..07af474de 100644
--- a/scripts/generate-ts7-ast-schema.mjs
+++ b/scripts/generate-ts7-ast-schema.mjs
@@ -44,6 +44,7 @@ const enumNames = [
"ModuleDetectionKind",
"OuterExpressionKinds",
"LanguageVariant",
+ "JsxEmit",
];
const enumLines = [
"/* eslint-disable @typescript-eslint/no-duplicate-enum-values -- TypeScript aliases are part of the protocol. */",
@@ -67,6 +68,7 @@ for (const name of enumNames) {
"ModuleKind",
"ModuleResolutionKind",
"ModuleDetectionKind",
+ "JsxEmit",
].includes(name)
) {
const values = require(join(packageRoot, "dist/enums", `${basename}.js`))[name];
diff --git a/scripts/test-ts7.mjs b/scripts/test-ts7.mjs
index e022c1ff5..b776bea3e 100644
--- a/scripts/test-ts7.mjs
+++ b/scripts/test-ts7.mjs
@@ -25,6 +25,7 @@ if (process.argv.includes("--baselines-only")) {
const ALLOWED_TYPESCRIPT5_IMPORTS = new Set([
"packages/compiler/src/frontend/comptime-node.ts",
+ "packages/compiler/src/frontend/jsx-source.test.ts",
"packages/compiler/src/frontend/ts7/world-check.ts",
"packages/compiler/src/frontend/ts7/source-parser.test.ts",
"packages/compiler/src/library/semantic-source.ts",
diff --git a/tests/corpus/6822-jsx-classic-runtime/factory.ts b/tests/corpus/6822-jsx-classic-runtime/factory.ts
new file mode 100644
index 000000000..df09c6dde
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/factory.ts
@@ -0,0 +1,7 @@
+// A React-shaped factory for the pragma file: createElement and Fragment.
+export const Fragment = "frag";
+
+export function createElement(tag: string, props: object | null, ...children: unknown[]): string {
+ const inner = children.map((child) => String(child)).join(",");
+ return `${tag}${props === null ? "" : JSON.stringify(props)}(${inner})`;
+}
diff --git a/tests/corpus/6822-jsx-classic-runtime/html.ts b/tests/corpus/6822-jsx-classic-runtime/html.ts
new file mode 100644
index 000000000..2e4beb5cc
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/html.ts
@@ -0,0 +1,45 @@
+// A small server-side JSX runtime: h builds elements, render writes HTML.
+export type Leaf = string | number | boolean | null | undefined | Element;
+export type Child = Leaf | Leaf[];
+export type Props = { [name: string]: unknown };
+export type Component = (props: Props, children: Child[]) => Element;
+export interface Element {
+ tag: string;
+ props: Props | null;
+ children: Child[];
+}
+
+export const Fragment = "#fragment";
+
+export function h(tag: string | Component, props: Props | null, ...children: Child[]): Element {
+ if (typeof tag === "function") return tag(props ?? {}, children);
+ return { tag, props, children };
+}
+
+function escape(text: string): string {
+ return text.replace(/&/g, "&").replace(/${inner}${child.tag}>`;
+}
+
+declare global {
+ namespace JSX {
+ type Element = import("./html.js").Element;
+ interface IntrinsicElements {
+ [name: string]: object;
+ }
+ }
+}
diff --git a/tests/corpus/6822-jsx-classic-runtime/list.jsx b/tests/corpus/6822-jsx-classic-runtime/list.jsx
new file mode 100644
index 000000000..d5aa3aee1
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/list.jsx
@@ -0,0 +1,22 @@
+/** @jsx tag */
+// JSX in a JavaScript module, with a JavaScript factory that builds strings.
+function tag(name, props, ...children) {
+ const attributes = Object.entries(props ?? {})
+ .map(([key, value]) => ` ${key}=${JSON.stringify(value)}`)
+ .join("");
+ return `<${name}${attributes}>${children.flat().join("")}${name}>`;
+}
+
+const names = ["a", "b"];
+
+export function List() {
+ return (
+
+ {names.map((name, index) => (
+ -
+ {name.toUpperCase()}
+
+ ))}
+
+ );
+}
diff --git a/tests/corpus/6822-jsx-classic-runtime/main.ts b/tests/corpus/6822-jsx-classic-runtime/main.ts
new file mode 100644
index 000000000..4378557a0
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/main.ts
@@ -0,0 +1,15 @@
+// @tsc-jsx
+// JSX in .tsx modules with the classic runtime: jsxFactory, jsxFragmentFactory and per-file pragmas.
+import { render } from "./html.js";
+import { List } from "./list.js";
+import { Page } from "./page.js";
+import { viaPragma } from "./pragma.js";
+
+const items = [
+ { id: 1, name: "write ", done: true },
+ { id: 2, name: "ship", done: false },
+];
+console.log(render(Page("Today", items)));
+console.log(render(Page("Empty", [])));
+console.log(viaPragma);
+console.log(List());
diff --git a/tests/corpus/6822-jsx-classic-runtime/page.tsx b/tests/corpus/6822-jsx-classic-runtime/page.tsx
new file mode 100644
index 000000000..c3fa6a03c
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/page.tsx
@@ -0,0 +1,40 @@
+import { Fragment, h, type Child, type Props } from "./html.js";
+
+const Ui = {
+ Badge(props: Props, children: Child[]) {
+ return {...children};
+ },
+};
+
+function Row(props: Props) {
+ const name = typeof props.name === "string" ? props.name : "?";
+ const done = props.done === true;
+ return (
+
+ {name}
+ {done && ✓}
+
+ );
+}
+
+export function Page(title: string, items: { id: number; name: string; done: boolean }[]) {
+ const extra = { lang: "en", dir: "ltr" };
+ return (
+ <>
+ {title}
+
+ {items.map((item) => (
+
+ ))}
+
+
+ {items.filter((item) => item.done).length} of {items.length} done
+ multi word
+ badge
+
+
+
+ <>nested {"fragment"}>
+ >
+ );
+}
diff --git a/tests/corpus/6822-jsx-classic-runtime/pragma.tsx b/tests/corpus/6822-jsx-classic-runtime/pragma.tsx
new file mode 100644
index 000000000..3aea8c330
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/pragma.tsx
@@ -0,0 +1,11 @@
+/** @jsx React.createElement */
+/** @jsxFrag React.Fragment */
+// The per-file pragmas select another factory than the tsconfig's.
+import * as React from "./factory.js";
+
+export const viaPragma = (
+
+ <>{1}>
+ text
+
+);
diff --git a/tests/corpus/6822-jsx-classic-runtime/tsconfig.json b/tests/corpus/6822-jsx-classic-runtime/tsconfig.json
new file mode 100644
index 000000000..6ca523cf2
--- /dev/null
+++ b/tests/corpus/6822-jsx-classic-runtime/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "allowJs": true,
+ "jsx": "react",
+ "jsxFactory": "h",
+ "jsxFragmentFactory": "Fragment"
+ }
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/alt.tsx b/tests/corpus/6824-jsx-automatic-runtime/alt.tsx
new file mode 100644
index 000000000..a76d0ca72
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/alt.tsx
@@ -0,0 +1,3 @@
+/** @jsxImportSource #alt */
+// This file's JSX uses the runtime the pragma names.
+export const viaPragma = [one, {1}{2}, <>frag>];
diff --git a/tests/corpus/6824-jsx-automatic-runtime/alt/jsx-runtime.ts b/tests/corpus/6824-jsx-automatic-runtime/alt/jsx-runtime.ts
new file mode 100644
index 000000000..89b942ed2
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/alt/jsx-runtime.ts
@@ -0,0 +1,17 @@
+// A second runtime, selected per file with @jsxImportSource.
+export const Fragment = "alt-fragment";
+
+export function jsx(type: string, props: { children?: unknown }): string {
+ return `${type}(${JSON.stringify(props.children ?? null)})`;
+}
+
+export function jsxs(type: string, props: { children: unknown[] }): string {
+ return `${type}[${props.children.map((child) => JSON.stringify(child)).join(",")}]`;
+}
+
+export namespace JSX {
+ export type Element = string;
+ export interface IntrinsicElements {
+ [name: string]: object;
+ }
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/main.ts b/tests/corpus/6824-jsx-automatic-runtime/main.ts
new file mode 100644
index 000000000..b25a4bc09
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/main.ts
@@ -0,0 +1,10 @@
+// @tsc-jsx
+// JSX with the automatic runtime: jsx/jsxs imports from jsxImportSource, keys, and per-file @jsxImportSource.
+import { calls } from "#jsx/jsx-runtime";
+import { viaPragma } from "./alt.js";
+import { page } from "./page.js";
+import { render } from "./render.js";
+
+console.log(render(page(["ada", "lin"])));
+console.log(calls.join(" "));
+console.log(viaPragma.join(" | "));
diff --git a/tests/corpus/6824-jsx-automatic-runtime/package.json b/tests/corpus/6824-jsx-automatic-runtime/package.json
new file mode 100644
index 000000000..53f44dc61
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/package.json
@@ -0,0 +1,8 @@
+{
+ "type": "module",
+ "imports": {
+ "#jsx": "./runtime/index.ts",
+ "#jsx/jsx-runtime": "./runtime/jsx-runtime.ts",
+ "#alt/jsx-runtime": "./alt/jsx-runtime.ts"
+ }
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/page.tsx b/tests/corpus/6824-jsx-automatic-runtime/page.tsx
new file mode 100644
index 000000000..fe7fd8c0c
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/page.tsx
@@ -0,0 +1,33 @@
+import type { Props } from "#jsx/jsx-runtime";
+
+function Card(props: Props) {
+ return (
+
+ {typeof props.title === "string" ? props.title : "?"}
+ {props.children}
+
+ );
+}
+
+export function page(names: string[]) {
+ const extra = { id: "list", role: "list" };
+ return (
+ <>
+
+
+ {names.map((name, index) => (
+ -
+ {name}
+
+ ))}
+
+
+ fallback
+
+ two {"children"}
+
+ {...names}
+
+ >
+ );
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/render.ts b/tests/corpus/6824-jsx-automatic-runtime/render.ts
new file mode 100644
index 000000000..6f5f1e3c9
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/render.ts
@@ -0,0 +1,15 @@
+// Renders elements to HTML-like text.
+import type { Element } from "#jsx/jsx-runtime";
+
+export function render(value: unknown): string {
+ if (value === null || value === undefined || typeof value === "boolean") return "";
+ if (typeof value === "string" || typeof value === "number") return String(value);
+ if (Array.isArray(value)) return value.map(render).join("");
+ const element = value as Element;
+ const { children, ...rest } = element.props;
+ const inner = render(children);
+ if (element.type === "#fragment") return inner;
+ let attributes = element.key === undefined ? "" : ` key="${element.key}"`;
+ for (const [name, item] of Object.entries(rest)) attributes += ` ${name}=${JSON.stringify(item)}`;
+ return `<${element.type}${attributes}>${inner}${element.type}>`;
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/runtime/index.ts b/tests/corpus/6824-jsx-automatic-runtime/runtime/index.ts
new file mode 100644
index 000000000..c7809cdc0
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/runtime/index.ts
@@ -0,0 +1,12 @@
+// The import source's createElement: TypeScript calls it for an element
+// whose key follows a spread of props.
+import { calls, type Element, type Props } from "./jsx-runtime.js";
+
+export function createElement(type: string, props: Props | null, ...children: unknown[]): Element {
+ calls.push(`createElement:${type}`);
+ const all: Props = { ...props };
+ if (children.length > 0) all["children"] = children.length === 1 ? children[0] : children;
+ const key = all["key"];
+ delete all["key"];
+ return { type, props: all, key: key === undefined ? undefined : String(key) };
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/runtime/jsx-runtime.ts b/tests/corpus/6824-jsx-automatic-runtime/runtime/jsx-runtime.ts
new file mode 100644
index 000000000..185aafbd7
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/runtime/jsx-runtime.ts
@@ -0,0 +1,33 @@
+// The automatic runtime: jsx, jsxs and Fragment, with children in props.
+export type Props = { [name: string]: unknown };
+export interface Element {
+ type: string;
+ props: Props;
+ key: string | undefined;
+}
+export type Component = (props: Props) => Element;
+
+export const Fragment = "#fragment";
+export const calls: string[] = [];
+
+function element(kind: string, type: string | Component, props: Props, key?: unknown): Element {
+ const name = typeof type === "function" ? "component" : type;
+ calls.push(`${kind}:${name}${key === undefined ? "" : `#${String(key)}`}`);
+ if (typeof type === "function") return type(props);
+ return { type, props, key: key === undefined ? undefined : String(key) };
+}
+
+export function jsx(type: string | Component, props: Props, key?: unknown): Element {
+ return element("jsx", type, props, key);
+}
+
+export function jsxs(type: string | Component, props: Props, key?: unknown): Element {
+ return element("jsxs", type, props, key);
+}
+
+export namespace JSX {
+ export type Element = import("./jsx-runtime.js").Element;
+ export interface IntrinsicElements {
+ [name: string]: object;
+ }
+}
diff --git a/tests/corpus/6824-jsx-automatic-runtime/tsconfig.json b/tests/corpus/6824-jsx-automatic-runtime/tsconfig.json
new file mode 100644
index 000000000..2e5b6aee7
--- /dev/null
+++ b/tests/corpus/6824-jsx-automatic-runtime/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "module": "nodenext",
+ "jsx": "react-jsx",
+ "jsxImportSource": "#jsx"
+ }
+}
diff --git a/tests/corpus/6825-jsx-automatic-development/main.ts b/tests/corpus/6825-jsx-automatic-development/main.ts
new file mode 100644
index 000000000..9ff046128
--- /dev/null
+++ b/tests/corpus/6825-jsx-automatic-development/main.ts
@@ -0,0 +1,5 @@
+// @tsc-jsx
+// JSX with react-jsxdev: jsxDEV gets the key, static children, the source position and `this`.
+import { views } from "./view.js";
+
+for (const view of views()) console.log(view);
diff --git a/tests/corpus/6825-jsx-automatic-development/package.json b/tests/corpus/6825-jsx-automatic-development/package.json
new file mode 100644
index 000000000..cbd76bd4f
--- /dev/null
+++ b/tests/corpus/6825-jsx-automatic-development/package.json
@@ -0,0 +1,6 @@
+{
+ "type": "module",
+ "imports": {
+ "#dev/jsx-dev-runtime": "./runtime/jsx-dev-runtime.ts"
+ }
+}
diff --git a/tests/corpus/6825-jsx-automatic-development/runtime/jsx-dev-runtime.ts b/tests/corpus/6825-jsx-automatic-development/runtime/jsx-dev-runtime.ts
new file mode 100644
index 000000000..f4e3aea4b
--- /dev/null
+++ b/tests/corpus/6825-jsx-automatic-development/runtime/jsx-dev-runtime.ts
@@ -0,0 +1,31 @@
+// The development runtime: jsxDEV receives the key, whether the children
+// are static, the element's source position and the calling `this`.
+export const Fragment = "#fragment";
+
+interface Source {
+ fileName: string;
+ lineNumber: number;
+ columnNumber: number;
+}
+
+export function jsxDEV(
+ type: string | ((props: { [name: string]: unknown }) => string),
+ props: { [name: string]: unknown },
+ key: unknown,
+ isStatic: boolean,
+ source: Source,
+ self: unknown,
+): string {
+ const file = source.fileName.slice(source.fileName.lastIndexOf("/") + 1);
+ const where = `${file}:${source.lineNumber}:${source.columnNumber}`;
+ const owner = self === undefined ? "none" : typeof self;
+ const name = typeof type === "function" ? type(props) : type;
+ return `${name}{key=${String(key)} static=${isStatic} at=${where} self=${owner}}`;
+}
+
+export namespace JSX {
+ export type Element = string;
+ export interface IntrinsicElements {
+ [name: string]: object;
+ }
+}
diff --git a/tests/corpus/6825-jsx-automatic-development/tsconfig.json b/tests/corpus/6825-jsx-automatic-development/tsconfig.json
new file mode 100644
index 000000000..e1e88d270
--- /dev/null
+++ b/tests/corpus/6825-jsx-automatic-development/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "strict": true,
+ "module": "nodenext",
+ "jsx": "react-jsxdev",
+ "jsxImportSource": "#dev"
+ }
+}
diff --git a/tests/corpus/6825-jsx-automatic-development/view.tsx b/tests/corpus/6825-jsx-automatic-development/view.tsx
new file mode 100644
index 000000000..05fdc7b32
--- /dev/null
+++ b/tests/corpus/6825-jsx-automatic-development/view.tsx
@@ -0,0 +1,21 @@
+function Leaf() {
+ return "leaf";
+}
+
+export class Panel {
+ title = "panel";
+ render() {
+ return {1}{2}
;
+ }
+}
+
+export function views(): string[] {
+ return [
+ one
,
+ ,
+ <>
+
+ >,
+ new Panel().render(),
+ ];
+}
diff --git a/tests/corpus/6870-error-constructor-values.js b/tests/corpus/6870-error-constructor-values.js
new file mode 100644
index 000000000..79c970914
--- /dev/null
+++ b/tests/corpus/6870-error-constructor-values.js
@@ -0,0 +1,65 @@
+// The error constructors as values in JavaScript: an error's `constructor`
+// is the same function the global names, whose name, length, typeof and
+// String() are Node's, for errors the program throws, errors the runtime
+// throws, and errors read back from catch bindings and containers.
+function describe(label, error) {
+ const ctor = error.constructor;
+ console.log(label, error.name, error.constructor.name, typeof ctor, ctor.length, String(ctor));
+}
+
+describe('Error', new Error('e'));
+describe('TypeError', new TypeError('t'));
+describe('RangeError', new RangeError('r'));
+describe('SyntaxError', new SyntaxError('s'));
+describe('ReferenceError', new ReferenceError('f'));
+describe('EvalError', new EvalError('v'));
+describe('URIError', new URIError('u'));
+describe('AggregateError', new AggregateError([], 'a'));
+
+const thrown = [
+ () => JSON.parse('{'),
+ () => {
+ const holder = JSON.parse('{"value":null}');
+ return holder.value.missing;
+ },
+ () => new Array(-1),
+ () => decodeURIComponent('%'),
+ () => {
+ throw new TypeError('own');
+ },
+];
+const constructors = { SyntaxError, TypeError, RangeError, URIError };
+const caught = [];
+for (const run of thrown) {
+ try {
+ run();
+ } catch (error) {
+ caught.push(error);
+ console.log('caught', error.constructor.name, error.constructor === constructors[error.name]);
+ }
+}
+
+const byName = new Map();
+for (const error of caught) byName.set(error.constructor.name, (byName.get(error.constructor.name) ?? 0) + 1);
+console.log('kinds', JSON.stringify([...byName]));
+
+console.log('identity', new TypeError('x').constructor === TypeError, new RangeError('x').constructor === TypeError);
+console.log('globals', Error.name, TypeError.length, AggregateError.length, typeof RangeError, String(SyntaxError));
+
+const renamed = new TypeError('x');
+renamed.name = 'Custom';
+console.log('renamed', renamed.name, renamed.constructor.name);
+
+class ParseFailure extends SyntaxError {}
+const failure = new ParseFailure('p');
+console.log('subclass', failure.name, failure.constructor.name, failure instanceof SyntaxError);
+
+// Effect's isStackTraceLimitWritable reflects on Error as a value.
+const limit = Object.getOwnPropertyDescriptor(Error, 'stackTraceLimit');
+console.log('stackTraceLimit', JSON.stringify(limit), Object.hasOwn(limit, 'writable') ? limit.writable === true : limit.set !== undefined);
+Error.stackTraceLimit = 4;
+console.log('stackTraceLimit now', Object.getOwnPropertyDescriptor(Error, 'stackTraceLimit').value);
+Error.stackTraceLimit = 10;
+for (const ctor of [Error, TypeError, AggregateError, Object, Map]) {
+ console.log('extensible', ctor.name, Object.isExtensible(ctor), Object.isFrozen(ctor), Object.isSealed(ctor));
+}
diff --git a/tests/corpus/6871-worker-data-destructuring.cjs b/tests/corpus/6871-worker-data-destructuring.cjs
new file mode 100644
index 000000000..63ae789bd
--- /dev/null
+++ b/tests/corpus/6871-worker-data-destructuring.cjs
@@ -0,0 +1,30 @@
+// Destructuring the members of a worker's workerData in JavaScript: plain,
+// renamed, defaulted, nested and rest bindings read the data the worker was
+// started with, as reading `workerData.` does.
+'use strict';
+const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
+
+if (isMainThread) {
+ const sab = new SharedArrayBuffer(8);
+ const data = { name: 'job-7', size: 3, options: { verbose: true, tags: ['a', 'b'] }, counters: new Int32Array(sab), extra: 1, more: 'x' };
+ const worker = new Worker(__filename, { workerData: data });
+ worker.on('message', (lines) => {
+ for (const line of lines) console.log(line);
+ console.log('main sees', new Int32Array(sab)[0]);
+ });
+} else {
+ const lines = [];
+ const { name, size } = workerData;
+ lines.push(`plain ${name} ${size}`);
+ const { name: jobName, missing = 'fallback', size: count = 10 } = workerData;
+ lines.push(`renamed ${jobName} ${missing} ${count}`);
+ const {
+ options: { verbose, tags: [firstTag, ...otherTags] },
+ } = workerData;
+ lines.push(`nested ${verbose} ${firstTag} ${JSON.stringify(otherTags)}`);
+ const { name: ignored, size: alsoIgnored, options, counters, ...rest } = workerData;
+ lines.push(`rest ${JSON.stringify(rest)} ${typeof options} ${ignored === name && alsoIgnored === size}`);
+ counters[0] = 41;
+ counters[0] += 1;
+ parentPort.postMessage(lines);
+}
diff --git a/tests/corpus/7080-crypto-ed25519.mjs b/tests/corpus/7080-crypto-ed25519.mjs
new file mode 100644
index 000000000..355d98225
--- /dev/null
+++ b/tests/corpus/7080-crypto-ed25519.mjs
@@ -0,0 +1,34 @@
+// Ed25519 KeyObjects in node:crypto: the RFC 8032 test 1 key through
+// PKCS#8, SubjectPublicKeyInfo and JWK export and import, its deterministic
+// signatures from crypto.sign and crypto.verify, generateKeyPairSync, the
+// two-argument generateKeyPair callback, and Node's errors for digests,
+// Sign objects and the RSA and EC key encodings.
+import * as c from "node:crypto";
+const t = (l, f) => { try { const v = f(); console.log(l, "=>", typeof v === "object" && v !== null && !Buffer.isBuffer(v) ? JSON.stringify(v) : Buffer.isBuffer(v) ? v.toString("hex") : String(v).slice(0, 200)); } catch (e) { console.log(l, "!!", e.name, e.code, e.message); } };
+const seed = Buffer.from("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", "hex");
+const der = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
+const k = c.createPrivateKey({ key: der, format: "der", type: "pkcs8" });
+const p = c.createPublicKey(k);
+t("type", () => [k.type, k.asymmetricKeyType, JSON.stringify(k.asymmetricKeyDetails), p.type].join());
+t("pkcs8 pem", () => k.export({ type: "pkcs8", format: "pem" }));
+t("spki pem", () => p.export({ type: "spki", format: "pem" }));
+t("spki der", () => p.export({ type: "spki", format: "der" }));
+t("jwk priv", () => k.export({ format: "jwk" }));
+t("jwk pub", () => p.export({ format: "jwk" }));
+t("sig empty", () => c.sign(null, Buffer.alloc(0), k));
+t("sig abc", () => c.sign(undefined, Buffer.from("abc"), k));
+t("verify", () => c.verify(null, Buffer.from("abc"), p, c.sign(null, Buffer.from("abc"), k)));
+t("verify bad", () => c.verify(null, Buffer.from("abd"), p, c.sign(null, Buffer.from("abc"), k)));
+t("sign sha256", () => c.sign("sha256", Buffer.from("abc"), k));
+t("createSign", () => c.createSign("sha256").update("x").sign(k));
+t("pkcs1 export", () => k.export({ type: "pkcs1", format: "pem" }));
+t("sec1 export", () => k.export({ type: "sec1", format: "pem" }));
+t("gen cb form", () => { c.generateKeyPair("ed25519", (e, pub, priv) => console.log("cb", e, pub.asymmetricKeyType, priv.type)); return "scheduled"; });
+t("gen", () => { const g = c.generateKeyPairSync("ed25519"); return [g.publicKey.asymmetricKeyType, Object.keys(g).join()].join(); });
+t("gen opts", () => { const g = c.generateKeyPairSync("ed25519", { publicKeyEncoding: { type: "spki", format: "der" } }); return g.publicKey.length; });
+t("jwk import", () => c.createPrivateKey({ key: k.export({ format: "jwk" }), format: "jwk" }).equals(k));
+t("jwk pub import", () => c.createPublicKey({ key: p.export({ format: "jwk" }), format: "jwk" }).equals(p));
+t("dsaEncoding", () => c.sign(null, Buffer.from("x"), { key: k, dsaEncoding: "ieee-p1363" }).length);
+t("verify short sig", () => c.verify(null, Buffer.from("x"), p, Buffer.alloc(10)));
+t("pem parse", () => c.createPrivateKey(k.export({ type: "pkcs8", format: "pem" })).equals(k));
+t("equals ec", () => k.equals(c.generateKeyPairSync("ec", { namedCurve: "P-256" }).privateKey));
diff --git a/tests/corpus/7081-webcrypto-ed25519.mjs b/tests/corpus/7081-webcrypto-ed25519.mjs
new file mode 100644
index 000000000..5cf7728f1
--- /dev/null
+++ b/tests/corpus/7081-webcrypto-ed25519.mjs
@@ -0,0 +1,39 @@
+// WebCrypto Ed25519: the RFC 8032 test 1 key imported from pkcs8, jwk and
+// raw, its deterministic signatures, verify, generateKey, every export
+// format, and Node's DOMExceptions for usages, raw private export, short
+// raw keys and mismatched JWK members.
+
+const t = async (label, fn) => { try { const v = await fn(); console.log(label, "=>", v instanceof ArrayBuffer ? Buffer.from(v).toString("hex") : typeof v === "object" && v !== null ? JSON.stringify(v) : v); } catch (e) { console.log(label, "!!", e.name, e.code, e.message); } };
+const seed = Buffer.from("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", "hex");
+const pkcs8 = Buffer.concat([Buffer.from("302e020100300506032b657004220420", "hex"), seed]);
+const priv = await crypto.subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, true, ["sign"]);
+await t("algorithm", () => [JSON.stringify(priv.algorithm), priv.type, priv.usages.join(), priv.extractable].join(" "));
+await t("sign empty", () => crypto.subtle.sign("Ed25519", priv, new Uint8Array(0)));
+await t("sign abc", () => crypto.subtle.sign({ name: "Ed25519" }, priv, new TextEncoder().encode("abc")));
+await t("jwk priv", () => crypto.subtle.exportKey("jwk", priv));
+await t("pkcs8 back", async () => Buffer.from(await crypto.subtle.exportKey("pkcs8", priv)).equals(pkcs8));
+const jwk = await crypto.subtle.exportKey("jwk", priv);
+const pubJwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x };
+const pub = await crypto.subtle.importKey("jwk", pubJwk, "Ed25519", true, ["verify"]);
+await t("raw pub", () => crypto.subtle.exportKey("raw", pub));
+await t("spki pub", () => crypto.subtle.exportKey("spki", pub));
+await t("jwk pub", () => crypto.subtle.exportKey("jwk", pub));
+const sig = await crypto.subtle.sign("Ed25519", priv, new TextEncoder().encode("abc"));
+await t("verify", () => crypto.subtle.verify("Ed25519", pub, sig, new TextEncoder().encode("abc")));
+await t("verify bad", () => crypto.subtle.verify("Ed25519", pub, sig, new TextEncoder().encode("abd")));
+await t("verify short", () => crypto.subtle.verify("Ed25519", pub, new Uint8Array(10), new TextEncoder().encode("abc")));
+const rawImported = await crypto.subtle.importKey("raw", await crypto.subtle.exportKey("raw", pub), { name: "Ed25519" }, false, ["verify"]);
+await t("raw import verify", () => crypto.subtle.verify("Ed25519", rawImported, sig, new TextEncoder().encode("abc")));
+const pair = await crypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
+await t("generated", async () => [pair.privateKey.usages.join(), pair.publicKey.usages.join(), pair.publicKey.extractable, (await crypto.subtle.exportKey("raw", pair.publicKey)).byteLength, await crypto.subtle.verify("Ed25519", pair.publicKey, await crypto.subtle.sign("Ed25519", pair.privateKey, new Uint8Array([1])), new Uint8Array([1]))].join(" "));
+await t("raw private", () => crypto.subtle.exportKey("raw", pair.privateKey));
+await t("bad usage", () => crypto.subtle.generateKey({ name: "Ed25519" }, true, ["encrypt"]));
+await t("verify only", () => crypto.subtle.generateKey("Ed25519", true, ["verify"]));
+await t("raw short", () => crypto.subtle.importKey("raw", new Uint8Array(31), { name: "Ed25519" }, true, ["verify"]));
+await t("raw sign usage", () => crypto.subtle.importKey("raw", new Uint8Array(32), "Ed25519", true, ["sign"]));
+await t("jwk alg EdDSA", async () => (await crypto.subtle.importKey("jwk", { ...pubJwk, alg: "EdDSA" }, "Ed25519", true, ["verify"])).type);
+await t("jwk alg bad", () => crypto.subtle.importKey("jwk", { ...pubJwk, alg: "RS256" }, "Ed25519", true, ["verify"]));
+await t("jwk crv bad", () => crypto.subtle.importKey("jwk", { ...pubJwk, crv: "Ed448" }, "Ed25519", true, ["verify"]));
+await t("jwk kty bad", () => crypto.subtle.importKey("jwk", { ...pubJwk, kty: "EC" }, "Ed25519", true, ["verify"]));
+await t("ecdsa key as ed", async () => { const e = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]); return crypto.subtle.importKey("spki", await crypto.subtle.exportKey("spki", e.publicKey), "Ed25519", true, ["verify"]); });
+await t("sign wrong alg", () => crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, priv, new Uint8Array(1)));
diff --git a/tests/corpus/7082-crypto-key-objects.ts b/tests/corpus/7082-crypto-key-objects.ts
new file mode 100644
index 000000000..816dd6be0
--- /dev/null
+++ b/tests/corpus/7082-crypto-key-objects.ts
@@ -0,0 +1,132 @@
+// Private and public KeyObjects: createPrivateKey and createPublicKey from
+// PEM, DER, JWK, a private KeyObject and a certificate; the type,
+// asymmetricKeyType and asymmetricKeyDetails getters; export() as PKCS#1,
+// SEC1, PKCS#8, SubjectPublicKeyInfo, DER and JWK; equals(); instanceof;
+// and Node's errors for invalid options and inputs, including a private or
+// public key passed to createHmac.
+import * as crypto from "node:crypto";
+
+const RSA = `-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDWei4BREJm+4C6u/+F4NI7kTOQ9gf/a+Ecd2SZxfpXRtXT0QVN
+MAI1uQNmB4abYzvd1G2V2XKsTJFJcJWibjAz56UxtxxggTpSAalFZYoPm6HfabwJ
+b/YWh0K0OrNFvdkeXLLMiHQsxmIQ2lrFoycQEpZ4/yxjLe6LQF+L79k2ywIDAQAB
+AoGAMWS/9ZC3zim7ssWbCkUFhaxh+M+AdgVtOXYRmAxtI+5vTjHEC8uzppWuP7Aa
+Au0um17RwgHrE90qVIR2IYtI1C9K9RLUNlKKVPqnSSmr0qkXR246R/Zd8eho1XSS
+MX6KmnbmRijUx9Rw1AbD2ZmYCrXsPf2HsCdH+SGQ1ZTunnECQQD090dTA21Jyu/b
+ADglqgixJCPIoc6qpxst+W1LreIo4UvkSmgnRYvsKDlM7FNI+6f7igI625KXIAnl
+xVlmGLXDAkEA4CNVeP07fmQUBs1aPQ9LCOvN42SV6S8Fjbf4XHIYG5Kp48/98oJj
+1pFsXNa8nn8fmN9zFw8yM/yd22iZdueCWQJAWMja//po/OrhniVpNDV5awKnOYtz
+xSRF1JbnhxthseFeSUKylzE8sn4trnSlBvm2+5MclcqJSd7h13Z8nWVtUwJAZ0SA
+Ve0+ov4TKAsnH2jTZnnWc4VdeIFcqq3vdRBwQQvy0G59D2W6ABsidSfO4auqcDHL
+gf1TYRZr2LI5usnxiQJAGt3e0QC0q0WN4p49YhyXXVuHccx79RFgfN7V1lkAyYuL
+2bPV41mEtD/AbSrObtqqvOaWZjwSr8JCyPdPZZAIMw==
+-----END RSA PRIVATE KEY-----
+`;
+const EC = `-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILpVcXOEZQXpH/q3ec3AsLg4x4Bld6uZLqDC56OYKQK4oAoGCCqGSM49
+AwEHoUQDQgAE0gwi0a/4kuVxjsO3vw79rT3QSxnPY7sooVlI+ENq35D7meWZv/dp
+FTGFte7Bd8lFxaZ10dMo0tl++AOtt7umPg==
+-----END EC PRIVATE KEY-----
+`;
+const EC384 = `-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDDer7W8pQD96zdS8UWxg34nMTxqREszwP6niYYOdw75LHPmHFKWeMKW
+dmG6d3n4bRugBwYFK4EEACKhZANiAATIQKDRVJ501CTQtbK2IslxdO8Oma2ZU4ee
+GRuQFg6ajhKXa0iCU5o9rpdObfCecyiYApdtVpahD2SwPua5CZyq/0jUn9frwKtB
+uEeB/3T/yYRCD4zGUc7TtUDh8c7Gt5c=
+-----END EC PRIVATE KEY-----
+`;
+const CERT = `-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIPAPAN+s4AESIzRFVmd4iZMA0GCSqGSIb3DQEBDAUAMIGc
+MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu
+IEZyYW5jaXNjbzEUMBIGA1UECgwLRXhhbXBsZSBPcmcxETAPBgNVBAsMCERldiBU
+ZWFtMRgwFgYDVQQDDA9FeGFtcGxlIFJvb3QgQ0ExHTAbBgkqhkiG9w0BCQEWDmNh
+QGV4YW1wbGUuY29tMB4XDTI2MTAyMTE2MzM1MloXDTM2MTAxODE2MzM1MlowJzER
+MA8GA1UECgwITGVhZiBPcmcxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49
+AgEGCCqGSM49AwEHA0IABJVfVy9EH/VHQuRqpFFnXk0F00ncDht6APgVPDoybK8H
+PlcM4T5H1yAOMHnlH29LK1jtMqU1aAOiiQRzPdCvqoujggE3MIIBMzAJBgNVHRME
+AjAAMGgGA1UdEQRhMF+CCWxvY2FsaG9zdIINKi5leGFtcGxlLmNvbYcEfwAAAYcQ
+AAAAAAAAAAAAAAAAAAAAAYERYWRtaW5AZXhhbXBsZS5jb22GGGh0dHBzOi8vZXhh
+bXBsZS5jb20vcGF0aDBdBggrBgEFBQcBAQRRME8wIwYIKwYBBQUHMAGGF2h0dHA6
+Ly9vY3NwLmV4YW1wbGUuY29tMCgGCCsGAQUFBzAChhxodHRwOi8vY2EuZXhhbXBs
+ZS5jb20vY2EuY3J0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV
+HQ4EFgQUdKIx6ULFLPNP/kzwUFjSa5D19RswHwYDVR0jBBgwFoAUP+yQyItpNYEl
+s26Jfv+NnciGaK8wDQYJKoZIhvcNAQEMBQADggEBABmQ8f2VsXNaMM9X1MEhi27D
+LjwcOaaGEINNZq7xu/HL6UljMb1Odsuz66DkiH3Exd3MrWy6OlOpJJF0VzsqMsoD
+0CRBxXF28o6Jil3vOGVwlYNmRidpkAkj4VyWDvv8FCS9VbsBtXaSHwu9wcCwePE0
+cGJRfTKoZ6KJ8vQ57DBEZ/ZszuhehOZYqrTmoUdjZ123kXdxQvK+ALn4SlIXHBhN
+9unhd+8i00EmkXO/REw8paMcMVVGR+ioOEjyNHqZo6vkRqgpKggn1Yq8cTTDOMnS
+P1o42EQZLlbuvx0kqInBqwqjDx4nZiPaifoDc9BF6iZPBvm8dhrEfghfB1tkNk0=
+-----END CERTIFICATE-----
+`;
+
+function attempt(label: string, run: () => unknown): void {
+ try {
+ const value = run();
+ console.log(label, typeof value === "string" ? JSON.stringify(value) : value);
+ } catch (e) {
+ const err = e as NodeJS.ErrnoException;
+ console.log(label, "throws", err.name, err.code, err.message);
+ }
+}
+
+const rsa = crypto.createPrivateKey(RSA);
+const ec = crypto.createPrivateKey(Buffer.from(EC));
+const ec384 = crypto.createPrivateKey({ key: EC384, format: "pem" });
+const rsaPublic = crypto.createPublicKey(rsa);
+const ecPublic = crypto.createPublicKey(EC);
+const certPublic = crypto.createPublicKey(CERT);
+const keys: [string, crypto.KeyObject][] = [
+ ["rsa", rsa],
+ ["ec", ec],
+ ["ec384", ec384],
+ ["rsaPublic", rsaPublic],
+ ["ecPublic", ecPublic],
+ ["certPublic", certPublic],
+];
+for (const [label, key] of keys) {
+ console.log(label, key.type, key.asymmetricKeyType, key.asymmetricKeyDetails, key instanceof crypto.KeyObject);
+}
+
+attempt("pkcs1", () => rsa.export({ type: "pkcs1", format: "pem" }) === RSA + "\n");
+attempt("sec1", () => ec.export({ type: "sec1", format: "pem" }) === EC + "\n");
+attempt("rsa pkcs8", () => rsa.export({ type: "pkcs8", format: "pem" }));
+attempt("ec pkcs8", () => ec.export({ type: "pkcs8", format: "pem" }));
+attempt("rsa spki", () => rsaPublic.export({ type: "spki", format: "pem" }));
+attempt("rsa public pkcs1", () => rsaPublic.export({ type: "pkcs1", format: "pem" }));
+attempt("ec spki", () => ecPublic.export({ type: "spki", format: "pem" }));
+attempt("cert spki", () => certPublic.export({ type: "spki", format: "pem" }));
+attempt("der", () => (rsa.export({ type: "pkcs8", format: "der" }) as Buffer).toString("base64"));
+attempt("ec jwk", () => JSON.stringify(ec.export({ format: "jwk" })));
+attempt("ec384 public jwk", () => JSON.stringify(crypto.createPublicKey(ec384).export({ format: "jwk" })));
+attempt("rsa jwk", () => JSON.stringify(rsa.export({ format: "jwk" })));
+attempt("rsa public jwk", () => JSON.stringify(rsaPublic.export({ format: "jwk" })));
+
+// Round trips through DER and JWK.
+const der = rsa.export({ type: "pkcs1", format: "der" }) as Buffer;
+attempt("from der", () => crypto.createPrivateKey({ key: der, format: "der", type: "pkcs1" }).equals(rsa));
+const spki = ecPublic.export({ type: "spki", format: "der" }) as Buffer;
+attempt("public from der", () => crypto.createPublicKey({ key: spki, format: "der", type: "spki" }).equals(ecPublic));
+attempt("from jwk", () => crypto.createPrivateKey({ key: ec.export({ format: "jwk" }), format: "jwk" }).equals(ec));
+attempt("rsa from jwk", () => crypto.createPrivateKey({ key: rsa.export({ format: "jwk" }), format: "jwk" }).equals(rsa));
+attempt("public from jwk", () => crypto.createPublicKey({ key: rsaPublic.export({ format: "jwk" }), format: "jwk" }).equals(rsaPublic));
+attempt("pkcs8 parse", () => crypto.createPrivateKey(ec.export({ type: "pkcs8", format: "pem" })).equals(ec));
+attempt("equals", () => [rsaPublic.equals(crypto.createPublicKey(RSA)), ec.equals(ecPublic), ec.equals(ec384)].join());
+attempt("tag", () => Object.prototype.toString.call(rsa));
+
+// Errors.
+attempt("export no options", () => rsa.export());
+attempt("export no format", () => rsa.export({} as crypto.AsymmetricKeyExportOptions<"pem">));
+attempt("export no type", () => rsa.export({ format: "pem" } as crypto.AsymmetricKeyExportOptions<"pem">));
+attempt("export spki private", () => rsa.export({ type: "spki", format: "pem" }));
+attempt("export sec1 rsa", () => rsa.export({ type: "sec1", format: "pem" }));
+attempt("export pkcs1 ec", () => ec.export({ type: "pkcs1", format: "der" }));
+attempt("export pkcs8 public", () => ecPublic.export({ type: "pkcs8", format: "pem" }));
+attempt("private from public pem", () => crypto.createPrivateKey(ecPublic.export({ type: "spki", format: "pem" }) as string));
+attempt("private from garbage", () => crypto.createPrivateKey("not a key"));
+attempt("public from public", () => crypto.createPublicKey(ecPublic));
+attempt("der without type", () => crypto.createPrivateKey({ key: der, format: "der" } as crypto.PrivateKeyInput));
+attempt("hmac private key", () => crypto.createHmac("sha256", rsa));
+attempt("hmac public key", () => crypto.createHmac("sha256", ecPublic));
+// Last: OpenSSL keeps an error queued after comparing keys of different
+// types, which a later call in Node can report.
+attempt("equals other type", () => rsa.equals(ec));
diff --git a/tests/corpus/7083-crypto-sign-verify.ts b/tests/corpus/7083-crypto-sign-verify.ts
new file mode 100644
index 000000000..4f6b86e72
--- /dev/null
+++ b/tests/corpus/7083-crypto-sign-verify.ts
@@ -0,0 +1,88 @@
+// crypto.sign and crypto.verify, and the Sign and Verify objects: RSA
+// PKCS#1 v1.5 signatures (deterministic, so they compare with Node), RSA-PSS,
+// ECDSA in DER and IEEE P1363 encodings, keys given as KeyObjects, PEM and
+// option objects, output and signature encodings, and Node's errors.
+import * as crypto from "node:crypto";
+
+const RSA = `-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQDWei4BREJm+4C6u/+F4NI7kTOQ9gf/a+Ecd2SZxfpXRtXT0QVN
+MAI1uQNmB4abYzvd1G2V2XKsTJFJcJWibjAz56UxtxxggTpSAalFZYoPm6HfabwJ
+b/YWh0K0OrNFvdkeXLLMiHQsxmIQ2lrFoycQEpZ4/yxjLe6LQF+L79k2ywIDAQAB
+AoGAMWS/9ZC3zim7ssWbCkUFhaxh+M+AdgVtOXYRmAxtI+5vTjHEC8uzppWuP7Aa
+Au0um17RwgHrE90qVIR2IYtI1C9K9RLUNlKKVPqnSSmr0qkXR246R/Zd8eho1XSS
+MX6KmnbmRijUx9Rw1AbD2ZmYCrXsPf2HsCdH+SGQ1ZTunnECQQD090dTA21Jyu/b
+ADglqgixJCPIoc6qpxst+W1LreIo4UvkSmgnRYvsKDlM7FNI+6f7igI625KXIAnl
+xVlmGLXDAkEA4CNVeP07fmQUBs1aPQ9LCOvN42SV6S8Fjbf4XHIYG5Kp48/98oJj
+1pFsXNa8nn8fmN9zFw8yM/yd22iZdueCWQJAWMja//po/OrhniVpNDV5awKnOYtz
+xSRF1JbnhxthseFeSUKylzE8sn4trnSlBvm2+5MclcqJSd7h13Z8nWVtUwJAZ0SA
+Ve0+ov4TKAsnH2jTZnnWc4VdeIFcqq3vdRBwQQvy0G59D2W6ABsidSfO4auqcDHL
+gf1TYRZr2LI5usnxiQJAGt3e0QC0q0WN4p49YhyXXVuHccx79RFgfN7V1lkAyYuL
+2bPV41mEtD/AbSrObtqqvOaWZjwSr8JCyPdPZZAIMw==
+-----END RSA PRIVATE KEY-----
+`;
+const EC = `-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILpVcXOEZQXpH/q3ec3AsLg4x4Bld6uZLqDC56OYKQK4oAoGCCqGSM49
+AwEHoUQDQgAE0gwi0a/4kuVxjsO3vw79rT3QSxnPY7sooVlI+ENq35D7meWZv/dp
+FTGFte7Bd8lFxaZ10dMo0tl++AOtt7umPg==
+-----END EC PRIVATE KEY-----
+`;
+
+function attempt(label: string, run: () => unknown): void {
+ try {
+ console.log(label, run());
+ } catch (e) {
+ const err = e as NodeJS.ErrnoException;
+ console.log(label, "throws", err.name, err.code, err.message);
+ }
+}
+
+const rsa = crypto.createPrivateKey(RSA);
+const rsaPublic = crypto.createPublicKey(rsa);
+const ec = crypto.createPrivateKey(EC);
+const ecPublic = crypto.createPublicKey(ec);
+const data = Buffer.from("signed message");
+
+for (const digest of ["sha1", "sha224", "sha256", "SHA384", "RSA-SHA512", "sha256WithRSAEncryption"]) {
+ const signature = crypto.sign(digest, data, rsa);
+ console.log(digest, signature.toString("base64"), crypto.verify(digest, data, rsaPublic, signature));
+}
+attempt("null digest", () => crypto.sign(null, data, RSA).equals(crypto.sign("sha256", data, rsa)));
+attempt("private key verifies", () => crypto.verify("sha256", data, RSA, crypto.sign("sha256", data, rsa)));
+attempt("wrong data", () => crypto.verify("sha256", Buffer.from("other"), rsaPublic, crypto.sign("sha256", data, rsa)));
+
+const pss = crypto.sign("sha256", data, { key: rsa, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 32 });
+attempt("pss", () => [pss.length, crypto.verify("sha256", data, { key: rsaPublic, padding: crypto.constants.RSA_PKCS1_PSS_PADDING }, pss)].join());
+attempt("pss as pkcs1", () => crypto.verify("sha256", data, rsaPublic, pss));
+attempt("pss digest salt", () =>
+ crypto.verify("sha256", data, { key: rsaPublic, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST }, pss),
+);
+
+const der = crypto.sign("sha256", data, ec);
+attempt("ecdsa der", () => [der[0], crypto.verify("sha256", data, ecPublic, der)].join());
+const p1363 = crypto.sign("sha384", data, { key: EC, dsaEncoding: "ieee-p1363" });
+attempt("ecdsa p1363", () => [p1363.length, crypto.verify("sha384", data, { key: ecPublic, dsaEncoding: "ieee-p1363" }, p1363)].join());
+attempt("p1363 as der", () => crypto.verify("sha384", data, ecPublic, p1363));
+attempt("truncated", () => crypto.verify("sha256", data, ecPublic, der.subarray(0, 10)));
+
+const signer = crypto.createSign("SHA256");
+signer.update("signed ");
+signer.update(Buffer.from("message"));
+const hex = signer.sign(rsa, "hex");
+attempt("sign object", () => hex === crypto.sign("sha256", data, rsa).toString("hex"));
+attempt("sign again", () => signer.sign(rsa));
+attempt("update after sign", () => signer.update("more"));
+const verifier = crypto.createVerify("RSA-SHA256");
+verifier.update("signed message", "utf8");
+attempt("verify object", () => verifier.verify(RSA, Buffer.from(hex, "hex").toString("base64"), "base64"));
+attempt("verify again", () => verifier.verify(rsaPublic, hex, "hex"));
+const ecSigner = crypto.createSign("sha512").update("chained");
+const ecSignature = ecSigner.sign({ key: ec, dsaEncoding: "ieee-p1363" });
+attempt("chained", () => crypto.createVerify("sha512").update("chained").verify({ key: ecPublic, dsaEncoding: "ieee-p1363" }, ecSignature));
+
+attempt("invalid digest", () => crypto.sign("nope", data, rsa));
+const longDigest = "unknown-digest-".repeat(40);
+attempt("long digest", () => crypto.sign(longDigest, data, rsa));
+attempt("long verify digest", () => crypto.verify(longDigest, data, rsaPublic, Buffer.alloc(64)));
+attempt("public key signs", () => crypto.sign("sha256", data, rsaPublic));
+attempt("invalid createSign", () => crypto.createSign("nope"));
+attempt("invalid dsaEncoding", () => crypto.sign("sha256", data, { key: ec, dsaEncoding: "x" as "der" }));
diff --git a/tests/corpus/7084-crypto-key-pairs.cjs b/tests/corpus/7084-crypto-key-pairs.cjs
new file mode 100644
index 000000000..ca267d89f
--- /dev/null
+++ b/tests/corpus/7084-crypto-key-pairs.cjs
@@ -0,0 +1,78 @@
+// generateKeyPairSync and generateKeyPair for RSA and EC keys: KeyObject
+// results, PEM and DER encodings, curve names and NIST aliases, the
+// callback's later macrotask, and Node's errors for invalid types and
+// options.
+const crypto = require("node:crypto");
+
+function attempt(label, run) {
+ try {
+ console.log(label, run());
+ } catch (e) {
+ console.log(label, "throws", e.name, e.code, e.message);
+ }
+}
+
+const data = Buffer.from("key pair");
+for (const curve of ["prime256v1", "P-256", "secp384r1", "P-384", "secp521r1", "P-521", "secp256k1"]) {
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("ec", { namedCurve: curve });
+ const signature = crypto.sign("sha256", data, privateKey);
+ console.log(curve, privateKey.type, publicKey.type, privateKey.asymmetricKeyDetails, crypto.verify("sha256", data, publicKey, signature));
+}
+const rsa = crypto.generateKeyPairSync("rsa", { modulusLength: 1024 });
+console.log("rsa", rsa.privateKey.asymmetricKeyDetails, Object.keys(rsa));
+const exponent = crypto.generateKeyPairSync("rsa", { modulusLength: 1024, publicExponent: 3 });
+console.log("exponent", exponent.publicKey.asymmetricKeyDetails);
+const encoded = crypto.generateKeyPairSync("ec", {
+ namedCurve: "P-256",
+ publicKeyEncoding: { type: "spki", format: "pem" },
+ privateKeyEncoding: { type: "sec1", format: "der" },
+});
+console.log("encoded", typeof encoded.publicKey, encoded.publicKey.split("\n")[0], Buffer.isBuffer(encoded.privateKey));
+const reparsed = crypto.createPrivateKey({ key: encoded.privateKey, format: "der", type: "sec1" });
+console.log("reparsed", crypto.createPublicKey(reparsed).export({ type: "spki", format: "pem" }) === encoded.publicKey);
+const rsaPem = crypto.generateKeyPairSync("rsa", {
+ modulusLength: 1024,
+ publicKeyEncoding: { type: "pkcs1", format: "pem" },
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
+});
+console.log("rsa pem", rsaPem.publicKey.split("\n")[0], rsaPem.privateKey.split("\n")[0]);
+const jwk = crypto.generateKeyPairSync("ec", { namedCurve: "secp256k1", publicKeyEncoding: { format: "jwk" } });
+console.log("jwk", Object.keys(jwk.publicKey), jwk.publicKey.crv);
+
+attempt("no options", () => crypto.generateKeyPairSync("rsa"));
+attempt("no modulusLength", () => crypto.generateKeyPairSync("rsa", {}));
+attempt("string modulusLength", () => crypto.generateKeyPairSync("rsa", { modulusLength: "2048" }));
+attempt("negative modulusLength", () => crypto.generateKeyPairSync("rsa", { modulusLength: -1 }));
+attempt("unknown type", () => crypto.generateKeyPairSync("foo", {}));
+attempt("number type", () => crypto.generateKeyPairSync(5, {}));
+attempt("no curve", () => crypto.generateKeyPairSync("ec", {}));
+attempt("unknown curve", () => crypto.generateKeyPairSync("ec", { namedCurve: "nope" }));
+attempt("bad encoding format", () => crypto.generateKeyPairSync("ec", { namedCurve: "P-256", publicKeyEncoding: { type: "spki", format: "xml" } }));
+attempt("bad encoding type", () => crypto.generateKeyPairSync("ec", { namedCurve: "P-256", privateKeyEncoding: { type: "spki", format: "pem" } }));
+attempt("encoding not object", () => crypto.generateKeyPairSync("ec", { namedCurve: "P-256", publicKeyEncoding: "pem" }));
+// Error messages hold the whole text at any length, and received values
+// render as Node's validators print them.
+attempt("long type", () => crypto.generateKeyPairSync("unknown-type-".repeat(20), {}));
+attempt("long encoding type", () => crypto.generateKeyPairSync("ec", { namedCurve: "P-256", publicKeyEncoding: { type: "z".repeat(300), format: "pem" } }));
+attempt("quoted encoding type", () => crypto.generateKeyPairSync("ec", { namedCurve: "P-256", publicKeyEncoding: { type: "it's\n", format: "pem" } }));
+attempt("huge modulusLength", () => crypto.generateKeyPairSync("rsa", { modulusLength: 2 ** 40 }));
+attempt("fractional modulusLength", () => crypto.generateKeyPairSync("rsa", { modulusLength: 1.5 }));
+attempt("NaN modulusLength", () => crypto.generateKeyPairSync("rsa", { modulusLength: NaN }));
+const key = rsa.privateKey;
+attempt("long method", () => key.aMethodNameLongEnoughThatTheWholeMessageRunsPastOneHundredAndSixtyBytesOnceTheReceiverTextAndTheSuffixAreIncluded());
+attempt("getter", () => {
+ "use strict";
+ key.asymmetricKeyType = "ec";
+});
+
+let returned = false;
+crypto.generateKeyPair("ec", { namedCurve: "P-384" }, (err, publicKey, privateKey) => {
+ console.log("callback", returned, err, publicKey.type, privateKey.asymmetricKeyDetails);
+ crypto.generateKeyPair("rsa", { modulusLength: 1024 }, (err2, publicKey2) => {
+ console.log("rsa callback", err2, publicKey2.asymmetricKeyDetails.modulusLength);
+ });
+});
+process.nextTick(() => console.log("tick"));
+attempt("callback errors throw", () => crypto.generateKeyPair("ec", {}, () => console.log("never")));
+returned = true;
+console.log("returned");
diff --git a/tests/corpus/7085-webcrypto-ecdsa-rsa.ts b/tests/corpus/7085-webcrypto-ecdsa-rsa.ts
new file mode 100644
index 000000000..446ed7c63
--- /dev/null
+++ b/tests/corpus/7085-webcrypto-ecdsa-rsa.ts
@@ -0,0 +1,66 @@
+// WebCrypto ECDSA, RSASSA-PKCS1-v1_5 and RSA-PSS: importKey from spki,
+// pkcs8, jwk and raw, exportKey to each format, sign and verify (RSA
+// PKCS#1 v1.5 signatures are deterministic, so they compare with Node),
+// generateKey for every curve and both RSA algorithms, and the CryptoKey
+// members of these keys.
+const RSA_PKCS8 = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANZ6LgFEQmb7gLq7/4Xg0juRM5D2B/9r4Rx3ZJnF+ldG1dPRBU0wAjW5A2YHhptjO93UbZXZcqxMkUlwlaJuMDPnpTG3HGCBOlIBqUVlig+bod9pvAlv9haHQrQ6s0W92R5cssyIdCzGYhDaWsWjJxASlnj/LGMt7otAX4vv2TbLAgMBAAECgYAxZL/1kLfOKbuyxZsKRQWFrGH4z4B2BW05dhGYDG0j7m9OMcQLy7Omla4/sBoC7S6bXtHCAesT3SpUhHYhi0jUL0r1EtQ2UopU+qdJKavSqRdHbjpH9l3x6GjVdJIxfoqaduZGKNTH1HDUBsPZmZgKtew9/YewJ0f5IZDVlO6ecQJBAPT3R1MDbUnK79sAOCWqCLEkI8ihzqqnGy35bUut4ijhS+RKaCdFi+woOUzsU0j7p/uKAjrbkpcgCeXFWWYYtcMCQQDgI1V4/Tt+ZBQGzVo9D0sI683jZJXpLwWNt/hcchgbkqnjz/3ygmPWkWxc1ryefx+Y33MXDzIz/J3baJl254JZAkBYyNr/+mj86uGeJWk0NXlrAqc5i3PFJEXUlueHG2Gx4V5JQrKXMTyyfi2udKUG+bb7kxyVyolJ3uHXdnydZW1TAkBnRIBV7T6i/hMoCycfaNNmedZzhV14gVyqre91EHBBC/LQbn0PZboAGyJ1J87hq6pwMcuB/VNhFmvYsjm6yfGJAkAa3d7RALSrRY3inj1iHJddW4dxzHv1EWB83tXWWQDJi4vZs9XjWYS0P8BtKs5u2qq85pZmPBKvwkLI909lkAgz";
+const RSA_SPKI = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWei4BREJm+4C6u/+F4NI7kTOQ9gf/a+Ecd2SZxfpXRtXT0QVNMAI1uQNmB4abYzvd1G2V2XKsTJFJcJWibjAz56UxtxxggTpSAalFZYoPm6HfabwJb/YWh0K0OrNFvdkeXLLMiHQsxmIQ2lrFoycQEpZ4/yxjLe6LQF+L79k2ywIDAQAB";
+const EC_PKCS8 = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgulVxc4RlBekf+rd5zcCwuDjHgGV3q5kuoMLno5gpArihRANCAATSDCLRr/iS5XGOw7e/Dv2tPdBLGc9juyihWUj4Q2rfkPuZ5Zm/92kVMYW17sF3yUXFpnXR0yjS2X74A623u6Y+";
+const EC_SPKI = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0gwi0a/4kuVxjsO3vw79rT3QSxnPY7sooVlI+ENq35D7meWZv/dpFTGFte7Bd8lFxaZ10dMo0tl++AOtt7umPg==";
+
+const bytes = (text: string): Uint8Array => new Uint8Array(Buffer.from(text, "base64"));
+const hex = (buffer: ArrayBuffer): string => Buffer.from(buffer).toString("hex");
+const data = new TextEncoder().encode("webcrypto message");
+
+const rsaPrivate = await crypto.subtle.importKey("pkcs8", bytes(RSA_PKCS8), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, true, ["sign"]);
+const rsaPublic = await crypto.subtle.importKey("spki", bytes(RSA_SPKI), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, true, ["verify"]);
+console.log(rsaPrivate.type, rsaPublic.type, rsaPrivate.extractable, rsaPublic.usages);
+const rsaAlgorithm = rsaPrivate.algorithm as RsaHashedKeyAlgorithm;
+console.log(rsaAlgorithm.name, rsaAlgorithm.modulusLength, Array.from(rsaAlgorithm.publicExponent), rsaAlgorithm.hash.name);
+const rsaSignature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", rsaPrivate, data);
+console.log("rsa signature", hex(rsaSignature));
+console.log("rsa verify", await crypto.subtle.verify({ name: "RSASSA-PKCS1-v1_5" }, rsaPublic, rsaSignature, data));
+console.log("rsa spki", Buffer.from(await crypto.subtle.exportKey("spki", rsaPublic)).toString("base64") === RSA_SPKI);
+console.log("rsa pkcs8", Buffer.from(await crypto.subtle.exportKey("pkcs8", rsaPrivate)).toString("base64") === RSA_PKCS8);
+const rsaJwk = await crypto.subtle.exportKey("jwk", rsaPublic);
+console.log("rsa jwk", JSON.stringify(rsaJwk));
+const fromJwk = await crypto.subtle.importKey("jwk", rsaJwk, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["verify"]);
+console.log("rsa jwk verify", await crypto.subtle.verify("RSASSA-PKCS1-v1_5", fromJwk, rsaSignature, data), fromJwk.extractable);
+
+const ecPrivate = await crypto.subtle.importKey("pkcs8", bytes(EC_PKCS8), { name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]);
+const ecPublic = await crypto.subtle.importKey("spki", bytes(EC_SPKI), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
+console.log(JSON.stringify(ecPrivate.algorithm), ecPrivate.usages, ecPublic.usages);
+console.log("ec jwk", JSON.stringify(await crypto.subtle.exportKey("jwk", ecPrivate)));
+const point = await crypto.subtle.exportKey("raw", ecPublic);
+console.log("ec raw", hex(point));
+const fromPoint = await crypto.subtle.importKey("raw", point, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]);
+for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
+ const signature = await crypto.subtle.sign({ name: "ECDSA", hash }, ecPrivate, data);
+ console.log("ecdsa", hash, signature.byteLength, await crypto.subtle.verify({ name: "ECDSA", hash: { name: hash } }, fromPoint, signature, data));
+}
+const ecSignature = await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, ecPrivate, data);
+const tampered = new Uint8Array(ecSignature);
+tampered[5] ^= 1;
+console.log("tampered", await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, ecPublic, tampered, data));
+console.log("other data", await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, ecPublic, ecSignature, new Uint8Array([1])));
+
+for (const namedCurve of ["P-256", "P-384", "P-521"]) {
+ const pair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve }, false, ["sign", "verify"]);
+ const signature = await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-384" }, pair.privateKey, data);
+ console.log(namedCurve, Object.keys(pair).join(), pair.privateKey.extractable, pair.publicKey.extractable, signature.byteLength,
+ await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-384" }, pair.publicKey, signature, data),
+ (await crypto.subtle.exportKey("raw", pair.publicKey)).byteLength);
+}
+for (const name of ["RSASSA-PKCS1-v1_5", "RSA-PSS"]) {
+ const pair = await crypto.subtle.generateKey({ name, modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-512" }, true, ["sign", "verify"]);
+ const params = { name, saltLength: 20 };
+ const signature = await crypto.subtle.sign(params, pair.privateKey, data);
+ const jwk = await crypto.subtle.exportKey("jwk", pair.privateKey);
+ console.log(name, signature.byteLength, await crypto.subtle.verify(params, pair.publicKey, signature, data), jwk.alg, Object.keys(jwk).join());
+}
+const pss = await crypto.subtle.importKey("pkcs8", bytes(RSA_PKCS8), { name: "RSA-PSS", hash: "SHA-256" }, false, ["sign"]);
+const pssPublic = await crypto.subtle.importKey("spki", bytes(RSA_SPKI), { name: "RSA-PSS", hash: "SHA-256" }, false, ["verify"]);
+const pssSignature = await crypto.subtle.sign({ name: "RSA-PSS", saltLength: 32 }, pss, data);
+console.log("pss", await crypto.subtle.verify({ name: "RSA-PSS", saltLength: 32 }, pssPublic, pssSignature, data),
+ await crypto.subtle.verify({ name: "RSA-PSS", saltLength: 0 }, pssPublic, pssSignature, data));
+console.log(ecPrivate instanceof CryptoKey, String(ecPublic), Object.prototype.toString.call(rsaPublic));
diff --git a/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs b/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs
new file mode 100644
index 000000000..62292fbdd
--- /dev/null
+++ b/tests/corpus/7086-webcrypto-ecdsa-rsa-errors.mjs
@@ -0,0 +1,58 @@
+// WebCrypto ECDSA and RSA errors: Node's TypeErrors for missing algorithm
+// members, and its DOMExceptions for curves, usages, key types, formats,
+// extractability and JWK members, from generateKey, importKey, exportKey,
+// sign and verify.
+
+const t = async (label, fn) => { try { const v = await fn(); console.log(label, "=>", v instanceof ArrayBuffer ? "AB" + v.byteLength : typeof v === "object" && v !== null ? JSON.stringify(v) : v); } catch (e) { console.log(label, "!!", e.name, e.code, e.message); } };
+const e3 = new Uint8Array([1, 0, 1]);
+await t("rsa no hash", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: e3 }, true, ["sign"]));
+await t("rsa no len", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", publicExponent: e3, hash: "SHA-256" }, true, ["sign"]));
+await t("rsa no exp", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, hash: "SHA-256" }, true, ["sign"]));
+await t("rsa bad usage", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: e3, hash: "SHA-256" }, true, ["encrypt"]));
+await t("pss bad usage", () => crypto.subtle.generateKey({ name: "RSA-PSS", modulusLength: 1024, publicExponent: e3, hash: "SHA-256" }, true, ["encrypt"]));
+await t("rsa bad hash", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: e3, hash: "MD5" }, true, ["sign"]));
+await t("rsa exp 3", async () => (await crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: new Uint8Array([3]), hash: "SHA-256" }, true, ["sign"])).publicKey.algorithm.publicExponent.length);
+await t("rsa exp 4", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: new Uint8Array([4]), hash: "SHA-256" }, true, ["sign"]));
+await t("rsa small", () => crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 256, publicExponent: e3, hash: "SHA-256" }, true, ["sign"]));
+await t("ecdsa no curve", () => crypto.subtle.generateKey({ name: "ECDSA" }, true, ["sign"]));
+await t("unknown alg", () => crypto.subtle.generateKey({ name: "FOO" }, true, ["sign"]));
+const ec = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
+await t("export bad format", () => crypto.subtle.exportKey("foo", ec.publicKey));
+await t("export not key", () => crypto.subtle.exportKey("spki", {}));
+await t("export spki private", () => crypto.subtle.exportKey("spki", ec.privateKey));
+await t("export pkcs8 public", () => crypto.subtle.exportKey("pkcs8", ec.publicKey));
+await t("import raw private", () => crypto.subtle.importKey("raw", new Uint8Array(32), { name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]));
+await t("import pkcs8 verify", async () => crypto.subtle.importKey("pkcs8", await crypto.subtle.exportKey("pkcs8", ec.privateKey), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+await t("import pkcs8 empty", async () => crypto.subtle.importKey("pkcs8", await crypto.subtle.exportKey("pkcs8", ec.privateKey), { name: "ECDSA", namedCurve: "P-256" }, true, []));
+await t("import spki empty", async () => (await crypto.subtle.importKey("spki", await crypto.subtle.exportKey("spki", ec.publicKey), { name: "ECDSA", namedCurve: "P-256" }, true, [])).usages);
+await t("import no curve", async () => crypto.subtle.importKey("spki", await crypto.subtle.exportKey("spki", ec.publicKey), { name: "ECDSA" }, true, ["verify"]));
+await t("import rsa no hash", async () => crypto.subtle.importKey("spki", await crypto.subtle.exportKey("spki", ec.publicKey), { name: "RSASSA-PKCS1-v1_5" }, true, ["verify"]));
+await t("import ec as rsa", async () => crypto.subtle.importKey("spki", await crypto.subtle.exportKey("spki", ec.publicKey), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, true, ["verify"]));
+await t("import jwk bad", () => crypto.subtle.importKey("jwk", { kty: "EC", crv: "P-256", x: "AA", y: "AA" }, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+await t("import jwk crv mismatch", async () => crypto.subtle.importKey("jwk", await crypto.subtle.exportKey("jwk", ec.publicKey), { name: "ECDSA", namedCurve: "P-384" }, true, ["verify"]));
+await t("import jwk kty", async () => crypto.subtle.importKey("jwk", { ...(await crypto.subtle.exportKey("jwk", ec.publicKey)), kty: "RSA" }, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+await t("jwk ext false", async () => crypto.subtle.importKey("jwk", { ...(await crypto.subtle.exportKey("jwk", ec.publicKey)), ext: false }, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+await t("jwk key_ops", async () => crypto.subtle.importKey("jwk", { ...(await crypto.subtle.exportKey("jwk", ec.publicKey)), key_ops: ["sign"] }, { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+const data = new Uint8Array([1, 2, 3]);
+const spki = await crypto.subtle.exportKey("spki", ec.publicKey);
+const rsa = await crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, false, ["sign", "verify"]);
+const pss = await crypto.subtle.generateKey({ name: "RSA-PSS", modulusLength: 1024, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-384" }, true, ["sign", "verify"]);
+await t("export nonextractable", () => crypto.subtle.exportKey("pkcs8", rsa.privateKey));
+await t("export raw private", () => crypto.subtle.exportKey("raw", ec.privateKey));
+await t("sign with public", () => crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, ec.publicKey, data));
+await t("sign wrong alg", () => crypto.subtle.sign("RSASSA-PKCS1-v1_5", ec.privateKey, data));
+await t("ecdsa no hash", () => crypto.subtle.sign({ name: "ECDSA" }, ec.privateKey, data));
+await t("bad curve", () => crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-999" }, true, ["sign"]));
+await t("bad usage", () => crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["encrypt"]));
+await t("empty usages", () => crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, []));
+await t("verify usage only", async () => { const k = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, ["verify"]); return k.privateKey.usages + "|" + k.publicKey.usages; });
+await t("import wrong curve", () => crypto.subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: "P-384" }, true, ["verify"]));
+await t("import sign usage public", () => crypto.subtle.importKey("spki", spki, { name: "ECDSA", namedCurve: "P-256" }, true, ["sign"]));
+await t("import garbage", () => crypto.subtle.importKey("spki", new Uint8Array([1, 2, 3]), { name: "ECDSA", namedCurve: "P-256" }, true, ["verify"]));
+await t("pss no salt", () => crypto.subtle.sign({ name: "RSA-PSS" }, pss.privateKey, data));
+await t("verify bad sig len", () => crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, ec.publicKey, new Uint8Array(10), data));
+await t("verify with private", () => crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, ec.privateKey, new Uint8Array(64), data));
+await t("sign hmac with ec", () => crypto.subtle.sign("HMAC", ec.privateKey, data));
+await t("sign ec hash obj", async () => (await crypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-512" } }, ec.privateKey, data)).byteLength);
+await t("p521", async () => (await crypto.subtle.sign({ name: "ECDSA", hash: "SHA-256" }, (await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-521" }, false, ["sign"])).privateKey, data)).byteLength);
+await t("raw export p384", async () => (await crypto.subtle.exportKey("raw", (await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-384" }, true, ["sign","verify"])).publicKey)).byteLength);
diff --git a/tests/corpus/7087-tls-socket-wrap.ts b/tests/corpus/7087-tls-socket-wrap.ts
new file mode 100644
index 000000000..d1357a98e
--- /dev/null
+++ b/tests/corpus/7087-tls-socket-wrap.ts
@@ -0,0 +1,104 @@
+// new tls.TLSSocket(socket, { isServer: true, ... }) over a net server's
+// connection: the wrap is a new socket that runs the server handshake, with
+// cert/key or a secureContext and ALPNProtocols. The wrapped socket stays
+// open without I/O, destroy() on it closes both, and its 'close' fires just
+// before the TLS socket's. A peer that closes, resets or sends bytes that
+// are not TLS fails the handshake with Node's events and codes.
+import { readFileSync } from "node:fs";
+import * as net from "node:net";
+import * as tls from "node:tls";
+
+const certs = "tests/fixtures/server/certs";
+const ca = readFileSync(`${certs}/ca.pem`);
+const cert = readFileSync(`${certs}/localhost.pem`);
+const key = readFileSync(`${certs}/localhost-key.pem`);
+const context = tls.createSecureContext({ cert, key });
+
+// cert/key and ALPN: the events, the state of both sockets, and the data.
+function certKey(next: () => void): void {
+ const server = net.createServer((raw) => {
+ raw.on("close", (hadError: boolean) => console.log("raw close", hadError, raw.destroyed));
+ const s = new tls.TLSSocket(raw, { isServer: true, cert, key, ALPNProtocols: ["h2", "http/1.1"] });
+ console.log("after wrap", raw.destroyed, raw.readyState, raw.writable, s === raw);
+ console.log("before handshake", s.destroyed, s.encrypted, s.alpnProtocol);
+ console.log("same peer", s.remoteAddress === raw.remoteAddress, s.remotePort === raw.remotePort);
+ s.on("connect", () => console.log("never: connect"));
+ s.on("secureConnect", () => console.log("never: secureConnect"));
+ s.on("secure", () => {
+ console.log("secure", s.getProtocol(), s.alpnProtocol, s.authorized, s.authorizationError);
+ });
+ s.on("data", (chunk: Buffer) => {
+ console.log("server data", chunk.toString());
+ s.end("pong");
+ });
+ s.on("end", () => console.log("server end"));
+ s.on("close", (hadError: boolean) => {
+ console.log("tls close", hadError, raw.destroyed, s.destroyed);
+ server.close(next);
+ });
+ });
+ server.listen(0, () => {
+ const port = (server.address() as net.AddressInfo).port;
+ const c = tls.connect({ port, host: "localhost", ca, ALPNProtocols: ["http/1.1"] }, () => {
+ console.log("client secureConnect", c.authorized, c.alpnProtocol);
+ c.write("ping");
+ });
+ c.on("secure", () => console.log("client secure"));
+ c.on("data", (chunk: Buffer) => console.log("client data", chunk.toString()));
+ c.on("close", () => console.log("client close"));
+ });
+}
+
+// secureContext, and destroy() on the wrapped socket.
+function secureContext(next: () => void): void {
+ const server = net.createServer((raw) => {
+ raw.on("close", (hadError: boolean) => console.log("raw close", hadError));
+ const s = new tls.TLSSocket(raw, { isServer: true, secureContext: context });
+ s.on("secure", () => {
+ console.log("context secure", s.alpnProtocol);
+ raw.destroy();
+ });
+ s.on("close", (hadError: boolean) => {
+ console.log("tls close", hadError, s.destroyed);
+ server.close(next);
+ });
+ });
+ server.listen(0, () => {
+ const port = (server.address() as net.AddressInfo).port;
+ const c = tls.connect({ port, host: "localhost", ca }, () => console.log("client secureConnect"));
+ c.on("error", (err: Error & { code?: string }) => console.log("client error", err.code));
+ c.on("close", () => console.log("client close"));
+ });
+}
+
+// Handshakes the peer fails.
+function failure(mode: string, next: () => void): void {
+ const server = net.createServer((raw) => {
+ const s = new tls.TLSSocket(raw, { isServer: true, cert, key });
+ s.on("secure", () => console.log("never: secure"));
+ s.on("error", (err: Error & { code?: string }) => console.log(mode, "error", err.code));
+ s.on("end", () => console.log(mode, "end"));
+ s.on("close", (hadError: boolean) => {
+ console.log(mode, "close", hadError);
+ server.close(next);
+ });
+ });
+ server.listen(0, () => {
+ const port = (server.address() as net.AddressInfo).port;
+ const c = net.connect(port, "localhost", () => {
+ if (mode === "close") c.end();
+ else if (mode === "garbage") c.end("hello there, this is not tls\r\n");
+ else if (mode === "http") c.end("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
+ else c.resetAndDestroy();
+ });
+ c.on("error", () => {});
+ });
+}
+
+certKey(() =>
+ secureContext(() =>
+ failure("close", () =>
+ failure("garbage", () => failure("http", () => failure("reset", () => console.log("done")))),
+ ),
+ ),
+);
diff --git a/tests/corpus/7088-tls-socket-connect-tunnel.ts b/tests/corpus/7088-tls-socket-connect-tunnel.ts
new file mode 100644
index 000000000..806575ddb
--- /dev/null
+++ b/tests/corpus/7088-tls-socket-connect-tunnel.ts
@@ -0,0 +1,64 @@
+// An HTTPS-intercepting proxy: the http server answers CONNECT, then runs
+// the TLS server side on the tunnel socket with new tls.TLSSocket and a
+// secureContext; the client runs its handshake inside the tunnel with
+// tls.connect({ socket }). Without a servername the client verifies against
+// the host the tunnel socket dialed.
+import { readFileSync } from "node:fs";
+import * as http from "node:http";
+import * as net from "node:net";
+import * as tls from "node:tls";
+
+const certs = "tests/fixtures/server/certs";
+const ca = readFileSync(`${certs}/ca.pem`);
+const context = tls.createSecureContext({
+ cert: readFileSync(`${certs}/localhost.pem`),
+ key: readFileSync(`${certs}/localhost-key.pem`),
+});
+
+let tunnels = 0;
+const proxy = http.createServer((_req, res) => {
+ res.end("plain");
+});
+proxy.on("connect", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
+ const id = ++tunnels;
+ console.log(id, "proxy connect", req.method, req.url, head.length);
+ socket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
+ const secure = new tls.TLSSocket(socket, { isServer: true, secureContext: context });
+ secure.on("secure", () => console.log(id, "proxy secure", secure.getProtocol()));
+ secure.on("data", (chunk: Buffer) => {
+ console.log(id, "proxy got", chunk.toString());
+ secure.end(`intercepted ${chunk.toString()}`);
+ });
+ secure.on("close", () => {
+ console.log(id, "proxy tls close");
+ if (id === 2) proxy.close();
+ });
+ socket.on("close", () => console.log(id, "tunnel close"));
+});
+
+function tunnel(port: number, servername: string | undefined, next: () => void): void {
+ const raw = net.connect(port, "localhost", () => {
+ raw.write("CONNECT example.test:443 HTTP/1.1\r\nHost: example.test:443\r\n\r\n");
+ });
+ raw.once("data", (chunk: Buffer) => {
+ console.log("client got", chunk.toString().split("\r\n")[0]);
+ const onSecure = (): void => {
+ console.log("client secureConnect", client.authorized);
+ client.write(servername ?? "no servername");
+ };
+ const client =
+ servername === undefined
+ ? tls.connect({ socket: raw, ca }, onSecure)
+ : tls.connect({ socket: raw, ca, servername }, onSecure);
+ client.on("data", (data: Buffer) => console.log("client data", data.toString()));
+ client.on("close", () => {
+ console.log("client close");
+ next();
+ });
+ });
+}
+
+proxy.listen(0, () => {
+ const port = (proxy.address() as net.AddressInfo).port;
+ tunnel(port, "localhost", () => tunnel(port, undefined, () => console.log("done")));
+});
diff --git a/tests/corpus/7089-http-host-header-tunnels.ts b/tests/corpus/7089-http-host-header-tunnels.ts
new file mode 100644
index 000000000..bc982b2d1
--- /dev/null
+++ b/tests/corpus/7089-http-host-header-tunnels.ts
@@ -0,0 +1,31 @@
+// An HTTP/1.1 request without a Host header gets 400 Bad Request, except
+// the requests that leave the parser first: an upgrade an 'upgrade'
+// listener takes, and a CONNECT (destroyed here, with no 'connect'
+// listener).
+import * as http from "node:http";
+import * as net from "node:net";
+
+const server = http.createServer((_req, res) => res.end("request"));
+server.on("upgrade", (req: http.IncomingMessage, socket: net.Socket) => {
+ console.log("upgrade", req.url, req.headers.host);
+ socket.end("HTTP/1.1 101 Switching Protocols\r\n\r\n");
+});
+function probe(head: string, next: () => void): void {
+ const port = (server.address() as net.AddressInfo).port;
+ const raw = net.connect(port, "127.0.0.1", () => raw.write(head));
+ let got = "";
+ raw.on("data", (d: Buffer) => {
+ got += d.toString();
+ });
+ raw.on("close", () => {
+ console.log("got", JSON.stringify(got.split("\r\n")[0]));
+ next();
+ });
+}
+server.listen(0, "127.0.0.1", () => {
+ probe("GET /ws HTTP/1.1\r\nConnection: upgrade\r\nUpgrade: websocket\r\n\r\n", () =>
+ probe("CONNECT a:1 HTTP/1.1\r\n\r\n", () =>
+ probe("GET / HTTP/1.1\r\nConnection: close\r\n\r\n", () => server.close()),
+ ),
+ );
+});
diff --git a/tests/corpus/7094-tls-socket-failed-handshakes.ts b/tests/corpus/7094-tls-socket-failed-handshakes.ts
new file mode 100644
index 000000000..daad49a4e
--- /dev/null
+++ b/tests/corpus/7094-tls-socket-failed-handshakes.ts
@@ -0,0 +1,49 @@
+// A TLSSocket whose handshake never completes still closes and releases
+// its listeners, including a 'secure' listener that holds the socket: one
+// peer closes before sending anything and the next sends a plain HTTP
+// request.
+import { readFileSync } from "node:fs";
+import * as net from "node:net";
+import * as tls from "node:tls";
+
+const certs = "tests/fixtures/server/certs";
+const cert = readFileSync(`${certs}/localhost.pem`);
+const key = readFileSync(`${certs}/localhost-key.pem`);
+const context = tls.createSecureContext({ cert, key });
+
+const peers: ((port: number) => void)[] = [
+ (port) => {
+ const quiet = net.connect(port, "127.0.0.1", () => quiet.end());
+ },
+ (port) => {
+ const plain = net.connect(port, "127.0.0.1", () => plain.write("GET / HTTP/1.1\r\nHost: x\r\n\r\n"));
+ plain.on("error", () => {});
+ plain.resume();
+ },
+ (port) => {
+ const quiet = net.connect(port, "127.0.0.1", () => quiet.destroy());
+ },
+];
+let served = 0;
+const server = net.createServer((raw) => {
+ served++;
+ const secure =
+ served === 3
+ ? new tls.TLSSocket(raw, { isServer: true, secureContext: context })
+ : new tls.TLSSocket(raw, { isServer: true, cert, key });
+ secure.on("secure", () => console.log("secure", served, secure.alpnProtocol));
+ secure.on("error", (err: NodeJS.ErrnoException) => console.log("error", served, err.code));
+ secure.on("close", (hadError: boolean) => {
+ console.log("close", served, hadError);
+ next();
+ });
+});
+function next(): void {
+ const peer = peers.shift();
+ if (peer === undefined) {
+ server.close(() => console.log("server closed"));
+ return;
+ }
+ peer((server.address() as net.AddressInfo).port);
+}
+server.listen(0, "127.0.0.1", next);
diff --git a/tests/corpus/7106-async-prefix-enter-with.ts b/tests/corpus/7106-async-prefix-enter-with.ts
new file mode 100644
index 000000000..d70822171
--- /dev/null
+++ b/tests/corpus/7106-async-prefix-enter-with.ts
@@ -0,0 +1,119 @@
+// AsyncLocalStorage.enterWith in an async callee: before the callee's first
+// await it runs in the caller's execution, so the caller sees the store
+// after the call (Node's semantics), and the callee keeps it after its own
+// await; after the first await it stays inside the callee. Covers callees
+// that never suspend, stackless and fiber-backed callees, nested callees,
+// run() scopes inside the prefix, and calls without an await. A store that
+// an event-loop callback enters this way, or through a generator step, ends
+// with that callback and does not reach the next one.
+import { AsyncLocalStorage } from "node:async_hooks";
+
+const store = new AsyncLocalStorage();
+const log: string[] = [];
+const current = (): string => store.getStore() ?? "none";
+
+async function neverSuspends(tag: string): Promise {
+ store.enterWith(`never-${tag}`);
+ return 1;
+}
+async function prefixThenAwait(tag: string): Promise {
+ store.enterWith(`prefix-${tag}`);
+ await null;
+ log.push(`callee ${tag} after its await: ${current()}`);
+ return 2;
+}
+async function afterAwait(tag: string): Promise {
+ await null;
+ store.enterWith(`after-${tag}`);
+ return 3;
+}
+async function nested(tag: string): Promise {
+ await neverSuspends(`inner-${tag}`);
+ log.push(`nested ${tag} after its callee: ${current()}`);
+ return 4;
+}
+async function runInPrefix(tag: string): Promise {
+ store.run(`run-${tag}`, () => store.enterWith("inside-run"));
+ return 5;
+}
+async function* ticks(): AsyncGenerator {
+ yield 1;
+}
+async function fibered(tag: string): Promise {
+ store.enterWith(`fibered-${tag}`);
+ let n = 0;
+ for await (const t of ticks()) n += t;
+ log.push(`fibered ${tag} after its loop: ${current()}`);
+ return n;
+}
+
+function* steps(): Generator {
+ store.enterWith("generator-step");
+ yield 1;
+}
+function callbacks(): Promise {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ void prefixThenAwait("9");
+ log.push(`timer after the call: ${current()}`);
+ }, 0);
+ setTimeout(() => {
+ log.push(`second timer: ${current()}`);
+ steps().next();
+ log.push(`second timer after a generator step: ${current()}`);
+ }, 0);
+ setTimeout(() => {
+ log.push(`third timer: ${current()}`);
+ void neverSuspends("10");
+ log.push(`third timer after the call: ${current()}`);
+ }, 0);
+ setTimeout(() => {
+ log.push(`last timer: ${current()}`);
+ resolve();
+ }, 0);
+ });
+}
+
+async function main(): Promise {
+ await store.run("A", async () => {
+ await neverSuspends("1");
+ log.push(`A after neverSuspends: ${current()}`);
+ });
+ await store.run("B", async () => {
+ const pending = prefixThenAwait("2");
+ log.push(`B after the call: ${current()}`);
+ store.enterWith("B-later");
+ await pending;
+ log.push(`B after its await: ${current()}`);
+ });
+ await store.run("C", async () => {
+ await afterAwait("3");
+ log.push(`C after afterAwait: ${current()}`);
+ });
+ await store.run("D", async () => {
+ await nested("4");
+ log.push(`D after nested: ${current()}`);
+ });
+ await store.run("E", async () => {
+ await runInPrefix("5");
+ log.push(`E after runInPrefix: ${current()}`);
+ });
+ await store.run("F", async () => {
+ void neverSuspends("6");
+ log.push(`F after an unawaited call: ${current()}`);
+ await null;
+ log.push(`F later: ${current()}`);
+ });
+ store.run("G", () => {
+ void neverSuspends("7");
+ log.push(`G synchronous caller: ${current()}`);
+ });
+ await store.run("H", async () => {
+ await fibered("8");
+ log.push(`H after fibered: ${current()}`);
+ });
+ await callbacks();
+ log.push(`main after the callbacks: ${current()}`);
+ for (const line of log) console.log(line);
+}
+void main();
diff --git a/tests/corpus/7107-for-await-context.ts b/tests/corpus/7107-for-await-context.ts
new file mode 100644
index 000000000..8e21933b9
--- /dev/null
+++ b/tests/corpus/7107-for-await-context.ts
@@ -0,0 +1,37 @@
+// An async function's AsyncLocalStorage store across `for await` over an
+// async generator: the consumer keeps its store after each resumption of
+// the generator, whether it came from run(), from enterWith before the
+// loop, or from enterWith after an earlier await.
+import { AsyncLocalStorage } from "node:async_hooks";
+
+const store = new AsyncLocalStorage();
+const current = (): string => store.getStore() ?? "none";
+
+async function* numbers(n: number): AsyncGenerator {
+ for (let i = 0; i < n; i++) {
+ await null;
+ yield i;
+ }
+}
+
+async function consume(label: string): Promise {
+ let sum = 0;
+ for await (const v of numbers(3)) {
+ sum += v;
+ console.log(`${label} step ${v}: ${current()}`);
+ }
+ console.log(`${label} after the loop (${sum}): ${current()}`);
+}
+
+async function entered(): Promise {
+ await null;
+ store.enterWith("entered");
+ await consume("entered");
+}
+
+async function main(): Promise {
+ await store.run("run", () => consume("run"));
+ await entered();
+ console.log(`main: ${current()}`);
+}
+void main();
diff --git a/tests/corpus/7108-generator-step-context.ts b/tests/corpus/7108-generator-step-context.ts
new file mode 100644
index 000000000..3e9a01a5f
--- /dev/null
+++ b/tests/corpus/7108-generator-step-context.ts
@@ -0,0 +1,49 @@
+// A synchronous generator has no AsyncLocalStorage context of its own: each
+// step runs in its resumer's execution, so it sees the store current at
+// that next() call, and a store it enters is the resumer's after the step.
+// Covers steps inside and outside run(), for-of, spread and early return.
+import { AsyncLocalStorage } from "node:async_hooks";
+
+const store = new AsyncLocalStorage();
+const current = (): string => store.getStore() ?? "none";
+
+function* steps(): Generator {
+ console.log(`start sees ${current()}`);
+ yield "a";
+ console.log(`step 2 sees ${current()}`);
+ store.enterWith("entered-in-step-2");
+ yield "b";
+ console.log(`step 3 sees ${current()}`);
+}
+
+function* labels(): Generator {
+ yield current();
+ yield current();
+}
+
+store.run("S1", () => {
+ const g = steps();
+ g.next();
+ store.run("S2", () => g.next());
+ console.log(`caller after the S2 step: ${current()}`);
+ store.enterWith("S3");
+ g.next();
+ console.log(`caller after step 3: ${current()}`);
+});
+
+store.run("forof", () => {
+ const seen: string[] = [];
+ for (const label of labels()) seen.push(label);
+ console.log(`for-of: ${seen.join(",")}`);
+ console.log(`spread: ${[...labels()].join(",")}`);
+});
+
+store.run("outer", () => {
+ const g = steps();
+ g.next();
+ g.next();
+ console.log(`caller after entering step: ${current()}`);
+ g.return();
+ console.log(`caller after return: ${current()}`);
+});
+console.log(`top level: ${current()}`);
diff --git a/tests/corpus/7180-generic-local-binding-early-uses.ts b/tests/corpus/7180-generic-local-binding-early-uses.ts
new file mode 100644
index 000000000..f3f7816b5
--- /dev/null
+++ b/tests/corpus/7180-generic-local-binding-early-uses.ts
@@ -0,0 +1,69 @@
+// Generic arrow bindings declared inside functions, used by code that runs
+// before or after the declaration: closures created above the declaration
+// and hoisted function declarations read the binding like any const, so a
+// use before the declaration throws a ReferenceError and a later one sees
+// the function.
+function describe(error: unknown): string {
+ return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
+}
+
+function make(): (x: number) => number {
+ const read = (): ((x: number) => number) => id;
+ const id: (x: number) => number =