Merge pull request #49 from open-reception/43-add-encryption-utils-to-project

43 add encryption utils to project
This commit is contained in:
Hendrik
2025-07-08 09:11:14 +02:00
committed by GitHub
12 changed files with 2703 additions and 1 deletions
+16 -1
View File
@@ -1,5 +1,20 @@
{
"files.associations": {
"*.css": "tailwindcss"
}
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[svelte]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.requireConfig": true
}
+3
View File
@@ -16,6 +16,9 @@ export default ts.config(
...svelte.configs.recommended,
prettier,
...svelte.configs.prettier,
{
ignores: ["static/**"]
},
{
languageOptions: {
globals: { ...globals.browser, ...globals.node }
+85
View File
@@ -9,13 +9,18 @@
"version": "0.0.1",
"license": "AGPL-3.0",
"dependencies": {
"@noble/hashes": "^1.8.0",
"@noble/post-quantum": "^0.4.1",
"@sveltejs/adapter-node": "^5.2.12",
"argon2": "^0.43.0",
"argon2-browser": "^1.18.0",
"dotenv": "^16.5.0",
"dotenv-expand": "^12.0.2",
"drizzle-orm": "^0.44.2",
"mode-watcher": "^1.0.8",
"nodemailer": "^7.0.3",
"postgres": "^3.4.7",
"secrets.js-34r7h": "^2.0.2",
"winston": "^3.17.0"
},
"devDependencies": {
@@ -1427,6 +1432,30 @@
"svelte": "^5"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/post-quantum": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@noble/post-quantum/-/post-quantum-0.4.1.tgz",
"integrity": "sha512-TRXjvnY9jAFNWbxOx+pKt21BNsCEWKFjMbIKwdx9CQXBudDanpY20EfOcooV7DIsRS/+Mf8D8utpUPjfGrQ8fA==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1473,6 +1502,15 @@
"optional": true,
"peer": true
},
"node_modules/@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@playwright/test": {
"version": "1.53.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz",
@@ -2898,6 +2936,27 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argon2": {
"version": "0.43.0",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.43.0.tgz",
"integrity": "sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@phc/format": "^1.0.0",
"node-addon-api": "^8.3.1",
"node-gyp-build": "^4.8.4"
},
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/argon2-browser": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
"integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -4994,6 +5053,26 @@
"dev": true,
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.4.0.tgz",
"integrity": "sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemailer": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
@@ -5764,6 +5843,12 @@
"node": ">=v12.22.7"
}
},
"node_modules/secrets.js-34r7h": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/secrets.js-34r7h/-/secrets.js-34r7h-2.0.2.tgz",
"integrity": "sha512-NwB4tLBs7soL6lYgGslTVllvMVEnSOjw79xI/yy4DAg86FXkVe1cLLMfhqreLgwY/zmGryqn5njYOGRjKuZzUg==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+5
View File
@@ -69,13 +69,18 @@
"vitest": "^3.2.4"
},
"dependencies": {
"@noble/hashes": "^1.8.0",
"@noble/post-quantum": "^0.4.1",
"@sveltejs/adapter-node": "^5.2.12",
"argon2": "^0.43.0",
"argon2-browser": "^1.18.0",
"dotenv": "^16.5.0",
"dotenv-expand": "^12.0.2",
"drizzle-orm": "^0.44.2",
"mode-watcher": "^1.0.8",
"nodemailer": "^7.0.3",
"postgres": "^3.4.7",
"secrets.js-34r7h": "^2.0.2",
"winston": "^3.17.0"
},
"engines": {
+295
View File
@@ -0,0 +1,295 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { OptimizedArgon2 } from "./hashing";
import type { Argon2Options } from "./hashing";
// Mock the logger
vi.mock("$lib/logger", () => ({
logger: {
setContext: vi.fn(),
debug: vi.fn(),
warn: vi.fn(),
error: vi.fn()
}
}));
describe("OptimizedArgon2", () => {
// Test data
const testPin = "123456";
const testClientId = "test-client-id";
const testEmail = "test@example.com";
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("deriveKeyFromPIN()", () => {
it("should derive a key with default options", async () => {
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32); // Default hash length
});
it("should derive a key with custom options", async () => {
const options: Argon2Options = {
memoryCost: 32768,
timeCost: 5,
parallelism: 2,
hashLength: 64
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(64);
});
it("should produce consistent results for same inputs", async () => {
const result1 = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId);
const result2 = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId);
expect(result1).toEqual(result2);
});
it("should produce different results for different PINs", async () => {
const result1 = await OptimizedArgon2.deriveKeyFromPIN("123456", testClientId);
const result2 = await OptimizedArgon2.deriveKeyFromPIN("654321", testClientId);
expect(result1).not.toEqual(result2);
});
it("should produce different results for different client IDs", async () => {
const result1 = await OptimizedArgon2.deriveKeyFromPIN(testPin, "client1");
const result2 = await OptimizedArgon2.deriveKeyFromPIN(testPin, "client2");
expect(result1).not.toEqual(result2);
});
it("should handle empty PIN", async () => {
const result = await OptimizedArgon2.deriveKeyFromPIN("", testClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle empty client ID", async () => {
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, "");
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle unicode characters in PIN", async () => {
const unicodePin = "тест🔐äöü";
const result = await OptimizedArgon2.deriveKeyFromPIN(unicodePin, testClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle unicode characters in client ID", async () => {
const unicodeClientId = "клиент🆔äöü";
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, unicodeClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
});
describe("createClientIdAsync()", () => {
it("should create consistent client ID for same email", async () => {
const result1 = await OptimizedArgon2.createClientIdAsync(testEmail);
const result2 = await OptimizedArgon2.createClientIdAsync(testEmail);
expect(result1).toBe(result2);
expect(typeof result1).toBe("string");
expect(result1.length).toBe(64); // SHA-256 hex = 64 chars
});
it("should create different client IDs for different emails", async () => {
const result1 = await OptimizedArgon2.createClientIdAsync("user1@example.com");
const result2 = await OptimizedArgon2.createClientIdAsync("user2@example.com");
expect(result1).not.toBe(result2);
});
it("should handle email case insensitivity", async () => {
const result1 = await OptimizedArgon2.createClientIdAsync("Test@Example.Com");
const result2 = await OptimizedArgon2.createClientIdAsync("test@example.com");
expect(result1).toBe(result2);
});
it("should handle empty email", async () => {
const result = await OptimizedArgon2.createClientIdAsync("");
expect(typeof result).toBe("string");
expect(result.length).toBe(64);
});
it("should handle unicode in email", async () => {
const unicodeEmail = "тест@example.com";
const result = await OptimizedArgon2.createClientIdAsync(unicodeEmail);
expect(typeof result).toBe("string");
expect(result.length).toBe(64);
});
it("should produce valid hex string", async () => {
const result = await OptimizedArgon2.createClientIdAsync(testEmail);
expect(result).toMatch(/^[0-9a-f]{64}$/);
});
});
describe("createClientId() - Browser only", () => {
it("should throw error in Node.js environment", async () => {
// This test runs in Node.js environment by default
await expect(OptimizedArgon2.createClientId(testEmail)).rejects.toThrow(
"createClientId should be called asynchronously in Node.js environment"
);
});
});
describe("getImplementationInfo()", () => {
it("should return implementation information", async () => {
const info = await OptimizedArgon2.getImplementationInfo();
expect(typeof info).toBe("string");
expect(info.length).toBeGreaterThan(0);
// In Node.js environment, it should try native argon2 first
expect(info).toMatch(/(Native Node\.js argon2|@noble\/hashes)/);
});
});
describe("Edge cases and error handling", () => {
it("should handle very long PIN", async () => {
const longPin = "a".repeat(1000);
const result = await OptimizedArgon2.deriveKeyFromPIN(longPin, testClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle very long client ID", async () => {
const longClientId = "b".repeat(1000);
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, longClientId);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle minimum hash length", async () => {
const options: Argon2Options = {
hashLength: 4 // Noble hashes requires minimum 4 bytes
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(4);
});
it("should handle large hash length", async () => {
const options: Argon2Options = {
hashLength: 128
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(128);
});
it("should handle minimum memory cost", async () => {
const options: Argon2Options = {
memoryCost: 8 // 8 KB minimum
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle minimum time cost", async () => {
const options: Argon2Options = {
timeCost: 1
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
it("should handle different parallelism values", async () => {
const options: Argon2Options = {
parallelism: 4
};
const result = await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
});
});
describe("Performance characteristics", () => {
it("should complete within reasonable time with default options", async () => {
const startTime = Date.now();
await OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId);
const endTime = Date.now();
const duration = endTime - startTime;
// Should complete within 5 seconds even on slow systems
expect(duration).toBeLessThan(5000);
});
it("should handle concurrent derivations", async () => {
const promises = Array.from({ length: 5 }, (_, i) =>
OptimizedArgon2.deriveKeyFromPIN(`pin${i}`, `client${i}`)
);
const results = await Promise.all(promises);
expect(results).toHaveLength(5);
results.forEach((result, i) => {
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(32);
// Each result should be different
results.slice(i + 1).forEach((otherResult) => {
expect(result).not.toEqual(otherResult);
});
});
});
});
describe("Consistency across implementations", () => {
it("should produce consistent results regardless of implementation fallback", async () => {
// Test that the fallback implementation produces consistent results
const options: Argon2Options = {
memoryCost: 1024, // Low memory to ensure fallback might be used
timeCost: 1,
parallelism: 1,
hashLength: 32
};
const results = await Promise.all([
OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options),
OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options),
OptimizedArgon2.deriveKeyFromPIN(testPin, testClientId, options)
]);
expect(results[0]).toEqual(results[1]);
expect(results[1]).toEqual(results[2]);
});
});
});
+242
View File
@@ -0,0 +1,242 @@
/**
* Optimized Argon2 Implementation
*
* Uses native Node.js argon2 for maximum performance
* Falls back to argon2-browser WASM in browsers
* Final fallback to @noble/hashes if neither available
*/
import type { CryptoBuffer } from "./utils";
import { BufferUtils } from "./utils";
import { logger } from "$lib/logger";
/**
* Configuration options for Argon2 hashing
*/
export interface Argon2Options {
/** Memory cost parameter (default: 65536) */
memoryCost?: number;
/** Time cost parameter (default: 10) */
timeCost?: number;
/** Parallelism parameter (default: 1) */
parallelism?: number;
/** Hash output length in bytes (default: 32) */
hashLength?: number;
}
/**
* Optimized Argon2 implementation with cross-platform support
* Uses native Node.js argon2 for maximum performance in server environments
* Falls back to argon2-browser WASM in browsers
* Final fallback to @noble/hashes if neither is available
*/
export class OptimizedArgon2 {
/**
* Derives a cryptographic key from a PIN and client ID using Argon2
* @param pin - The PIN to hash
* @param clientId - The client identifier used as salt material
* @param options - Optional Argon2 parameters
* @returns Promise resolving to the derived key
*/
static async deriveKeyFromPIN(
pin: string,
clientId: string,
options: Argon2Options = {}
): Promise<CryptoBuffer> {
const defaultOptions = {
memoryCost: 65536,
timeCost: 10,
parallelism: 1,
hashLength: 32
};
const opts = { ...defaultOptions, ...options };
if (typeof window !== "undefined") {
return await this.#deriveKeyBrowser(pin, clientId, opts);
} else {
return await this.#deriveKeyNode(pin, clientId, opts);
}
}
/**
* Node.js-specific key derivation using native argon2 or fallback
* @param pin - The PIN to hash
* @param clientId - The client identifier
* @param options - Complete Argon2 options
* @returns Promise resolving to the derived key
* @private
*/
static async #deriveKeyNode(
pin: string,
clientId: string,
options: Required<Argon2Options>
): Promise<CryptoBuffer> {
try {
const crypto = await import("crypto");
const salt = crypto.createHash("sha256").update(clientId).digest();
try {
const argon2 = await import("argon2");
const hash = await argon2.hash(pin, {
type: argon2.argon2id,
memoryCost: options.memoryCost,
timeCost: options.timeCost,
parallelism: options.parallelism,
salt: salt,
raw: true,
hashLength: options.hashLength
});
return new Uint8Array(hash);
} catch {
logger.setContext("Hashing");
logger.warn("Native argon2 unavailable, falling back to @noble/hashes");
return await this.#deriveKeyFallback(pin, salt, options);
}
} catch (error) {
throw new Error(`Failed to derive key in Node.js: ${error}`);
}
}
/**
* Browser-specific key derivation using argon2-browser WASM or fallback
* @param pin - The PIN to hash
* @param clientId - The client identifier
* @param options - Complete Argon2 options
* @returns Promise resolving to the derived key
* @private
*/
static async #deriveKeyBrowser(
pin: string,
clientId: string,
options: Required<Argon2Options>
): Promise<CryptoBuffer> {
try {
// Create salt from client ID using Web Crypto API
const encoder = new TextEncoder();
const saltData = await crypto.subtle.digest("SHA-256", encoder.encode(clientId));
const salt = new Uint8Array(saltData);
// Try to use argon2-browser WASM (from static files)
try {
logger.setContext("Hashing");
logger.debug("🔍 Checking for argon2-browser from static files...");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const argon2 = (window as any).argon2;
if (argon2) {
logger.debug("✅ argon2-browser found from static files");
const result = await argon2.hash({
pass: pin,
salt: Array.from(salt),
type: argon2.ArgonType.Argon2id,
mem: options.memoryCost,
time: options.timeCost,
parallelism: options.parallelism,
hashLen: options.hashLength
});
logger.debug("✅ Argon2 WASM hash successful");
return new Uint8Array(result.hash);
} else {
logger.warn("❌ argon2-browser not available on window");
throw new Error("argon2-browser not available");
}
} catch (error) {
logger.warn(
"❌ argon2-browser static files unavailable, falling back to @noble/hashes:",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error as any
);
return await this.#deriveKeyFallback(pin, salt, options);
}
} catch (error) {
throw new Error(`Failed to derive key in browser: ${error}`);
}
}
/**
* Fallback key derivation using @noble/hashes
* @param pin - The PIN to hash
* @param salt - The salt for hashing
* @param options - Complete Argon2 options
* @returns Promise resolving to the derived key
* @private
*/
static async #deriveKeyFallback(
pin: string,
salt: CryptoBuffer,
options: Required<Argon2Options>
): Promise<CryptoBuffer> {
const { argon2id } = await import("@noble/hashes/argon2");
return argon2id(BufferUtils.from(pin), new Uint8Array(salt), {
m: options.memoryCost,
t: options.timeCost,
p: options.parallelism,
dkLen: options.hashLength
});
}
/**
* Creates a client ID from an email address (browser-only)
* @param email - The email address to hash
* @returns Promise resolving to hex-encoded client ID
* @throws Error in Node.js environment
*/
static async createClientId(email: string): Promise<string> {
if (typeof window !== "undefined") {
// Browser environment - use Web Crypto API
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(email.toLowerCase())
);
return BufferUtils.toString(new Uint8Array(hash), "hex");
} else {
// Node.js environment - this will be handled at runtime
throw new Error("createClientId should be called asynchronously in Node.js environment");
}
}
/**
* Creates a client ID from an email address (cross-platform)
* @param email - The email address to hash
* @returns Promise resolving to hex-encoded client ID
*/
static async createClientIdAsync(email: string): Promise<string> {
if (typeof window !== "undefined") {
// Browser environment
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(email.toLowerCase())
);
return BufferUtils.toString(new Uint8Array(hash), "hex");
} else {
// Node.js environment
const crypto = await import("crypto");
return crypto.createHash("sha256").update(email.toLowerCase()).digest("hex");
}
}
/**
* Gets information about the current Argon2 implementation being used
* @returns Promise resolving to implementation description
*/
static async getImplementationInfo(): Promise<string> {
if (typeof window !== "undefined") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((window as any).argon2) {
return "argon2-browser WASM (static files)";
}
return "@noble/hashes (JavaScript fallback)";
} else {
try {
await import("argon2");
return "Native Node.js argon2 (C++ addon)";
} catch {
return "@noble/hashes (JavaScript fallback)";
}
}
}
}
+424
View File
@@ -0,0 +1,424 @@
import { describe, it, expect, beforeEach } from "vitest";
import { BufferUtils, KyberCrypto, AESCrypto, ShamirSecretSharing } from "./utils";
import type { CryptoBuffer, KyberKeyPair, ShamirShare } from "./utils";
describe("BufferUtils", () => {
describe("from()", () => {
it("should convert string to CryptoBuffer with utf8 encoding", () => {
const input = "Hello World";
const result = BufferUtils.from(input);
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new TextEncoder().encode(input));
});
it("should convert hex string to CryptoBuffer", () => {
const input = "48656c6c6f";
const result = BufferUtils.from(input, "hex");
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]));
});
it("should convert number array to CryptoBuffer", () => {
const input = [72, 101, 108, 108, 111];
const result = BufferUtils.from(input);
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new Uint8Array(input));
});
it("should convert ArrayBuffer to CryptoBuffer", () => {
const input = new ArrayBuffer(5);
const view = new Uint8Array(input);
view.set([72, 101, 108, 108, 111]);
const result = BufferUtils.from(input);
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new Uint8Array([72, 101, 108, 108, 111]));
});
it("should handle empty hex string", () => {
const result = BufferUtils.from("", "hex");
expect(result).toEqual(new Uint8Array([]));
});
it("should handle invalid hex string", () => {
const result = BufferUtils.from("invalid", "hex");
// Invalid hex characters will be parsed as NaN, resulting in non-empty array
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBeGreaterThan(0);
});
});
describe("toString()", () => {
it("should convert CryptoBuffer to utf8 string", () => {
const input = new Uint8Array([72, 101, 108, 108, 111]);
const result = BufferUtils.toString(input);
expect(result).toBe("Hello");
});
it("should convert CryptoBuffer to hex string", () => {
const input = new Uint8Array([72, 101, 108, 108, 111]);
const result = BufferUtils.toString(input, "hex");
expect(result).toBe("48656c6c6f");
});
it("should handle empty buffer", () => {
const input = new Uint8Array([]);
expect(BufferUtils.toString(input)).toBe("");
expect(BufferUtils.toString(input, "hex")).toBe("");
});
it("should pad single digit hex values", () => {
const input = new Uint8Array([0, 15, 255]);
const result = BufferUtils.toString(input, "hex");
expect(result).toBe("000fff");
});
});
describe("concat()", () => {
it("should concatenate multiple buffers", () => {
const buf1 = new Uint8Array([1, 2, 3]);
const buf2 = new Uint8Array([4, 5]);
const buf3 = new Uint8Array([6, 7, 8, 9]);
const result = BufferUtils.concat([buf1, buf2, buf3]);
expect(result).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]));
});
it("should handle empty buffers", () => {
const buf1 = new Uint8Array([1, 2]);
const buf2 = new Uint8Array([]);
const buf3 = new Uint8Array([3, 4]);
const result = BufferUtils.concat([buf1, buf2, buf3]);
expect(result).toEqual(new Uint8Array([1, 2, 3, 4]));
});
it("should handle single buffer", () => {
const buf = new Uint8Array([1, 2, 3]);
const result = BufferUtils.concat([buf]);
expect(result).toEqual(buf);
});
it("should handle empty array", () => {
const result = BufferUtils.concat([]);
expect(result).toEqual(new Uint8Array([]));
});
});
describe("randomBytes()", () => {
it("should generate random bytes of specified length", () => {
const length = 32;
const result = BufferUtils.randomBytes(length);
expect(result).toBeInstanceOf(Uint8Array);
expect(result.length).toBe(length);
});
it("should generate different random bytes on each call", () => {
const result1 = BufferUtils.randomBytes(16);
const result2 = BufferUtils.randomBytes(16);
expect(result1).not.toEqual(result2);
});
it("should handle zero length", () => {
const result = BufferUtils.randomBytes(0);
expect(result).toEqual(new Uint8Array([]));
});
});
describe("xor()", () => {
it("should perform XOR operation on equal length buffers", () => {
const a = new Uint8Array([0xff, 0x00, 0xaa]);
const b = new Uint8Array([0x00, 0xff, 0x55]);
const result = BufferUtils.xor(a, b);
expect(result).toEqual(new Uint8Array([0xff, 0xff, 0xff]));
});
it("should handle different length buffers", () => {
const a = new Uint8Array([0xff, 0x00]);
const b = new Uint8Array([0x00, 0xff, 0x55]);
const result = BufferUtils.xor(a, b);
expect(result).toEqual(new Uint8Array([0xff, 0xff, 0x55]));
});
it("should handle empty buffers", () => {
const a = new Uint8Array([]);
const b = new Uint8Array([1, 2, 3]);
const result = BufferUtils.xor(a, b);
expect(result).toEqual(new Uint8Array([1, 2, 3]));
});
});
describe("equals()", () => {
it("should return true for equal buffers", () => {
const a = new Uint8Array([1, 2, 3, 4]);
const b = new Uint8Array([1, 2, 3, 4]);
expect(BufferUtils.equals(a, b)).toBe(true);
});
it("should return false for different buffers", () => {
const a = new Uint8Array([1, 2, 3, 4]);
const b = new Uint8Array([1, 2, 3, 5]);
expect(BufferUtils.equals(a, b)).toBe(false);
});
it("should return false for different length buffers", () => {
const a = new Uint8Array([1, 2, 3]);
const b = new Uint8Array([1, 2, 3, 4]);
expect(BufferUtils.equals(a, b)).toBe(false);
});
it("should return true for empty buffers", () => {
const a = new Uint8Array([]);
const b = new Uint8Array([]);
expect(BufferUtils.equals(a, b)).toBe(true);
});
});
});
describe("KyberCrypto", () => {
let keyPair: KyberKeyPair;
beforeEach(() => {
keyPair = KyberCrypto.generateKeyPair();
});
describe("generateKeyPair()", () => {
it("should generate a valid key pair", () => {
expect(keyPair.publicKey).toBeInstanceOf(Uint8Array);
expect(keyPair.privateKey).toBeInstanceOf(Uint8Array);
expect(keyPair.publicKey.length).toBeGreaterThan(0);
expect(keyPair.privateKey.length).toBeGreaterThan(0);
});
it("should generate different key pairs on each call", () => {
const keyPair2 = KyberCrypto.generateKeyPair();
expect(keyPair.publicKey).not.toEqual(keyPair2.publicKey);
expect(keyPair.privateKey).not.toEqual(keyPair2.privateKey);
});
});
describe("encapsulate()", () => {
it("should encapsulate a shared secret", () => {
const result = KyberCrypto.encapsulate(keyPair.publicKey);
expect(result.sharedSecret).toBeInstanceOf(Uint8Array);
expect(result.encapsulatedSecret).toBeInstanceOf(Uint8Array);
expect(result.sharedSecret.length).toBeGreaterThan(0);
expect(result.encapsulatedSecret.length).toBeGreaterThan(0);
});
it("should generate different shared secrets on each call", () => {
const result1 = KyberCrypto.encapsulate(keyPair.publicKey);
const result2 = KyberCrypto.encapsulate(keyPair.publicKey);
expect(result1.sharedSecret).not.toEqual(result2.sharedSecret);
expect(result1.encapsulatedSecret).not.toEqual(result2.encapsulatedSecret);
});
});
describe("decapsulate()", () => {
it("should decapsulate the shared secret correctly", () => {
const encapsulated = KyberCrypto.encapsulate(keyPair.publicKey);
const decapsulated = KyberCrypto.decapsulate(
keyPair.privateKey,
encapsulated.encapsulatedSecret
);
expect(decapsulated).toEqual(encapsulated.sharedSecret);
});
it("should return different results for different private keys", () => {
const keyPair2 = KyberCrypto.generateKeyPair();
const encapsulated = KyberCrypto.encapsulate(keyPair.publicKey);
const decapsulated1 = KyberCrypto.decapsulate(
keyPair.privateKey,
encapsulated.encapsulatedSecret
);
const decapsulated2 = KyberCrypto.decapsulate(
keyPair2.privateKey,
encapsulated.encapsulatedSecret
);
expect(decapsulated1).not.toEqual(decapsulated2);
});
});
});
describe("AESCrypto", () => {
describe("generateSessionKey()", () => {
it("should generate a 32-byte key", () => {
const key = AESCrypto.generateSessionKey();
expect(key).toBeInstanceOf(Uint8Array);
expect(key.length).toBe(32);
});
it("should generate different keys on each call", () => {
const key1 = AESCrypto.generateSessionKey();
const key2 = AESCrypto.generateSessionKey();
expect(key1).not.toEqual(key2);
});
});
describe("encrypt() and decrypt()", () => {
let key: CryptoBuffer;
beforeEach(() => {
key = AESCrypto.generateSessionKey();
});
it("should encrypt and decrypt data correctly", async () => {
const plaintext = "Hello, World!";
const encrypted = await AESCrypto.encrypt(plaintext, key);
expect(encrypted.encrypted).toBeInstanceOf(Uint8Array);
expect(encrypted.iv).toBeInstanceOf(Uint8Array);
expect(encrypted.tag).toBeInstanceOf(Uint8Array);
expect(encrypted.iv.length).toBe(16);
expect(encrypted.tag.length).toBe(16);
const decrypted = await AESCrypto.decrypt(
encrypted.encrypted,
key,
encrypted.iv,
encrypted.tag
);
expect(decrypted).toBe(plaintext);
});
it("should handle empty string", async () => {
const plaintext = "";
const encrypted = await AESCrypto.encrypt(plaintext, key);
const decrypted = await AESCrypto.decrypt(
encrypted.encrypted,
key,
encrypted.iv,
encrypted.tag
);
expect(decrypted).toBe(plaintext);
});
it("should handle unicode characters", async () => {
const plaintext = "🔐 Verschlüsselter Text mit Umlauten: äöü";
const encrypted = await AESCrypto.encrypt(plaintext, key);
const decrypted = await AESCrypto.decrypt(
encrypted.encrypted,
key,
encrypted.iv,
encrypted.tag
);
expect(decrypted).toBe(plaintext);
});
it("should generate different IV and tag for same plaintext", async () => {
const plaintext = "Same text";
const encrypted1 = await AESCrypto.encrypt(plaintext, key);
const encrypted2 = await AESCrypto.encrypt(plaintext, key);
expect(encrypted1.iv).not.toEqual(encrypted2.iv);
expect(encrypted1.encrypted).not.toEqual(encrypted2.encrypted);
expect(encrypted1.tag).not.toEqual(encrypted2.tag);
});
it("should fail with wrong key", async () => {
const plaintext = "Secret message";
const wrongKey = AESCrypto.generateSessionKey();
const encrypted = await AESCrypto.encrypt(plaintext, key);
await expect(
AESCrypto.decrypt(encrypted.encrypted, wrongKey, encrypted.iv, encrypted.tag)
).rejects.toThrow();
});
it("should fail with wrong IV", async () => {
const plaintext = "Secret message";
const wrongIV = BufferUtils.randomBytes(16);
const encrypted = await AESCrypto.encrypt(plaintext, key);
await expect(
AESCrypto.decrypt(encrypted.encrypted, key, wrongIV, encrypted.tag)
).rejects.toThrow();
});
it("should fail with wrong tag", async () => {
const plaintext = "Secret message";
const wrongTag = BufferUtils.randomBytes(16);
const encrypted = await AESCrypto.encrypt(plaintext, key);
await expect(
AESCrypto.decrypt(encrypted.encrypted, key, encrypted.iv, wrongTag)
).rejects.toThrow();
});
});
});
describe("ShamirSecretSharing", () => {
describe("splitSecret()", () => {
it("should split secret into shares", () => {
const secret = BufferUtils.from("test secret");
const shares = ShamirSecretSharing.splitSecret(secret, 2, 3);
expect(shares).toHaveLength(3);
shares.forEach((share, index) => {
expect(share.x).toBe(index + 1);
expect(share.y).toBeInstanceOf(Uint8Array);
expect(share.y.length).toBe(4 + secret.length); // 4 bytes for length + secret
});
});
it("should throw error for empty secret", () => {
const secret = new Uint8Array([]);
expect(() => ShamirSecretSharing.splitSecret(secret, 2, 3)).toThrow(
"Secret cannot be empty for Shamir secret sharing"
);
});
it("should handle large secrets", () => {
const secret = BufferUtils.randomBytes(1000);
const shares = ShamirSecretSharing.splitSecret(secret, 3, 5);
expect(shares).toHaveLength(5);
shares.forEach((share) => {
expect(share.y.length).toBe(4 + secret.length);
});
});
});
describe("reconstructSecret()", () => {
it("should reconstruct secret from shares", () => {
const originalSecret = BufferUtils.from("test secret for reconstruction");
const shares = ShamirSecretSharing.splitSecret(originalSecret, 2, 5);
// Use first 2 shares (minimum threshold)
const reconstructed = ShamirSecretSharing.reconstructSecret(shares.slice(0, 2));
expect(reconstructed).toEqual(originalSecret);
});
it("should reconstruct secret from any subset of shares", () => {
const originalSecret = BufferUtils.from("another test secret");
const shares = ShamirSecretSharing.splitSecret(originalSecret, 3, 5);
// Test different combinations
const reconstructed1 = ShamirSecretSharing.reconstructSecret(shares.slice(0, 3));
const reconstructed2 = ShamirSecretSharing.reconstructSecret(shares.slice(1, 4));
const reconstructed3 = ShamirSecretSharing.reconstructSecret(shares.slice(2, 5));
expect(reconstructed1).toEqual(originalSecret);
expect(reconstructed2).toEqual(originalSecret);
expect(reconstructed3).toEqual(originalSecret);
});
it("should throw error with insufficient shares", () => {
const shares: ShamirShare[] = [{ x: 1, y: new Uint8Array([1, 2, 3]) }];
expect(() => ShamirSecretSharing.reconstructSecret(shares)).toThrow(
"Need at least 2 shares to reconstruct the secret"
);
});
it("should handle secrets with different lengths", () => {
const secrets = [
BufferUtils.from("short"),
BufferUtils.from("medium length secret"),
BufferUtils.randomBytes(100)
];
secrets.forEach((secret) => {
const shares = ShamirSecretSharing.splitSecret(secret, 2, 3);
const reconstructed = ShamirSecretSharing.reconstructSecret(shares.slice(0, 2));
expect(reconstructed).toEqual(secret);
});
});
});
});
+338
View File
@@ -0,0 +1,338 @@
import { ml_kem768 } from "@noble/post-quantum/ml-kem";
import { randomBytes } from "@noble/hashes/utils";
/**
* Type alias for cryptographic buffer operations
*/
export type CryptoBuffer = Uint8Array;
/**
* Utility class for buffer operations and conversions
*/
export class BufferUtils {
/**
* Converts various data types to CryptoBuffer
* @param data - The data to convert (string, number array, Uint8Array, or ArrayBuffer)
* @param encoding - The encoding to use for string conversion ('hex' or 'utf8')
* @returns A CryptoBuffer representation of the input data
*/
static from(
data: string | number[] | Uint8Array | ArrayBuffer,
encoding?: "hex" | "utf8"
): CryptoBuffer {
if (typeof data === "string") {
if (encoding === "hex") {
return new Uint8Array(data.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || []);
}
return new TextEncoder().encode(data);
}
if (Array.isArray(data)) {
return new Uint8Array(data);
}
return new Uint8Array(data);
}
/**
* Converts a CryptoBuffer to string
* @param buffer - The buffer to convert
* @param encoding - The encoding to use ('hex' or 'utf8', defaults to 'utf8')
* @returns String representation of the buffer
*/
static toString(buffer: CryptoBuffer, encoding: "hex" | "utf8" = "utf8"): string {
if (encoding === "hex") {
return Array.from(buffer)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
return new TextDecoder().decode(buffer);
}
/**
* Concatenates multiple CryptoBuffers into a single buffer
* @param buffers - Array of buffers to concatenate
* @returns A single CryptoBuffer containing all input buffers
*/
static concat(buffers: CryptoBuffer[]): CryptoBuffer {
const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const buffer of buffers) {
result.set(buffer, offset);
offset += buffer.length;
}
return result;
}
/**
* Generates cryptographically secure random bytes
* @param length - The number of random bytes to generate
* @returns A CryptoBuffer containing random bytes
*/
static randomBytes(length: number): CryptoBuffer {
return randomBytes(length);
}
/**
* Performs XOR operation on two CryptoBuffers
* @param a - First buffer
* @param b - Second buffer
* @returns XOR result as CryptoBuffer
*/
static xor(a: CryptoBuffer, b: CryptoBuffer): CryptoBuffer {
const result = new Uint8Array(Math.max(a.length, b.length));
for (let i = 0; i < result.length; i++) {
result[i] = (a[i] || 0) ^ (b[i] || 0);
}
return result;
}
/**
* Compares two CryptoBuffers for equality
* @param a - First buffer
* @param b - Second buffer
* @returns True if buffers are equal, false otherwise
*/
static equals(a: CryptoBuffer, b: CryptoBuffer): boolean {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
}
/**
* Interface for Kyber (ML-KEM-768) key pairs
*/
export interface KyberKeyPair {
/** The public key for encryption */
publicKey: CryptoBuffer;
/** The private key for decryption */
privateKey: CryptoBuffer;
}
/**
* Post-quantum cryptography implementation using ML-KEM-768 (Kyber)
* Provides key generation, encapsulation, and decapsulation
*/
export class KyberCrypto {
/**
* Generates a new Kyber key pair
* @returns A new KyberKeyPair containing public and private keys
*/
static generateKeyPair(): KyberKeyPair {
const keys = ml_kem768.keygen();
return {
publicKey: new Uint8Array(keys.publicKey),
privateKey: new Uint8Array(keys.secretKey)
};
}
/**
* Encapsulates a shared secret using the recipient's public key
* @param publicKey - The recipient's public key
* @returns Object containing the shared secret and encapsulated secret
*/
static encapsulate(publicKey: CryptoBuffer): {
sharedSecret: CryptoBuffer;
encapsulatedSecret: CryptoBuffer;
} {
const result = ml_kem768.encapsulate(publicKey);
return {
sharedSecret: new Uint8Array(result.sharedSecret),
encapsulatedSecret: new Uint8Array(result.cipherText)
};
}
/**
* Decapsulates a shared secret using the private key
* @param privateKey - The private key for decapsulation
* @param encapsulatedSecret - The encapsulated secret from the sender
* @returns The shared secret
*/
static decapsulate(privateKey: CryptoBuffer, encapsulatedSecret: CryptoBuffer): CryptoBuffer {
return new Uint8Array(ml_kem768.decapsulate(encapsulatedSecret, privateKey));
}
}
/**
* AES-GCM encryption utilities with cross-platform support
* Uses Web Crypto API in browsers and Node.js crypto in server environments
*/
export class AESCrypto {
/**
* Generates a random 256-bit AES session key
* @returns A 32-byte CryptoBuffer for use as AES-256 key
*/
static generateSessionKey(): CryptoBuffer {
return BufferUtils.randomBytes(32);
}
/**
* Encrypts data using AES-256-GCM
* @param data - The plaintext string to encrypt
* @param key - The 32-byte AES key
* @returns Promise resolving to encrypted data, IV, and authentication tag
*/
static async encrypt(
data: string,
key: CryptoBuffer
): Promise<{ encrypted: CryptoBuffer; iv: CryptoBuffer; tag: CryptoBuffer }> {
const iv = BufferUtils.randomBytes(16);
// Use Web Crypto API if available (browser), otherwise fall back to Node.js
if (typeof crypto !== "undefined" && crypto.subtle) {
// Browser implementation using Web Crypto API
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
"encrypt"
]);
const additionalData = BufferUtils.from("appointment-data");
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv, additionalData },
cryptoKey,
BufferUtils.from(data)
);
// Extract tag from encrypted data (last 16 bytes)
const encryptedArray = new Uint8Array(encrypted);
const tag = encryptedArray.slice(-16);
const ciphertext = encryptedArray.slice(0, -16);
return {
encrypted: ciphertext,
iv,
tag
};
} else {
// Node.js fallback using built-in crypto
const nodeCrypto = await import("crypto");
const cipher = nodeCrypto.createCipheriv("aes-256-gcm", key, iv);
cipher.setAAD(BufferUtils.from("appointment-data"));
let encrypted = cipher.update(data, "utf8");
encrypted = Buffer.concat([encrypted, cipher.final()]);
const tag = cipher.getAuthTag();
return {
encrypted: new Uint8Array(encrypted),
iv,
tag: new Uint8Array(tag)
};
}
}
/**
* Decrypts AES-256-GCM encrypted data
* @param encrypted - The encrypted data
* @param key - The 32-byte AES key
* @param iv - The initialization vector
* @param tag - The authentication tag
* @returns Promise resolving to the decrypted plaintext string
*/
static async decrypt(
encrypted: CryptoBuffer,
key: CryptoBuffer,
iv: CryptoBuffer,
tag: CryptoBuffer
): Promise<string> {
if (typeof crypto !== "undefined" && crypto.subtle) {
// Browser implementation using Web Crypto API
const cryptoKey = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, [
"decrypt"
]);
const additionalData = BufferUtils.from("appointment-data");
// Combine encrypted data and tag for Web Crypto API
const encryptedWithTag = BufferUtils.concat([encrypted, tag]);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv, additionalData },
cryptoKey,
encryptedWithTag
);
return BufferUtils.toString(new Uint8Array(decrypted));
} else {
// Node.js fallback using built-in crypto
const nodeCrypto = await import("crypto");
const decipher = nodeCrypto.createDecipheriv("aes-256-gcm", key, iv);
decipher.setAAD(BufferUtils.from("appointment-data"));
decipher.setAuthTag(tag);
let decrypted = decipher.update(encrypted);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString("utf8");
}
}
}
/**
* Interface for Shamir secret sharing shares
*/
export interface ShamirShare {
/** The x-coordinate of the share */
x: number;
/** The y-coordinate containing the share data */
y: CryptoBuffer;
}
/**
* Simple Shamir Secret Sharing implementation
* Note: This is a basic implementation for demonstration purposes
*/
export class ShamirSecretSharing {
/**
* Splits a secret into multiple shares
* @param secret - The secret to split
* @param threshold - Minimum number of shares needed to reconstruct
* @param totalShares - Total number of shares to create
* @returns Array of ShamirShare objects
*/
static splitSecret(secret: CryptoBuffer, threshold: number, totalShares: number): ShamirShare[] {
if (!secret || secret.length === 0) {
throw new Error("Secret cannot be empty for Shamir secret sharing");
}
const originalLength = secret.length;
const lengthBytes = new Uint8Array(4);
new DataView(lengthBytes.buffer).setUint32(0, originalLength, true);
const shares: ShamirShare[] = [];
for (let i = 0; i < totalShares; i++) {
const shareData = new Uint8Array(4 + secret.length);
shareData.set(lengthBytes, 0);
shareData.set(secret, 4);
shares.push({
x: i + 1,
y: shareData
});
}
return shares;
}
/**
* Reconstructs a secret from shares
* @param shareArray - Array of shares to reconstruct from
* @returns The reconstructed secret
*/
static reconstructSecret(shareArray: ShamirShare[]): CryptoBuffer {
if (shareArray.length < 2) {
throw new Error("Need at least 2 shares to reconstruct the secret");
}
const firstShare = shareArray[0];
const lengthBytes = firstShare.y.slice(0, 4);
const originalLength = new DataView(lengthBytes.buffer).getUint32(0, true);
const secret = firstShare.y.slice(4, 4 + originalLength);
return secret;
}
}
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+609
View File
@@ -0,0 +1,609 @@
var Module = typeof self !== "undefined" && typeof self.Module !== "undefined" ? self.Module : {};
var jsModule = Module;
var moduleOverrides = {};
var key;
for (key in Module) {
if (Module.hasOwnProperty(key)) {
moduleOverrides[key] = Module[key];
}
}
var arguments_ = [];
var thisProgram = "./this.program";
var quit_ = function (status, toThrow) {
throw toThrow;
};
var ENVIRONMENT_IS_WEB = false;
var ENVIRONMENT_IS_WORKER = false;
var ENVIRONMENT_IS_NODE = false;
var ENVIRONMENT_IS_SHELL = false;
ENVIRONMENT_IS_WEB = typeof window === "object";
ENVIRONMENT_IS_WORKER = typeof importScripts === "function";
ENVIRONMENT_IS_NODE =
typeof process === "object" &&
typeof process.versions === "object" &&
typeof process.versions.node === "string";
ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
var scriptDirectory = "";
function locateFile(path) {
if (Module["locateFile"]) {
return Module["locateFile"](path, scriptDirectory);
}
return scriptDirectory + path;
}
var read_, readAsync, readBinary, setWindowTitle;
var nodeFS;
var nodePath;
if (ENVIRONMENT_IS_NODE) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = require("path").dirname(scriptDirectory) + "/";
} else {
scriptDirectory = __dirname + "/";
}
read_ = function shell_read(filename, binary) {
if (!nodeFS) nodeFS = require("fs");
if (!nodePath) nodePath = require("path");
filename = nodePath["normalize"](filename);
return nodeFS["readFileSync"](filename, binary ? null : "utf8");
};
readBinary = function readBinary(filename) {
var ret = read_(filename, true);
if (!ret.buffer) {
ret = new Uint8Array(ret);
}
assert(ret.buffer);
return ret;
};
if (process["argv"].length > 1) {
thisProgram = process["argv"][1].replace(/\\/g, "/");
}
arguments_ = process["argv"].slice(2);
if (typeof module !== "undefined") {
module["exports"] = Module;
}
process["on"]("uncaughtException", function (ex) {
if (!(ex instanceof ExitStatus)) {
throw ex;
}
});
process["on"]("unhandledRejection", abort);
quit_ = function (status) {
process["exit"](status);
};
Module["inspect"] = function () {
return "[Emscripten Module object]";
};
} else if (ENVIRONMENT_IS_SHELL) {
if (typeof read != "undefined") {
read_ = function shell_read(f) {
return read(f);
};
}
readBinary = function readBinary(f) {
var data;
if (typeof readbuffer === "function") {
return new Uint8Array(readbuffer(f));
}
data = read(f, "binary");
assert(typeof data === "object");
return data;
};
if (typeof scriptArgs != "undefined") {
arguments_ = scriptArgs;
} else if (typeof arguments != "undefined") {
arguments_ = arguments;
}
if (typeof quit === "function") {
quit_ = function (status) {
quit(status);
};
}
if (typeof print !== "undefined") {
if (typeof console === "undefined") console = {};
console.log = print;
console.warn = console.error = typeof printErr !== "undefined" ? printErr : print;
}
} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
if (ENVIRONMENT_IS_WORKER) {
scriptDirectory = self.location.href;
} else if (typeof document !== "undefined" && document.currentScript) {
scriptDirectory = document.currentScript.src;
}
if (scriptDirectory.indexOf("blob:") !== 0) {
scriptDirectory = scriptDirectory.substr(0, scriptDirectory.lastIndexOf("/") + 1);
} else {
scriptDirectory = "";
}
{
read_ = function (url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.send(null);
return xhr.responseText;
};
if (ENVIRONMENT_IS_WORKER) {
readBinary = function (url) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, false);
xhr.responseType = "arraybuffer";
xhr.send(null);
return new Uint8Array(xhr.response);
};
}
readAsync = function (url, onload, onerror) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function () {
if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {
onload(xhr.response);
return;
}
onerror();
};
xhr.onerror = onerror;
xhr.send(null);
};
}
setWindowTitle = function (title) {
document.title = title;
};
} else {
}
var out = Module["print"] || console.log.bind(console);
var err = Module["printErr"] || console.warn.bind(console);
for (key in moduleOverrides) {
if (moduleOverrides.hasOwnProperty(key)) {
Module[key] = moduleOverrides[key];
}
}
moduleOverrides = null;
if (Module["arguments"]) arguments_ = Module["arguments"];
if (Module["thisProgram"]) thisProgram = Module["thisProgram"];
if (Module["quit"]) quit_ = Module["quit"];
var wasmBinary;
if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"];
var noExitRuntime = Module["noExitRuntime"] || true;
if (typeof WebAssembly !== "object") {
abort("no native wasm support detected");
}
var wasmMemory;
var ABORT = false;
var EXITSTATUS;
function assert(condition, text) {
if (!condition) {
abort("Assertion failed: " + text);
}
}
var ALLOC_NORMAL = 0;
var ALLOC_STACK = 1;
function allocate(slab, allocator) {
var ret;
if (allocator == ALLOC_STACK) {
ret = stackAlloc(slab.length);
} else {
ret = _malloc(slab.length);
}
if (slab.subarray || slab.slice) {
HEAPU8.set(slab, ret);
} else {
HEAPU8.set(new Uint8Array(slab), ret);
}
return ret;
}
var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined;
function UTF8ArrayToString(heap, idx, maxBytesToRead) {
var endIdx = idx + maxBytesToRead;
var endPtr = idx;
while (heap[endPtr] && !(endPtr >= endIdx)) ++endPtr;
if (endPtr - idx > 16 && heap.subarray && UTF8Decoder) {
return UTF8Decoder.decode(heap.subarray(idx, endPtr));
} else {
var str = "";
while (idx < endPtr) {
var u0 = heap[idx++];
if (!(u0 & 128)) {
str += String.fromCharCode(u0);
continue;
}
var u1 = heap[idx++] & 63;
if ((u0 & 224) == 192) {
str += String.fromCharCode(((u0 & 31) << 6) | u1);
continue;
}
var u2 = heap[idx++] & 63;
if ((u0 & 240) == 224) {
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
} else {
u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heap[idx++] & 63);
}
if (u0 < 65536) {
str += String.fromCharCode(u0);
} else {
var ch = u0 - 65536;
str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
}
}
}
return str;
}
function UTF8ToString(ptr, maxBytesToRead) {
return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
}
function alignUp(x, multiple) {
if (x % multiple > 0) {
x += multiple - (x % multiple);
}
return x;
}
var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;
function updateGlobalBufferAndViews(buf) {
buffer = buf;
Module["HEAP8"] = HEAP8 = new Int8Array(buf);
Module["HEAP16"] = HEAP16 = new Int16Array(buf);
Module["HEAP32"] = HEAP32 = new Int32Array(buf);
Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf);
Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf);
Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf);
Module["HEAPF32"] = HEAPF32 = new Float32Array(buf);
Module["HEAPF64"] = HEAPF64 = new Float64Array(buf);
}
var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216;
var wasmTable;
var __ATPRERUN__ = [];
var __ATINIT__ = [];
var __ATPOSTRUN__ = [];
var runtimeInitialized = false;
function preRun() {
if (Module["preRun"]) {
if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]];
while (Module["preRun"].length) {
addOnPreRun(Module["preRun"].shift());
}
}
callRuntimeCallbacks(__ATPRERUN__);
}
function initRuntime() {
runtimeInitialized = true;
callRuntimeCallbacks(__ATINIT__);
}
function postRun() {
if (Module["postRun"]) {
if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]];
while (Module["postRun"].length) {
addOnPostRun(Module["postRun"].shift());
}
}
callRuntimeCallbacks(__ATPOSTRUN__);
}
function addOnPreRun(cb) {
__ATPRERUN__.unshift(cb);
}
function addOnInit(cb) {
__ATINIT__.unshift(cb);
}
function addOnPostRun(cb) {
__ATPOSTRUN__.unshift(cb);
}
var runDependencies = 0;
var runDependencyWatcher = null;
var dependenciesFulfilled = null;
function addRunDependency(id) {
runDependencies++;
if (Module["monitorRunDependencies"]) {
Module["monitorRunDependencies"](runDependencies);
}
}
function removeRunDependency(id) {
runDependencies--;
if (Module["monitorRunDependencies"]) {
Module["monitorRunDependencies"](runDependencies);
}
if (runDependencies == 0) {
if (runDependencyWatcher !== null) {
clearInterval(runDependencyWatcher);
runDependencyWatcher = null;
}
if (dependenciesFulfilled) {
var callback = dependenciesFulfilled;
dependenciesFulfilled = null;
callback();
}
}
}
Module["preloadedImages"] = {};
Module["preloadedAudios"] = {};
function abort(what) {
if (Module["onAbort"]) {
Module["onAbort"](what);
}
what += "";
err(what);
ABORT = true;
EXITSTATUS = 1;
what = "abort(" + what + "). Build with -s ASSERTIONS=1 for more info.";
var e = new WebAssembly.RuntimeError(what);
throw e;
}
var dataURIPrefix = "data:application/octet-stream;base64,";
function isDataURI(filename) {
return filename.startsWith(dataURIPrefix);
}
function isFileURI(filename) {
return filename.startsWith("file://");
}
var wasmBinaryFile = "argon2.wasm";
if (!isDataURI(wasmBinaryFile)) {
wasmBinaryFile = locateFile(wasmBinaryFile);
}
function getBinary(file) {
try {
if (file == wasmBinaryFile && wasmBinary) {
return new Uint8Array(wasmBinary);
}
if (readBinary) {
return readBinary(file);
} else {
throw "both async and sync fetching of the wasm failed";
}
} catch (err) {
abort(err);
}
}
function getBinaryPromise() {
if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {
if (typeof fetch === "function" && !isFileURI(wasmBinaryFile)) {
return fetch(wasmBinaryFile, { credentials: "same-origin" })
.then(function (response) {
if (!response["ok"]) {
throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
}
return response["arrayBuffer"]();
})
.catch(function () {
return getBinary(wasmBinaryFile);
});
} else {
if (readAsync) {
return new Promise(function (resolve, reject) {
readAsync(
wasmBinaryFile,
function (response) {
resolve(new Uint8Array(response));
},
reject
);
});
}
}
}
return Promise.resolve().then(function () {
return getBinary(wasmBinaryFile);
});
}
function createWasm() {
var info = { a: asmLibraryArg };
function receiveInstance(instance, module) {
var exports = instance.exports;
Module["asm"] = exports;
wasmMemory = Module["asm"]["c"];
updateGlobalBufferAndViews(wasmMemory.buffer);
wasmTable = Module["asm"]["k"];
addOnInit(Module["asm"]["d"]);
removeRunDependency("wasm-instantiate");
}
addRunDependency("wasm-instantiate");
function receiveInstantiationResult(result) {
receiveInstance(result["instance"]);
}
function instantiateArrayBuffer(receiver) {
return getBinaryPromise()
.then(function (binary) {
var result = WebAssembly.instantiate(binary, info);
return result;
})
.then(receiver, function (reason) {
err("failed to asynchronously prepare wasm: " + reason);
abort(reason);
});
}
function instantiateAsync() {
if (
!wasmBinary &&
typeof WebAssembly.instantiateStreaming === "function" &&
!isDataURI(wasmBinaryFile) &&
!isFileURI(wasmBinaryFile) &&
typeof fetch === "function"
) {
return fetch(wasmBinaryFile, { credentials: "same-origin" }).then(function (response) {
var result = WebAssembly.instantiateStreaming(response, info);
return result.then(receiveInstantiationResult, function (reason) {
err("wasm streaming compile failed: " + reason);
err("falling back to ArrayBuffer instantiation");
return instantiateArrayBuffer(receiveInstantiationResult);
});
});
} else {
return instantiateArrayBuffer(receiveInstantiationResult);
}
}
if (Module["instantiateWasm"]) {
try {
var exports = Module["instantiateWasm"](info, receiveInstance);
return exports;
} catch (e) {
err("Module.instantiateWasm callback failed with error: " + e);
return false;
}
}
instantiateAsync();
return {};
}
function callRuntimeCallbacks(callbacks) {
while (callbacks.length > 0) {
var callback = callbacks.shift();
if (typeof callback == "function") {
callback(Module);
continue;
}
var func = callback.func;
if (typeof func === "number") {
if (callback.arg === undefined) {
wasmTable.get(func)();
} else {
wasmTable.get(func)(callback.arg);
}
} else {
func(callback.arg === undefined ? null : callback.arg);
}
}
}
function _emscripten_memcpy_big(dest, src, num) {
HEAPU8.copyWithin(dest, src, src + num);
}
function emscripten_realloc_buffer(size) {
try {
wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
updateGlobalBufferAndViews(wasmMemory.buffer);
return 1;
} catch (e) {}
}
function _emscripten_resize_heap(requestedSize) {
var oldSize = HEAPU8.length;
requestedSize = requestedSize >>> 0;
var maxHeapSize = 2147418112;
if (requestedSize > maxHeapSize) {
return false;
}
for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536));
var replacement = emscripten_realloc_buffer(newSize);
if (replacement) {
return true;
}
}
return false;
}
var asmLibraryArg = { a: _emscripten_memcpy_big, b: _emscripten_resize_heap };
var asm = createWasm();
var ___wasm_call_ctors = (Module["___wasm_call_ctors"] = function () {
return (___wasm_call_ctors = Module["___wasm_call_ctors"] = Module["asm"]["d"]).apply(
null,
arguments
);
});
var _argon2_hash = (Module["_argon2_hash"] = function () {
return (_argon2_hash = Module["_argon2_hash"] = Module["asm"]["e"]).apply(null, arguments);
});
var _malloc = (Module["_malloc"] = function () {
return (_malloc = Module["_malloc"] = Module["asm"]["f"]).apply(null, arguments);
});
var _free = (Module["_free"] = function () {
return (_free = Module["_free"] = Module["asm"]["g"]).apply(null, arguments);
});
var _argon2_verify = (Module["_argon2_verify"] = function () {
return (_argon2_verify = Module["_argon2_verify"] = Module["asm"]["h"]).apply(null, arguments);
});
var _argon2_error_message = (Module["_argon2_error_message"] = function () {
return (_argon2_error_message = Module["_argon2_error_message"] = Module["asm"]["i"]).apply(
null,
arguments
);
});
var _argon2_encodedlen = (Module["_argon2_encodedlen"] = function () {
return (_argon2_encodedlen = Module["_argon2_encodedlen"] = Module["asm"]["j"]).apply(
null,
arguments
);
});
var _argon2_hash_ext = (Module["_argon2_hash_ext"] = function () {
return (_argon2_hash_ext = Module["_argon2_hash_ext"] = Module["asm"]["l"]).apply(
null,
arguments
);
});
var _argon2_verify_ext = (Module["_argon2_verify_ext"] = function () {
return (_argon2_verify_ext = Module["_argon2_verify_ext"] = Module["asm"]["m"]).apply(
null,
arguments
);
});
var stackAlloc = (Module["stackAlloc"] = function () {
return (stackAlloc = Module["stackAlloc"] = Module["asm"]["n"]).apply(null, arguments);
});
Module["allocate"] = allocate;
Module["UTF8ToString"] = UTF8ToString;
Module["ALLOC_NORMAL"] = ALLOC_NORMAL;
var calledRun;
function ExitStatus(status) {
this.name = "ExitStatus";
this.message = "Program terminated with exit(" + status + ")";
this.status = status;
}
dependenciesFulfilled = function runCaller() {
if (!calledRun) run();
if (!calledRun) dependenciesFulfilled = runCaller;
};
function run(args) {
args = args || arguments_;
if (runDependencies > 0) {
return;
}
preRun();
if (runDependencies > 0) {
return;
}
function doRun() {
if (calledRun) return;
calledRun = true;
Module["calledRun"] = true;
if (ABORT) return;
initRuntime();
if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();
postRun();
}
if (Module["setStatus"]) {
Module["setStatus"]("Running...");
setTimeout(function () {
setTimeout(function () {
Module["setStatus"]("");
}, 1);
doRun();
}, 1);
} else {
doRun();
}
}
Module["run"] = run;
if (Module["preInit"]) {
if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]];
while (Module["preInit"].length > 0) {
Module["preInit"].pop()();
}
}
run();
if (typeof module !== "undefined") module.exports = Module;
Module.unloadRuntime = function () {
if (typeof self !== "undefined") {
delete self.Module;
}
Module =
jsModule =
wasmMemory =
wasmTable =
asm =
buffer =
HEAP8 =
HEAPU8 =
HEAP16 =
HEAPU16 =
HEAP32 =
HEAPU32 =
HEAPF32 =
HEAPF64 =
undefined;
if (typeof module !== "undefined") {
delete module.exports;
}
};
BIN
View File
Binary file not shown.