diff --git a/apps/server/package.json b/apps/server/package.json index be99cf0..85d9d70 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -23,6 +23,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@furtherverse/crypto": "workspace:*", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/openapi": "catalog:", @@ -35,7 +36,7 @@ "@tanstack/react-router-ssr-query": "catalog:", "@tanstack/react-start": "catalog:", "drizzle-orm": "catalog:", - "postgres": "catalog:", + "jszip": "catalog:", "react": "catalog:", "react-dom": "catalog:", "uuid": "catalog:", diff --git a/bun.lock b/bun.lock index dad85ed..467ae89 100644 --- a/bun.lock +++ b/bun.lock @@ -35,6 +35,7 @@ "name": "@furtherverse/server", "version": "1.0.0", "dependencies": { + "@furtherverse/crypto": "workspace:*", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/openapi": "catalog:", @@ -47,7 +48,7 @@ "@tanstack/react-router-ssr-query": "catalog:", "@tanstack/react-start": "catalog:", "drizzle-orm": "catalog:", - "postgres": "catalog:", + "jszip": "catalog:", "react": "catalog:", "react-dom": "catalog:", "uuid": "catalog:", @@ -70,6 +71,17 @@ "vite-tsconfig-paths": "catalog:", }, }, + "packages/crypto": { + "name": "@furtherverse/crypto", + "version": "1.0.0", + "dependencies": { + "openpgp": "catalog:", + }, + "devDependencies": { + "@furtherverse/tsconfig": "workspace:*", + "@types/bun": "catalog:", + }, + }, "packages/tsconfig": { "name": "@furtherverse/tsconfig", "version": "1.0.0", @@ -79,7 +91,6 @@ "@types/node": "catalog:", }, "catalog": { - "@biomejs/biome": "^2.3.11", "@orpc/client": "^1.13.6", "@orpc/contract": "^1.13.6", "@orpc/openapi": "^1.13.6", @@ -105,15 +116,14 @@ "electron": "^34.0.0", "electron-builder": "^26.8.1", "electron-vite": "^5.0.0", + "jszip": "^3.10.1", "motion": "^12.35.0", "nitro": "npm:nitro-nightly@3.0.1-20260227-181935-bfbb207c", - "postgres": "^3.4.8", + "openpgp": "^6.0.1", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwindcss": "^4.2.1", "tree-kill": "^1.2.2", - "turbo": "^2.7.5", - "typescript": "^5.9.3", "uuid": "^13.0.0", "vite": "^8.0.0-beta.16", "vite-tsconfig-paths": "^6.1.1", @@ -296,6 +306,8 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@furtherverse/crypto": ["@furtherverse/crypto@workspace:packages/crypto"], + "@furtherverse/desktop": ["@furtherverse/desktop@workspace:apps/desktop"], "@furtherverse/server": ["@furtherverse/server@workspace:apps/server"], @@ -936,6 +948,8 @@ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -964,6 +978,8 @@ "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], "isbot": ["isbot@5.1.35", "", {}, "sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg=="], @@ -1000,6 +1016,8 @@ "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], + "jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="], + "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], @@ -1010,6 +1028,8 @@ "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], @@ -1146,6 +1166,8 @@ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "openpgp": ["openpgp@6.3.0", "", {}, "sha512-pLzCU8IgyKXPSO11eeharQkQ4GzOKNWhXq79pQarIRZEMt1/ssyr+MIuWBv1mNoenJLg04gvPx+fi4gcKZ4bag=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], @@ -1156,6 +1178,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], @@ -1192,6 +1216,8 @@ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], @@ -1214,7 +1240,7 @@ "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -1246,7 +1272,7 @@ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -1266,6 +1292,8 @@ "seroval-plugins": ["seroval-plugins@1.5.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA=="], + "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="], + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], @@ -1304,7 +1332,7 @@ "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -1518,6 +1546,8 @@ "bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "bl/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -1528,6 +1558,8 @@ "dir-compare/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "ecdsa-sig-formatter/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], "filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], @@ -1544,6 +1576,10 @@ "jsonwebtoken/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "jwa/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "jws/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -1568,8 +1604,6 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], - "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], @@ -1604,6 +1638,8 @@ "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -1688,6 +1724,8 @@ "app-builder-lib/@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + "bl/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -1696,6 +1734,10 @@ "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "ora/bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "ora/bl/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], } } diff --git a/package.json b/package.json index b241415..e91092b 100644 --- a/package.json +++ b/package.json @@ -52,9 +52,10 @@ "electron": "^34.0.0", "electron-builder": "^26.8.1", "electron-vite": "^5.0.0", + "jszip": "^3.10.1", "motion": "^12.35.0", "nitro": "npm:nitro-nightly@3.0.1-20260227-181935-bfbb207c", - "postgres": "^3.4.8", + "openpgp": "^6.0.1", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwindcss": "^4.2.1", diff --git a/packages/crypto/package.json b/packages/crypto/package.json new file mode 100644 index 0000000..6018329 --- /dev/null +++ b/packages/crypto/package.json @@ -0,0 +1,16 @@ +{ + "name": "@furtherverse/crypto", + "version": "1.0.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "openpgp": "catalog:" + }, + "devDependencies": { + "@furtherverse/tsconfig": "workspace:*", + "@types/bun": "catalog:" + } +} diff --git a/packages/crypto/src/aes-gcm.ts b/packages/crypto/src/aes-gcm.ts new file mode 100644 index 0000000..633ae4c --- /dev/null +++ b/packages/crypto/src/aes-gcm.ts @@ -0,0 +1,53 @@ +import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto' + +const GCM_IV_LENGTH = 12 // 96 bits +const GCM_TAG_LENGTH = 16 // 128 bits +const ALGORITHM = 'aes-256-gcm' + +/** + * AES-256-GCM encrypt. + * + * Output format (before Base64): [IV (12 bytes)] + [ciphertext] + [auth tag (16 bytes)] + * + * @param plaintext - UTF-8 string to encrypt + * @param key - 32-byte AES key + * @returns Base64-encoded encrypted data + */ +export const aesGcmEncrypt = (plaintext: string, key: Buffer): string => { + const iv = randomBytes(GCM_IV_LENGTH) + const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: GCM_TAG_LENGTH }) + + const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]) + const tag = cipher.getAuthTag() + + // Layout: IV + ciphertext + tag + const combined = Buffer.concat([iv, encrypted, tag]) + return combined.toString('base64') +} + +/** + * AES-256-GCM decrypt. + * + * Input format (after Base64 decode): [IV (12 bytes)] + [ciphertext] + [auth tag (16 bytes)] + * + * @param encryptedBase64 - Base64-encoded encrypted data + * @param key - 32-byte AES key + * @returns Decrypted UTF-8 string + */ +export const aesGcmDecrypt = (encryptedBase64: string, key: Buffer): string => { + const data = Buffer.from(encryptedBase64, 'base64') + + if (data.length < GCM_IV_LENGTH + GCM_TAG_LENGTH) { + throw new Error('Encrypted data too short: must contain IV + tag at minimum') + } + + const iv = data.subarray(0, GCM_IV_LENGTH) + const tag = data.subarray(data.length - GCM_TAG_LENGTH) + const ciphertext = data.subarray(GCM_IV_LENGTH, data.length - GCM_TAG_LENGTH) + + const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: GCM_TAG_LENGTH }) + decipher.setAuthTag(tag) + + const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]) + return decrypted.toString('utf-8') +} diff --git a/packages/crypto/src/hash.ts b/packages/crypto/src/hash.ts new file mode 100644 index 0000000..c18c4b6 --- /dev/null +++ b/packages/crypto/src/hash.ts @@ -0,0 +1,15 @@ +import { createHash } from 'node:crypto' + +/** + * Compute SHA-256 hash and return raw Buffer. + */ +export const sha256 = (data: string | Buffer): Buffer => { + return createHash('sha256').update(data).digest() +} + +/** + * Compute SHA-256 hash and return lowercase hex string. + */ +export const sha256Hex = (data: string | Buffer): string => { + return createHash('sha256').update(data).digest('hex') +} diff --git a/packages/crypto/src/hkdf.ts b/packages/crypto/src/hkdf.ts new file mode 100644 index 0000000..fa2f32a --- /dev/null +++ b/packages/crypto/src/hkdf.ts @@ -0,0 +1,15 @@ +import { hkdfSync } from 'node:crypto' + +/** + * Derive a key using HKDF-SHA256. + * + * @param ikm - Input keying material (string, will be UTF-8 encoded) + * @param salt - Salt value (string, will be UTF-8 encoded) + * @param info - Info/context string (will be UTF-8 encoded) + * @param length - Output key length in bytes (default: 32 for AES-256) + * @returns Derived key as Buffer + */ +export const hkdfSha256 = (ikm: string, salt: string, info: string, length = 32): Buffer => { + const derived = hkdfSync('sha256', ikm, salt, info, length) + return Buffer.from(derived) +} diff --git a/packages/crypto/src/hmac.ts b/packages/crypto/src/hmac.ts new file mode 100644 index 0000000..7379521 --- /dev/null +++ b/packages/crypto/src/hmac.ts @@ -0,0 +1,23 @@ +import { createHmac } from 'node:crypto' + +/** + * Compute HMAC-SHA256 and return Base64-encoded signature. + * + * @param key - HMAC key (Buffer) + * @param data - Data to sign (UTF-8 string) + * @returns Base64-encoded HMAC-SHA256 signature + */ +export const hmacSha256Base64 = (key: Buffer, data: string): string => { + return createHmac('sha256', key).update(data, 'utf-8').digest('base64') +} + +/** + * Compute HMAC-SHA256 and return raw Buffer. + * + * @param key - HMAC key (Buffer) + * @param data - Data to sign (UTF-8 string) + * @returns HMAC-SHA256 digest as Buffer + */ +export const hmacSha256 = (key: Buffer, data: string): Buffer => { + return createHmac('sha256', key).update(data, 'utf-8').digest() +} diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts new file mode 100644 index 0000000..e3ea7e0 --- /dev/null +++ b/packages/crypto/src/index.ts @@ -0,0 +1,6 @@ +export { aesGcmDecrypt, aesGcmEncrypt } from './aes-gcm' +export { sha256, sha256Hex } from './hash' +export { hkdfSha256 } from './hkdf' +export { hmacSha256, hmacSha256Base64 } from './hmac' +export { generatePgpKeyPair, pgpSignDetached, pgpVerifyDetached } from './pgp' +export { rsaOaepEncrypt } from './rsa-oaep' diff --git a/packages/crypto/src/pgp.ts b/packages/crypto/src/pgp.ts new file mode 100644 index 0000000..d809647 --- /dev/null +++ b/packages/crypto/src/pgp.ts @@ -0,0 +1,75 @@ +import * as openpgp from 'openpgp' + +/** + * Generate an OpenPGP RSA key pair. + * + * @param name - User name for the key + * @param email - User email for the key + * @returns ASCII-armored private and public keys + */ +export const generatePgpKeyPair = async ( + name: string, + email: string, +): Promise<{ privateKey: string; publicKey: string }> => { + const { privateKey, publicKey } = await openpgp.generateKey({ + type: 'rsa', + rsaBits: 2048, + userIDs: [{ name, email }], + format: 'armored', + }) + + return { privateKey, publicKey } +} + +/** + * Create a detached OpenPGP signature for the given data. + * + * @param data - Raw data to sign (Buffer or Uint8Array) + * @param armoredPrivateKey - ASCII-armored private key + * @returns ASCII-armored detached signature (signature.asc content) + */ +export const pgpSignDetached = async (data: Uint8Array, armoredPrivateKey: string): Promise => { + const privateKey = await openpgp.readPrivateKey({ armoredKey: armoredPrivateKey }) + const message = await openpgp.createMessage({ binary: data }) + + const signature = await openpgp.sign({ + message, + signingKeys: privateKey, + detached: true, + format: 'armored', + }) + + return signature as string +} + +/** + * Verify a detached OpenPGP signature. + * + * @param data - Original data (Buffer or Uint8Array) + * @param armoredSignature - ASCII-armored detached signature + * @param armoredPublicKey - ASCII-armored public key + * @returns true if signature is valid + */ +export const pgpVerifyDetached = async ( + data: Uint8Array, + armoredSignature: string, + armoredPublicKey: string, +): Promise => { + const publicKey = await openpgp.readKey({ armoredKey: armoredPublicKey }) + const signature = await openpgp.readSignature({ armoredSignature }) + const message = await openpgp.createMessage({ binary: data }) + + const verificationResult = await openpgp.verify({ + message, + signature, + verificationKeys: publicKey, + }) + + const { verified } = verificationResult.signatures[0]! + try { + await verified + return true + } catch { + return false + } +} diff --git a/packages/crypto/src/rsa-oaep.ts b/packages/crypto/src/rsa-oaep.ts new file mode 100644 index 0000000..1de1583 --- /dev/null +++ b/packages/crypto/src/rsa-oaep.ts @@ -0,0 +1,34 @@ +import { constants, createPublicKey, publicEncrypt } from 'node:crypto' + +/** + * RSA-OAEP encrypt with platform public key. + * + * Algorithm: RSA/ECB/OAEPWithSHA-256AndMGF1Padding + * - OAEP hash: SHA-256 + * - MGF1 hash: SHA-256 + * + * @param plaintext - UTF-8 string to encrypt + * @param publicKeyBase64 - Platform public key (X.509 DER, Base64 encoded) + * @returns Base64-encoded ciphertext + */ +export const rsaOaepEncrypt = (plaintext: string, publicKeyBase64: string): string => { + // Load public key from Base64-encoded DER (X.509 / SubjectPublicKeyInfo) + const publicKeyDer = Buffer.from(publicKeyBase64, 'base64') + const publicKey = createPublicKey({ + key: publicKeyDer, + format: 'der', + type: 'spki', + }) + + // Encrypt with RSA-OAEP (SHA-256 for both OAEP hash and MGF1) + const encrypted = publicEncrypt( + { + key: publicKey, + padding: constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: 'sha256', + }, + Buffer.from(plaintext, 'utf-8'), + ) + + return encrypted.toString('base64') +} diff --git a/packages/crypto/tsconfig.json b/packages/crypto/tsconfig.json new file mode 100644 index 0000000..3e18e6a --- /dev/null +++ b/packages/crypto/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@furtherverse/tsconfig/bun.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src"] +}