This commit is contained in:
2026-06-07 10:46:15 +02:00
parent bb422fdd8d
commit 781231e414
21 changed files with 443 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+24
View File
@@ -0,0 +1,24 @@
{
"name": "@cryptgeon/shared",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"@msgpack/msgpack": "^3.1.3",
"@noble/ciphers": "^2.2.0",
"@noble/hashes": "^2.2.0",
"ky": "^2.0.2",
"zod": "^4.4.3"
},
"devDependencies": {
"@tsconfig/strictest": "catalog:",
"@vitest/browser-playwright": "catalog:",
"typescript": "catalog:",
"vitest": "catalog:"
},
"scripts": {
"test:browser": "vitest"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

@@ -0,0 +1,38 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`encryption > stable derive key 1`] = `
Uint8Array [
107,
115,
150,
59,
243,
162,
229,
9,
221,
235,
124,
184,
124,
51,
96,
32,
183,
240,
114,
43,
221,
208,
248,
142,
16,
45,
163,
137,
102,
240,
245,
198,
]
`;
+14
View File
@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { upload } from "./actions";
import { deriveKey } from "./encryption";
describe("actions", () => {
describe("upload", () => {
it("upload", async () => {
const key = deriveKey("abc");
await expect(
upload({ public: "foo", metadata: { views: 1 }, files: [] }, key),
).resolves.toBe({});
});
});
});
+39
View File
@@ -0,0 +1,39 @@
import { encode } from "@msgpack/msgpack";
import { encrypt } from "./encryption";
import { ClientNote, ServerNote } from "./types";
import ky from "ky";
import z from "zod";
const client = ky.extend({ baseUrl: "http://localhost:8000/api" });
export async function upload(
note: ClientNote,
key: Uint8Array,
): Promise<string> {
const data = encode(note.files);
const encrypted = encrypt(data, key);
const serverNote: ServerNote = {
metadata: structuredClone(note.metadata),
public: "",
data: encrypted,
};
const payload = encode(serverNote);
const response = await client
.post("/notes/v2", {
headers: {
"content-type": "application/msgpack",
},
body: new Blob([payload]).stream(),
})
.json(
z.object({
id: z.string().nonempty(),
}),
);
console.info("created note", response);
throw new Error("not implemented");
}
export function view(noteId: string): Promise<ClientNote> {
throw new Error("not implemented");
}
+42
View File
@@ -0,0 +1,42 @@
import { describe, expect, it } from "vitest";
import {
decrypt,
deriveKey,
encrypt,
generateKey,
utf8ToBytes,
} from "./encryption";
describe("encryption", () => {
it("chacha20 custom key", () => {
const password = "abc";
const payload = utf8ToBytes("libero iste qui");
const key = deriveKey(password);
const encrypted = encrypt(payload, key);
const decrypted = decrypt(encrypted, key);
expect(decrypted).toEqual(payload);
});
it("chacha20 auto key", () => {
const payload = utf8ToBytes(
"Earum id inventore debitis rerum minima necessitatibus consequuntur.",
);
const key = generateKey();
const encrypted = encrypt(payload, key);
const decrypted = decrypt(encrypted, key);
expect(decrypted).toEqual(payload);
});
it("stable derive key", () => {
const password = "alias laboriosam porro";
expect(deriveKey(password)).toMatchSnapshot();
});
it("derived key has same length as generated", () => {
const derived = deriveKey("ipsam esse asperiores");
const generated = generateKey();
expect(derived.length).toEqual(generated.length);
});
});
+40
View File
@@ -0,0 +1,40 @@
import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
import {
managedNonce,
randomBytes,
utf8ToBytes,
} from "@noble/ciphers/utils.js";
import { scrypt } from "@noble/hashes/scrypt.js";
export { bytesToUtf8, utf8ToBytes } from "@noble/ciphers/utils.js";
const APP_SPECIFIC_SECRET = "758ac0b9d5f04efca13f57909d3d0fc0";
const SECURITY_LEVEL = 2 ** 15;
const KEY_SIZE = 32;
export function deriveKey(password: string) {
const key = scrypt(password, APP_SPECIFIC_SECRET, {
N: SECURITY_LEVEL,
r: 8,
p: 1,
dkLen: KEY_SIZE,
});
return key;
}
export function generateKey(): Uint8Array {
return randomBytes(KEY_SIZE);
}
export function encrypt(payload: Uint8Array, key: Uint8Array): Uint8Array {
const chacha = managedNonce(xchacha20poly1305)(key);
const data = payload instanceof Uint8Array ? payload : utf8ToBytes(payload);
const ciphertext = chacha.encrypt(data);
return ciphertext;
}
export function decrypt(ciphertext: Uint8Array, key: Uint8Array): Uint8Array {
const chacha = managedNonce(xchacha20poly1305)(key);
const decrypted = chacha.decrypt(ciphertext, key);
return decrypted;
}
+1
View File
@@ -0,0 +1 @@
export * as encryption from "./encryption";
+39
View File
@@ -0,0 +1,39 @@
/**
* `metadata` is public, and NOT encrypted
*/
export type NoteMetadata = {
expiration?: number;
views?: number;
};
export type ClientNote = {
metadata: NoteMetadata;
/**
* public information
*/
public: string;
/**
* `files` are encrypted
*/
files: {
name: string;
type: string;
content: Uint8Array;
}[];
};
export type ServerNote = {
metadata: NoteMetadata;
/**
* Publicly available information
*/
public: string;
/**
* Encrypted binary blob
*/
data: Uint8Array;
};
+8
View File
@@ -0,0 +1,8 @@
{
"extends": ["@tsconfig/strictest"],
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
},
}
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
export default defineConfig({
test: {
projects: [
{ test: { name: "node", environment: "node" } },
{
test: {
name: "Browser",
browser: {
enabled: true,
provider: playwright(),
headless: true,
// https://vitest.dev/config/browser/playwright
instances: [
{ browser: "chromium" },
{ browser: "firefox" },
{ browser: "webkit" },
],
},
},
},
],
},
});