Skip to content

Commit 1bd07f0

Browse files
committed
implemented a version registry
1 parent 311c43d commit 1bd07f0

14 files changed

Lines changed: 308 additions & 207 deletions

File tree

packages/core/src/algorithms/aesgmc/AESGCMEncryption.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

packages/core/src/algorithms/aesgmc/index.ts

Whitespace-only changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
2+
import { CryptoProvider } from '../../../providers/CryptoProvider.js';
3+
import { EncryptionAlgorithm } from '../../../types/index.js';
4+
5+
export class AESGCM implements EncryptionAlgorithm {
6+
private key!: CryptoKey;
7+
8+
constructor(private readonly p: CryptoProvider) {}
9+
10+
setKey(k: CryptoKey) { this.key = k; } // simple setter (ISP)
11+
// or expose through a common mix‑in helper to avoid repetition
12+
13+
async encryptChunk(plain: Uint8Array): Promise<Uint8Array> {
14+
const iv = this.p.getRandomValues(new Uint8Array(12));
15+
const cipher = new Uint8Array(
16+
await this.p.subtle.encrypt({ name: 'AES-GCM', iv }, this.key, plain),
17+
);
18+
const out = new Uint8Array(iv.length + cipher.length);
19+
out.set(iv);
20+
out.set(cipher, iv.length);
21+
return out;
22+
}
23+
async decryptChunk(data: Uint8Array): Promise<Uint8Array> {
24+
const iv = data.slice(0, 12);
25+
const cipher = data.slice(12);
26+
const plain = await this.p.subtle.decrypt({ name: 'AES-GCM', iv }, this.key, cipher);
27+
return new Uint8Array(plain);
28+
}
29+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// packages/core/src/algorithms/kdf/Argon2.ts
2+
import { KeyDerivation } from '../../types/index.js';
3+
import { argon2id, Argon2Tuning } from './argon2-wrapper.js';
4+
import { CryptoProvider } from '../../providers/CryptoProvider.js';
5+
6+
export class Argon2KDF implements KeyDerivation {
7+
constructor(private readonly presets: Record<string, Argon2Tuning>) {}
8+
9+
async derive(
10+
pass: Uint8Array | string,
11+
salt: Uint8Array,
12+
diff: string,
13+
p: CryptoProvider,
14+
): Promise<CryptoKey> {
15+
const { hash } = await argon2id(pass, salt, this.presets[diff], p.isNode ? 'node' : 'browser');
16+
return p.subtle.importKey('raw', hash, { name: 'AES-GCM', length: 256 }, false, [
17+
'encrypt',
18+
'decrypt',
19+
]);
20+
}
21+
}
File renamed without changes.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// packages/core/src/config/VersionRegistry.ts
2+
import { VersionDescriptor } from "../types/index.js";
3+
4+
export class VersionRegistry {
5+
private static readonly byId = new Map<number, VersionDescriptor>();
6+
7+
/** register at app‑start (or dynamically for plug‑ins) */
8+
static register(v: VersionDescriptor): void {
9+
if (this.byId.has(v.id)) throw new Error(`Version ${v.id} already registered`);
10+
this.byId.set(v.id, v);
11+
}
12+
static get(id: number): VersionDescriptor {
13+
const v = this.byId.get(id);
14+
if (!v) throw new Error(`Unknown version: ${id}`);
15+
return v;
16+
}
17+
/** default (current) version – convenience */
18+
static get current(): VersionDescriptor { return this.get(0); }
19+
}
Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
// packages/core/src/config/defaults.ts
2-
export const DefaultConfig = {
3-
saltLengths: { low: 12, high: 16 } as const,
4-
argon: {
5-
low: { time: 5, mem: 64 * 1024, parallelism: 1 },
6-
middle: { time: 20, mem: 64 * 1024, parallelism: 1 },
7-
high: { time: 40, mem: 64 * 1024, parallelism: 1 }
8-
},
9-
chunkSize: 512 * 1024 // 512 KiB
1+
import { VersionRegistry } from './VersionRegistry.js';
2+
import { AESGCM } from '../algorithms/encryption/aes-gmc/AESGCM.js';
3+
import { Argon2KDF } from '../algorithms/kdf/Argon2.js';
4+
import { VersionDescriptor } from '../types/index.js';
5+
6+
export const DEFAULT_DIFFICULTIES = {
7+
low : { time: 5, mem: 64 * 1024, parallelism: 1 },
8+
middle: { time: 20, mem: 64 * 1024, parallelism: 1 },
9+
high : { time: 40, mem: 64 * 1024, parallelism: 1 },
10+
} as const;
11+
12+
const v0: VersionDescriptor = {
13+
id: 0,
14+
cipher: AESGCM,
15+
kdf: new Argon2KDF(DEFAULT_DIFFICULTIES),
16+
saltLengths: { low: 12, high: 16 },
17+
difficulties: DEFAULT_DIFFICULTIES,
18+
defaultChunkSize: 512 * 1024,
1019
};
11-
export type Difficulty = keyof typeof DefaultConfig.argon;
12-
export type SaltStrength = keyof typeof DefaultConfig.saltLengths;
20+
21+
VersionRegistry.register(v0);
22+
23+
export type SaltStrength = 'low' | 'high';
24+
export type Difficulty = keyof typeof DEFAULT_DIFFICULTIES;
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
// packages/core/src/header/constants.ts
2-
export const HEADER_START_BYTE = 0x01; // magic
3-
export const CURRENT_VERSION = 0; // 3 bits
2+
export const HEADER_START_BYTE = 0x01; // magic
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { CURRENT_VERSION, HEADER_START_BYTE } from './constants.js';
2-
import { DefaultConfig, Difficulty, SaltStrength } from '../config/defaults.js';
1+
import { HEADER_START_BYTE } from './constants.js';
2+
import { VersionRegistry } from '../config/VersionRegistry.js';
33

4-
export function decodeHeader(bytes: Uint8Array) {
5-
if (bytes[0] !== HEADER_START_BYTE) throw new Error('INVALID_HEADER');
6-
const info = bytes[1];
7-
const version = info >> 5;
4+
export function decodeHeader(buf: Uint8Array) {
5+
if (buf[0] !== HEADER_START_BYTE) throw new Error('INVALID_HEADER');
6+
const info = buf[1];
7+
const version = info >> 5;
88
const saltStrength = ((info >> 2) & 1) ? 'high' : 'low';
9-
const diffCode = info & 0b11;
10-
const difficulty = (['low','middle','high'] as const)[diffCode];
11-
const saltLen = DefaultConfig.saltLengths[saltStrength];
12-
const salt = bytes.slice(2, 2 + saltLen);
9+
const diffCode = info & 0b11;
10+
const difficulty = (['low', 'middle', 'high'] as const)[diffCode];
11+
const saltLen = VersionRegistry.get(version).saltLengths[saltStrength];
12+
const salt = buf.slice(2, 2 + saltLen);
1313
return { version, difficulty, saltStrength, salt, headerLen: 2 + saltLen };
1414
}
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
// packages/core/src/header/encoder.ts
2-
import { DefaultConfig, Difficulty, SaltStrength } from '../config/defaults.js';
3-
import { CURRENT_VERSION, HEADER_START_BYTE } from './constants.js';
2+
import { HEADER_START_BYTE } from './constants.js';
3+
import { concat } from '../util/bytes.js';
44

55
export function encodeHeader(
6-
diff: Difficulty,
7-
saltStrength: SaltStrength,
8-
salt: Uint8Array
6+
version: number,
7+
difficulty: string,
8+
saltStrength: 'low' | 'high',
9+
salt: Uint8Array,
910
): Uint8Array {
10-
const infoByte =
11-
(CURRENT_VERSION << 5) |
12-
((saltStrength === 'high' ? 1 : 0) << 2) |
13-
({ low: 0, middle: 1, high: 2 } as const)[diff];
14-
return new Uint8Array([HEADER_START_BYTE, infoByte, ...salt]);
11+
const diffCode = { low: 0, middle: 1, high: 2 }[difficulty] ?? 0;
12+
const infoByte = (version << 5) | ((saltStrength === 'high' ? 1 : 0) << 2) | diffCode;
13+
return concat(new Uint8Array([HEADER_START_BYTE, infoByte]), salt);
1514
}

0 commit comments

Comments
 (0)