General Upgrade, NEW: WebServers, Better Docs
Some checks failed
CI / checks (push) Failing after 4m20s

This commit is contained in:
Alex Emmet 2026-07-18 03:08:03 +02:00
commit 62a8327239
122 changed files with 10187 additions and 5169 deletions

View file

@ -1,35 +1,5 @@
import * as bindings from "mtp/raw";
function utf8Encode(text: string): Uint8Array {
if (typeof TextEncoder !== "undefined") {
return new TextEncoder().encode(text);
}
if (typeof Buffer !== "undefined") {
return new Uint8Array(Buffer.from(text, "utf-8"));
}
const bytes = new Uint8Array(text.length * 4);
let len = 0;
for (let i = 0; i < text.length; i += 1) {
const code = text.codePointAt(i) as number;
if (code < 0x80) {
bytes[len++] = code;
} else if (code < 0x800) {
bytes[len++] = 0xc0 | (code >> 6);
bytes[len++] = 0x80 | (code & 0x3f);
} else if (code < 0x10000) {
bytes[len++] = 0xe0 | (code >> 12);
bytes[len++] = 0x80 | ((code >> 6) & 0x3f);
bytes[len++] = 0x80 | (code & 0x3f);
} else {
bytes[len++] = 0xf0 | (code >> 18);
bytes[len++] = 0x80 | ((code >> 12) & 0x3f);
bytes[len++] = 0x80 | ((code >> 6) & 0x3f);
bytes[len++] = 0x80 | (code & 0x3f);
i += 1;
}
}
return bytes.subarray(0, len);
}
import { concatBytes, utf8Encode, writeU64BE } from "./utils.js";
export const HKDF_SALT_ROOT = "mtp-e2ee-v1-root";
const HKDF_INITIATOR_SEND = "mtp-e2ee-v1-initiator-send";
@ -55,10 +25,17 @@ export interface MTPSessionState {
recvChainKey: Uint8Array;
sendCount: number;
recvCount: number;
/** Derived receive keys retained for bounded out-of-order delivery. */
skippedMessageKeys?: SkippedMessageKey[];
createdAt: number;
updatedAt: number;
}
export interface SkippedMessageKey {
messageNumber: number;
key: Uint8Array;
}
export interface MTPSessionStorage {
getSession(conversationId: string): Promise<MTPSessionState | null>;
setSession(state: MTPSessionState): Promise<void>;
@ -68,15 +45,40 @@ export interface MTPSessionStorage {
export class InMemorySessionStorage implements MTPSessionStorage {
private store = new Map<string, MTPSessionState>();
private cloneSession(state: MTPSessionState): MTPSessionState {
return {
...state,
peerPublicKey: state.peerPublicKey.slice(),
sendChainKey: state.sendChainKey.slice(),
recvChainKey: state.recvChainKey.slice(),
skippedMessageKeys: (state.skippedMessageKeys ?? []).map((skipped) => ({
messageNumber: skipped.messageNumber,
key: skipped.key.slice(),
})),
};
}
private zeroizeSession(state: MTPSessionState): void {
state.sendChainKey.fill(0);
state.recvChainKey.fill(0);
for (const skipped of state.skippedMessageKeys ?? []) skipped.key.fill(0);
}
async getSession(conversationId: string): Promise<MTPSessionState | null> {
return this.store.get(conversationId) ?? null;
const state = this.store.get(conversationId);
return state ? this.cloneSession(state) : null;
}
async setSession(state: MTPSessionState): Promise<void> {
this.store.set(state.conversationId, { ...state });
const replacement = this.cloneSession(state);
const previous = this.store.get(state.conversationId);
if (previous) this.zeroizeSession(previous);
this.store.set(state.conversationId, replacement);
}
async deleteSession(conversationId: string): Promise<void> {
const previous = this.store.get(conversationId);
if (previous) this.zeroizeSession(previous);
this.store.delete(conversationId);
}
}
@ -90,27 +92,6 @@ function writeU32BE(value: number): Uint8Array {
]);
}
function writeU64BE(value: bigint): Uint8Array {
if (value < 0n || value > 0xffff_ffff_ffff_ffffn)
throw new Error("u64 out of range");
const buf = new Uint8Array(8);
for (let i = 7; i >= 0; i -= 1) {
buf[i] = Number(value & 0xffn);
value >>= 8n;
}
return buf;
}
function concatBytes(parts: Uint8Array[]): Uint8Array {
const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));
let offset = 0;
for (const part of parts) {
out.set(part, offset);
offset += part.length;
}
return out;
}
function transcriptField(label: string, value: Uint8Array): Uint8Array {
const labelBytes = utf8Encode(label);
return concatBytes([
@ -240,6 +221,7 @@ export class MTPSessionManager {
recvChainKey: args.role === "initiator" ? initiatorRecv : initiatorSend,
sendCount: 0,
recvCount: 0,
skippedMessageKeys: [],
createdAt: now,
updatedAt: now,
};